@pyreon/document 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +7 -4
  2. package/lib/confluence-Bd3ua1Ut.js.map +1 -1
  3. package/lib/csv-COrS4qdy.js.map +1 -1
  4. package/lib/discord-BLUnkEh9.js.map +1 -1
  5. package/lib/docx-uNAel545.js.map +1 -1
  6. package/lib/email-D0bbfWq4.js.map +1 -1
  7. package/lib/google-chat-CkKCBUWC.js.map +1 -1
  8. package/lib/html-B5biprN2.js.map +1 -1
  9. package/lib/index.js.map +1 -1
  10. package/lib/markdown-CdtlFGC0.js.map +1 -1
  11. package/lib/notion-iG2C5bEY.js.map +1 -1
  12. package/lib/pdf-IuBgTb3T.js.map +1 -1
  13. package/lib/pptx-DXiMiYFM.js.map +1 -1
  14. package/lib/sanitize-O_3j1mNJ.js.map +1 -1
  15. package/lib/slack-BI3EQwYm.js.map +1 -1
  16. package/lib/svg-BKxumy-p.js.map +1 -1
  17. package/lib/teams-Cwz9lce0.js.map +1 -1
  18. package/lib/telegram-gYFqyMXb.js.map +1 -1
  19. package/lib/text-l1XNXBOC.js.map +1 -1
  20. package/lib/types/index.d.ts +27 -27
  21. package/lib/whatsapp-CjSGoOKx.js.map +1 -1
  22. package/lib/xlsx-Cvu4LBNy.js.map +1 -1
  23. package/package.json +21 -21
  24. package/src/builder.ts +36 -36
  25. package/src/download.ts +32 -32
  26. package/src/index.ts +5 -10
  27. package/src/nodes.ts +45 -45
  28. package/src/render.ts +43 -43
  29. package/src/renderers/confluence.ts +63 -63
  30. package/src/renderers/csv.ts +10 -10
  31. package/src/renderers/discord.ts +37 -37
  32. package/src/renderers/docx.ts +57 -57
  33. package/src/renderers/email.ts +72 -72
  34. package/src/renderers/google-chat.ts +34 -34
  35. package/src/renderers/html.ts +76 -76
  36. package/src/renderers/markdown.ts +42 -42
  37. package/src/renderers/notion.ts +60 -60
  38. package/src/renderers/pdf.ts +78 -78
  39. package/src/renderers/pptx.ts +51 -51
  40. package/src/renderers/slack.ts +48 -48
  41. package/src/renderers/svg.ts +47 -47
  42. package/src/renderers/teams.ts +67 -67
  43. package/src/renderers/telegram.ts +39 -39
  44. package/src/renderers/text.ts +43 -43
  45. package/src/renderers/whatsapp.ts +33 -33
  46. package/src/renderers/xlsx.ts +35 -35
  47. package/src/sanitize.ts +20 -20
  48. package/src/tests/document.test.ts +1302 -1302
  49. package/src/tests/stress.test.ts +110 -110
  50. package/src/types.ts +61 -61
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/render.ts","../src/download.ts","../src/nodes.ts","../src/builder.ts"],"sourcesContent":["import type { DocNode, DocumentRenderer, OutputFormat, RenderOptions, RenderResult } from \"./types\"\n\n// ─── Renderer Registry ──────────────────────────────────────────────────────\n\nconst renderers = new Map<string, DocumentRenderer | (() => Promise<DocumentRenderer>)>()\n\n/**\n * Register a custom renderer for a format.\n *\n * @example\n * ```ts\n * registerRenderer('thermal', {\n * render(node, options) {\n * // Walk nodes → ESC/POS commands\n * return escPosBuffer\n * },\n * })\n *\n * await render(receipt, 'thermal')\n * ```\n */\nexport function registerRenderer(\n format: string,\n renderer: DocumentRenderer | (() => Promise<DocumentRenderer>),\n): void {\n renderers.set(format, renderer)\n}\n\n/**\n * Remove a registered renderer.\n */\nexport function unregisterRenderer(format: string): void {\n renderers.delete(format)\n}\n\n// ─── Built-in Renderer Loaders ──────────────────────────────────────────────\n\n// Built-in renderers are registered lazily — only loaded when first used.\n\nregisterRenderer(\"html\", () => import(\"./renderers/html\").then((m) => m.htmlRenderer))\n\nregisterRenderer(\"email\", () => import(\"./renderers/email\").then((m) => m.emailRenderer))\n\nregisterRenderer(\"md\", () => import(\"./renderers/markdown\").then((m) => m.markdownRenderer))\n\nregisterRenderer(\"text\", () => import(\"./renderers/text\").then((m) => m.textRenderer))\n\nregisterRenderer(\"csv\", () => import(\"./renderers/csv\").then((m) => m.csvRenderer))\n\nregisterRenderer(\"pdf\", () => import(\"./renderers/pdf\").then((m) => m.pdfRenderer))\n\nregisterRenderer(\"docx\", () => import(\"./renderers/docx\").then((m) => m.docxRenderer))\n\nregisterRenderer(\"xlsx\", () => import(\"./renderers/xlsx\").then((m) => m.xlsxRenderer))\n\nregisterRenderer(\"pptx\", () => import(\"./renderers/pptx\").then((m) => m.pptxRenderer))\n\nregisterRenderer(\"slack\", () => import(\"./renderers/slack\").then((m) => m.slackRenderer))\n\nregisterRenderer(\"svg\", () => import(\"./renderers/svg\").then((m) => m.svgRenderer))\n\nregisterRenderer(\"teams\", () => import(\"./renderers/teams\").then((m) => m.teamsRenderer))\n\nregisterRenderer(\"discord\", () => import(\"./renderers/discord\").then((m) => m.discordRenderer))\n\nregisterRenderer(\"telegram\", () => import(\"./renderers/telegram\").then((m) => m.telegramRenderer))\n\nregisterRenderer(\"notion\", () => import(\"./renderers/notion\").then((m) => m.notionRenderer))\n\nregisterRenderer(\"confluence\", () =>\n import(\"./renderers/confluence\").then((m) => m.confluenceRenderer),\n)\n\nregisterRenderer(\"whatsapp\", () => import(\"./renderers/whatsapp\").then((m) => m.whatsappRenderer))\n\nregisterRenderer(\"google-chat\", () =>\n import(\"./renderers/google-chat\").then((m) => m.googleChatRenderer),\n)\n\n// ─── Render Function ────────────────────────────────────────────────────────\n\nasync function resolveRenderer(format: string): Promise<DocumentRenderer> {\n const entry = renderers.get(format)\n if (!entry) {\n throw new Error(\n `[@pyreon/document] No renderer registered for format '${format}'. Available: ${[...renderers.keys()].join(\", \")}`,\n )\n }\n\n if (typeof entry === \"function\") {\n const renderer = await entry()\n // Cache the resolved renderer so we don't re-import\n renderers.set(format, renderer)\n return renderer\n }\n\n return entry\n}\n\n/**\n * Render a document node tree to the specified format.\n *\n * @example\n * ```tsx\n * const doc = <Document title=\"Report\"><Page>...</Page></Document>\n *\n * const html = await render(doc, 'html') // → HTML string\n * const pdf = await render(doc, 'pdf') // → PDF Uint8Array\n * const docx = await render(doc, 'docx') // → DOCX Uint8Array\n * const email = await render(doc, 'email') // → email-safe HTML string\n * const md = await render(doc, 'md') // → Markdown string\n * ```\n */\nexport async function render(\n node: DocNode,\n format: OutputFormat | string,\n options?: RenderOptions,\n): Promise<RenderResult> {\n const renderer = await resolveRenderer(format)\n return renderer.render(node, options)\n}\n\n/** @internal For testing — reset renderer registry to defaults. */\nexport function _resetRenderers(): void {\n renderers.clear()\n // Re-register built-in lazy loaders\n registerRenderer(\"html\", () => import(\"./renderers/html\").then((m) => m.htmlRenderer))\n registerRenderer(\"email\", () => import(\"./renderers/email\").then((m) => m.emailRenderer))\n registerRenderer(\"md\", () => import(\"./renderers/markdown\").then((m) => m.markdownRenderer))\n registerRenderer(\"text\", () => import(\"./renderers/text\").then((m) => m.textRenderer))\n registerRenderer(\"csv\", () => import(\"./renderers/csv\").then((m) => m.csvRenderer))\n registerRenderer(\"pdf\", () => import(\"./renderers/pdf\").then((m) => m.pdfRenderer))\n registerRenderer(\"docx\", () => import(\"./renderers/docx\").then((m) => m.docxRenderer))\n registerRenderer(\"xlsx\", () => import(\"./renderers/xlsx\").then((m) => m.xlsxRenderer))\n registerRenderer(\"pptx\", () => import(\"./renderers/pptx\").then((m) => m.pptxRenderer))\n registerRenderer(\"slack\", () => import(\"./renderers/slack\").then((m) => m.slackRenderer))\n registerRenderer(\"svg\", () => import(\"./renderers/svg\").then((m) => m.svgRenderer))\n registerRenderer(\"teams\", () => import(\"./renderers/teams\").then((m) => m.teamsRenderer))\n registerRenderer(\"discord\", () => import(\"./renderers/discord\").then((m) => m.discordRenderer))\n registerRenderer(\"telegram\", () => import(\"./renderers/telegram\").then((m) => m.telegramRenderer))\n registerRenderer(\"notion\", () => import(\"./renderers/notion\").then((m) => m.notionRenderer))\n registerRenderer(\"confluence\", () =>\n import(\"./renderers/confluence\").then((m) => m.confluenceRenderer),\n )\n registerRenderer(\"whatsapp\", () => import(\"./renderers/whatsapp\").then((m) => m.whatsappRenderer))\n registerRenderer(\"google-chat\", () =>\n import(\"./renderers/google-chat\").then((m) => m.googleChatRenderer),\n )\n}\n","import { render } from \"./render\"\nimport type { DocNode, RenderOptions } from \"./types\"\n\nconst FORMAT_MAP: Record<string, string> = {\n html: \"html\",\n htm: \"html\",\n pdf: \"pdf\",\n docx: \"docx\",\n doc: \"docx\",\n xlsx: \"xlsx\",\n xls: \"xlsx\",\n pptx: \"pptx\",\n ppt: \"pptx\",\n md: \"md\",\n txt: \"text\",\n csv: \"csv\",\n svg: \"svg\",\n}\n\nconst MIME_TYPES: Record<string, string> = {\n html: \"text/html\",\n pdf: \"application/pdf\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n email: \"text/html\",\n md: \"text/markdown\",\n text: \"text/plain\",\n csv: \"text/csv\",\n svg: \"image/svg+xml\",\n}\n\n/**\n * Download a document in the browser.\n *\n * @example\n * ```tsx\n * await download(doc, 'report.pdf')\n * await download(doc, 'report.docx')\n * ```\n */\nexport async function download(\n node: DocNode,\n filename: string,\n options?: RenderOptions,\n): Promise<void> {\n const ext = filename.split(\".\").pop()?.toLowerCase()\n if (!ext) {\n throw new Error(\"[@pyreon/document] Filename must have an extension (e.g., report.pdf).\")\n }\n\n const format = FORMAT_MAP[ext]\n if (!format) {\n throw new Error(\n `[@pyreon/document] Unknown file extension '.${ext}'. Supported: ${Object.keys(FORMAT_MAP).join(\", \")}`,\n )\n }\n\n const result = await render(node, format, options)\n\n const blob =\n result instanceof Uint8Array\n ? new Blob([result as BlobPart])\n : new Blob([result], {\n type: MIME_TYPES[format] ?? \"application/octet-stream\",\n })\n\n if (typeof document === \"undefined\") {\n throw new Error(\"[@pyreon/document] download() requires a browser environment.\")\n }\n\n const url = URL.createObjectURL(blob)\n const a = document.createElement(\"a\")\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n","import type {\n ButtonProps,\n CodeProps,\n ColumnProps,\n DividerProps,\n DocChild,\n DocNode,\n DocumentProps,\n HeadingProps,\n ImageProps,\n LinkProps,\n ListItemProps,\n ListProps,\n NodeType,\n PageProps,\n QuoteProps,\n RowProps,\n SectionProps,\n SpacerProps,\n TableProps,\n TextProps,\n} from \"./types\"\n\n// ─── Node Constructor ───────────────────────────────────────────────────────\n\nfunction createNode(type: NodeType, props: object, children: unknown): DocNode {\n return {\n type,\n props: props as Record<string, unknown>,\n children: normalizeChildren(children),\n }\n}\n\nfunction normalizeChildren(children: unknown): DocChild[] {\n if (children == null || children === false) return []\n if (typeof children === \"string\") return [children]\n if (typeof children === \"number\") return [String(children)]\n if (Array.isArray(children)) return children.flatMap(normalizeChildren)\n if (isDocNode(children)) return [children]\n if (typeof children === \"object\") {\n throw new Error(\n \"[@pyreon/document] Invalid child: plain objects are not valid document children. Use a document node (Text, Heading, etc.) instead.\",\n )\n }\n return [String(children)]\n}\n\n/** Type guard — checks if a value is a DocNode. */\nexport function isDocNode(value: unknown): value is DocNode {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n \"props\" in value &&\n \"children\" in value\n )\n}\n\n// ─── Document Primitives ────────────────────────────────────────────────────\n\n/**\n * Root document container. Holds metadata and pages.\n *\n * @example\n * ```tsx\n * <Document title=\"Invoice #1234\" author=\"Acme Corp\">\n * <Page>...</Page>\n * </Document>\n * ```\n */\nexport function Document(props: DocumentProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"document\", rest, children)\n}\nDocument._documentType = \"document\" as const\n\n/**\n * Page container. Maps to a PDF page, DOCX section, or email block.\n *\n * @example\n * ```tsx\n * <Page size=\"A4\" margin={40}>\n * <Heading>Title</Heading>\n * </Page>\n * ```\n */\nexport function Page(props: PageProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"page\", rest, children)\n}\nPage._documentType = \"page\" as const\n\n/**\n * Layout section — groups content with optional direction, padding, background.\n *\n * @example\n * ```tsx\n * <Section background=\"#f5f5f5\" padding={20} direction=\"row\" gap={12}>\n * <Text>Left</Text>\n * <Text>Right</Text>\n * </Section>\n * ```\n */\nexport function Section(props: SectionProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"section\", rest, children)\n}\nSection._documentType = \"section\" as const\n\n/**\n * Horizontal layout container.\n *\n * @example\n * ```tsx\n * <Row gap={20}>\n * <Column width=\"60%\"><Text>Main</Text></Column>\n * <Column width=\"40%\"><Text>Side</Text></Column>\n * </Row>\n * ```\n */\nexport function Row(props: RowProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"row\", rest, children)\n}\nRow._documentType = \"row\" as const\n\n/**\n * Column within a Row.\n */\nexport function Column(props: ColumnProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"column\", rest, children)\n}\nColumn._documentType = \"column\" as const\n\n/**\n * Heading text (h1–h6).\n *\n * @example\n * ```tsx\n * <Heading level={1}>Invoice #1234</Heading>\n * <Heading level={2} color=\"#666\">Details</Heading>\n * ```\n */\nexport function Heading(props: HeadingProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"heading\", { level: 1, ...rest }, children)\n}\nHeading._documentType = \"heading\" as const\n\n/**\n * Text paragraph with optional formatting.\n *\n * @example\n * ```tsx\n * <Text bold size={14} color=\"#333\">Hello World</Text>\n * <Text italic align=\"right\">Subtotal: $100</Text>\n * ```\n */\nexport function Text(props: TextProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"text\", rest, children)\n}\nText._documentType = \"text\" as const\n\n/**\n * Hyperlink.\n *\n * @example\n * ```tsx\n * <Link href=\"https://example.com\">Visit site</Link>\n * ```\n */\nexport function Link(props: LinkProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"link\", rest, children)\n}\nLink._documentType = \"link\" as const\n\n/**\n * Image with optional sizing and caption.\n *\n * @example\n * ```tsx\n * <Image src=\"/logo.png\" width={120} alt=\"Company Logo\" />\n * <Image src={chartDataUrl} width={500} caption=\"Revenue Chart\" />\n * ```\n */\nexport function Image(props: ImageProps): DocNode {\n return createNode(\"image\", props, [])\n}\nImage._documentType = \"image\" as const\n\n/**\n * Data table with columns and rows.\n *\n * @example\n * ```tsx\n * <Table\n * columns={['Name', 'Price', 'Qty']}\n * rows={[['Widget', '$10', '5'], ['Gadget', '$20', '3']]}\n * striped\n * headerStyle={{ background: '#1a1a2e', color: '#fff' }}\n * />\n * ```\n */\nexport function Table(props: TableProps): DocNode {\n return createNode(\"table\", props, [])\n}\nTable._documentType = \"table\" as const\n\n/**\n * Ordered or unordered list.\n *\n * @example\n * ```tsx\n * <List ordered>\n * <ListItem>First item</ListItem>\n * <ListItem>Second item</ListItem>\n * </List>\n * ```\n */\nexport function List(props: ListProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"list\", rest, children)\n}\nList._documentType = \"list\" as const\n\n/**\n * Single list item within a List.\n */\nexport function ListItem(props: ListItemProps): DocNode {\n const { children } = props\n return createNode(\"list-item\", {}, children)\n}\nListItem._documentType = \"list-item\" as const\n\n/**\n * Code block with optional language hint.\n *\n * @example\n * ```tsx\n * <Code language=\"typescript\">const x = 42</Code>\n * ```\n */\nexport function Code(props: CodeProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"code\", rest, children)\n}\nCode._documentType = \"code\" as const\n\n/**\n * Horizontal divider line.\n *\n * @example\n * ```tsx\n * <Divider color=\"#ddd\" thickness={2} />\n * ```\n */\nexport function Divider(props: DividerProps = {}): DocNode {\n return createNode(\"divider\", props, [])\n}\nDivider._documentType = \"divider\" as const\n\n/**\n * Page break — forces content after this point to the next page (PDF/DOCX)\n * or inserts a visual separator (HTML/email).\n *\n * @example\n * ```tsx\n * <PageBreak />\n * ```\n */\nexport function PageBreak(): DocNode {\n return createNode(\"page-break\", {}, [])\n}\nPageBreak._documentType = \"page-break\" as const\n\n/**\n * Vertical spacer.\n *\n * @example\n * ```tsx\n * <Spacer height={20} />\n * ```\n */\nexport function Spacer(props: SpacerProps): DocNode {\n return createNode(\"spacer\", props, [])\n}\nSpacer._documentType = \"spacer\" as const\n\n/**\n * CTA button — renders as a bulletproof button in email, styled link in PDF/DOCX.\n *\n * @example\n * ```tsx\n * <Button href=\"https://acme.com/pay\" background=\"#4f46e5\" color=\"#fff\">\n * Pay Now\n * </Button>\n * ```\n */\nexport function Button(props: ButtonProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"button\", rest, children)\n}\nButton._documentType = \"button\" as const\n\n/**\n * Block quote.\n *\n * @example\n * ```tsx\n * <Quote borderColor=\"#4f46e5\">This is a quote.</Quote>\n * ```\n */\nexport function Quote(props: QuoteProps): DocNode {\n const { children, ...rest } = props\n return createNode(\"quote\", rest, children)\n}\nQuote._documentType = \"quote\" as const\n","import { download } from \"./download\"\nimport {\n Button,\n Code,\n Divider,\n Document,\n Heading,\n Image,\n Link,\n List,\n ListItem,\n Page,\n PageBreak,\n Quote,\n Section,\n Spacer,\n Table,\n Text,\n} from \"./nodes\"\nimport { render } from \"./render\"\nimport type {\n ButtonProps,\n CodeProps,\n DividerProps,\n DocNode,\n DocumentBuilder,\n DocumentProps,\n HeadingProps,\n ImageProps,\n LinkProps,\n ListProps,\n QuoteProps,\n RenderOptions,\n TableProps,\n TextProps,\n} from \"./types\"\n\n/**\n * Create a document using the builder pattern — no JSX needed.\n *\n * @example\n * ```ts\n * const doc = createDocument({ title: 'Report' })\n * .heading('Sales Report')\n * .text('Q4 performance summary.')\n * .table({ columns: ['Region', 'Revenue'], rows: [['US', '$1M']] })\n *\n * await doc.toPdf()\n * await doc.download('report.pdf')\n * ```\n */\nexport function createDocument(props: DocumentProps = {}): DocumentBuilder {\n const sections: DocNode[] = []\n\n function getNode(): DocNode {\n return Document({ ...props, children: [Page({ children: sections })] })\n }\n\n const builder: DocumentBuilder = {\n heading(text: string, p?: Omit<HeadingProps, \"children\">) {\n sections.push(Heading({ ...p, children: text }))\n return builder\n },\n\n text(text: string, p?: Omit<TextProps, \"children\">) {\n sections.push(Text({ ...p, children: text }))\n return builder\n },\n\n paragraph(text: string, p?: Omit<TextProps, \"children\">) {\n return builder.text(text, p)\n },\n\n image(src: string, p?: Omit<ImageProps, \"src\">) {\n sections.push(Image({ src, ...p }))\n return builder\n },\n\n table(p: TableProps) {\n sections.push(Table(p))\n return builder\n },\n\n list(items: string[], p?: Omit<ListProps, \"children\">) {\n sections.push(\n List({\n ...p,\n children: items.map((item) => ListItem({ children: item })),\n }),\n )\n return builder\n },\n\n code(text: string, p?: Omit<CodeProps, \"children\">) {\n sections.push(Code({ ...p, children: text }))\n return builder\n },\n\n divider(p?: DividerProps) {\n sections.push(Divider(p))\n return builder\n },\n\n spacer(height: number) {\n sections.push(Spacer({ height }))\n return builder\n },\n\n quote(text: string, p?: Omit<QuoteProps, \"children\">) {\n sections.push(Quote({ ...p, children: text }))\n return builder\n },\n\n button(text: string, p: Omit<ButtonProps, \"children\">) {\n sections.push(Button({ ...p, children: text }))\n return builder\n },\n\n link(text: string, p: Omit<LinkProps, \"children\">) {\n sections.push(Link({ ...p, children: text }))\n return builder\n },\n\n pageBreak() {\n sections.push(PageBreak())\n return builder\n },\n\n add(node) {\n if (Array.isArray(node)) {\n sections.push(...node)\n } else {\n sections.push(node)\n }\n return builder\n },\n\n section(children) {\n sections.push(Section({ children }))\n return builder\n },\n\n chart(instance: unknown, p?: { width?: number; height?: number; caption?: string }) {\n // Try to get data URL from chart instance\n const inst = instance as { getDataURL?: (opts: unknown) => string }\n if (inst?.getDataURL) {\n const dataUrl = inst.getDataURL({ type: \"png\", pixelRatio: 2 })\n sections.push(\n Image({\n src: dataUrl,\n ...(p?.width != null ? { width: p.width } : {}),\n ...(p?.height != null ? { height: p.height } : {}),\n ...(p?.caption != null ? { caption: p.caption } : {}),\n }),\n )\n } else {\n sections.push(\n Text({\n children: \"[Chart]\",\n italic: true,\n color: \"#999\",\n } as TextProps & { children: string }),\n )\n }\n return builder\n },\n\n flow(instance: unknown, p?: { width?: number; height?: number; caption?: string }) {\n // Try to get SVG from flow instance\n const inst = instance as { toSVG?: () => string }\n if (inst?.toSVG) {\n const svg = inst.toSVG()\n sections.push(\n Image({\n src: `data:image/svg+xml,${encodeURIComponent(svg)}`,\n ...(p?.width != null ? { width: p.width } : {}),\n ...(p?.height != null ? { height: p.height } : {}),\n ...(p?.caption != null ? { caption: p.caption } : {}),\n }),\n )\n } else {\n sections.push(\n Text({\n children: \"[Flow Diagram]\",\n italic: true,\n color: \"#999\",\n } as TextProps & { children: string }),\n )\n }\n return builder\n },\n\n build() {\n return getNode()\n },\n\n async toHtml(options?: RenderOptions) {\n return render(getNode(), \"html\", options) as Promise<string>\n },\n\n async toPdf(options?: RenderOptions) {\n return render(getNode(), \"pdf\", options) as Promise<Uint8Array>\n },\n\n async toDocx(options?: RenderOptions) {\n return render(getNode(), \"docx\", options) as Promise<Uint8Array>\n },\n\n async toEmail(options?: RenderOptions) {\n return render(getNode(), \"email\", options) as Promise<string>\n },\n\n async toPptx(options?: RenderOptions) {\n return render(getNode(), \"pptx\", options) as Promise<Uint8Array>\n },\n\n async toXlsx(options?: RenderOptions) {\n return render(getNode(), \"xlsx\", options) as Promise<Uint8Array>\n },\n\n async toMarkdown(options?: RenderOptions) {\n return render(getNode(), \"md\", options) as Promise<string>\n },\n\n async toText(options?: RenderOptions) {\n return render(getNode(), \"text\", options) as Promise<string>\n },\n\n async toCsv(options?: RenderOptions) {\n return render(getNode(), \"csv\", options) as Promise<string>\n },\n\n async toSlack(options?: RenderOptions) {\n return render(getNode(), \"slack\", options) as Promise<string>\n },\n\n async toSvg(options?: RenderOptions) {\n return render(getNode(), \"svg\", options) as Promise<string>\n },\n\n async toTeams(options?: RenderOptions) {\n return render(getNode(), \"teams\", options) as Promise<string>\n },\n\n async toDiscord(options?: RenderOptions) {\n return render(getNode(), \"discord\", options) as Promise<string>\n },\n\n async toTelegram(options?: RenderOptions) {\n return render(getNode(), \"telegram\", options) as Promise<string>\n },\n\n async toNotion(options?: RenderOptions) {\n return render(getNode(), \"notion\", options) as Promise<string>\n },\n\n async toConfluence(options?: RenderOptions) {\n return render(getNode(), \"confluence\", options) as Promise<string>\n },\n\n async toWhatsApp(options?: RenderOptions) {\n return render(getNode(), \"whatsapp\", options) as Promise<string>\n },\n\n async toGoogleChat(options?: RenderOptions) {\n return render(getNode(), \"google-chat\", options) as Promise<string>\n },\n\n async download(filename: string, options?: RenderOptions) {\n return download(getNode(), filename, options)\n },\n }\n\n return builder\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/render.ts","../src/download.ts","../src/nodes.ts","../src/builder.ts"],"sourcesContent":["import type { DocNode, DocumentRenderer, OutputFormat, RenderOptions, RenderResult } from './types'\n\n// ─── Renderer Registry ──────────────────────────────────────────────────────\n\nconst renderers = new Map<string, DocumentRenderer | (() => Promise<DocumentRenderer>)>()\n\n/**\n * Register a custom renderer for a format.\n *\n * @example\n * ```ts\n * registerRenderer('thermal', {\n * render(node, options) {\n * // Walk nodes → ESC/POS commands\n * return escPosBuffer\n * },\n * })\n *\n * await render(receipt, 'thermal')\n * ```\n */\nexport function registerRenderer(\n format: string,\n renderer: DocumentRenderer | (() => Promise<DocumentRenderer>),\n): void {\n renderers.set(format, renderer)\n}\n\n/**\n * Remove a registered renderer.\n */\nexport function unregisterRenderer(format: string): void {\n renderers.delete(format)\n}\n\n// ─── Built-in Renderer Loaders ──────────────────────────────────────────────\n\n// Built-in renderers are registered lazily — only loaded when first used.\n\nregisterRenderer('html', () => import('./renderers/html').then((m) => m.htmlRenderer))\n\nregisterRenderer('email', () => import('./renderers/email').then((m) => m.emailRenderer))\n\nregisterRenderer('md', () => import('./renderers/markdown').then((m) => m.markdownRenderer))\n\nregisterRenderer('text', () => import('./renderers/text').then((m) => m.textRenderer))\n\nregisterRenderer('csv', () => import('./renderers/csv').then((m) => m.csvRenderer))\n\nregisterRenderer('pdf', () => import('./renderers/pdf').then((m) => m.pdfRenderer))\n\nregisterRenderer('docx', () => import('./renderers/docx').then((m) => m.docxRenderer))\n\nregisterRenderer('xlsx', () => import('./renderers/xlsx').then((m) => m.xlsxRenderer))\n\nregisterRenderer('pptx', () => import('./renderers/pptx').then((m) => m.pptxRenderer))\n\nregisterRenderer('slack', () => import('./renderers/slack').then((m) => m.slackRenderer))\n\nregisterRenderer('svg', () => import('./renderers/svg').then((m) => m.svgRenderer))\n\nregisterRenderer('teams', () => import('./renderers/teams').then((m) => m.teamsRenderer))\n\nregisterRenderer('discord', () => import('./renderers/discord').then((m) => m.discordRenderer))\n\nregisterRenderer('telegram', () => import('./renderers/telegram').then((m) => m.telegramRenderer))\n\nregisterRenderer('notion', () => import('./renderers/notion').then((m) => m.notionRenderer))\n\nregisterRenderer('confluence', () =>\n import('./renderers/confluence').then((m) => m.confluenceRenderer),\n)\n\nregisterRenderer('whatsapp', () => import('./renderers/whatsapp').then((m) => m.whatsappRenderer))\n\nregisterRenderer('google-chat', () =>\n import('./renderers/google-chat').then((m) => m.googleChatRenderer),\n)\n\n// ─── Render Function ────────────────────────────────────────────────────────\n\nasync function resolveRenderer(format: string): Promise<DocumentRenderer> {\n const entry = renderers.get(format)\n if (!entry) {\n throw new Error(\n `[@pyreon/document] No renderer registered for format '${format}'. Available: ${[...renderers.keys()].join(', ')}`,\n )\n }\n\n if (typeof entry === 'function') {\n const renderer = await entry()\n // Cache the resolved renderer so we don't re-import\n renderers.set(format, renderer)\n return renderer\n }\n\n return entry\n}\n\n/**\n * Render a document node tree to the specified format.\n *\n * @example\n * ```tsx\n * const doc = <Document title=\"Report\"><Page>...</Page></Document>\n *\n * const html = await render(doc, 'html') // → HTML string\n * const pdf = await render(doc, 'pdf') // → PDF Uint8Array\n * const docx = await render(doc, 'docx') // → DOCX Uint8Array\n * const email = await render(doc, 'email') // → email-safe HTML string\n * const md = await render(doc, 'md') // → Markdown string\n * ```\n */\nexport async function render(\n node: DocNode,\n format: OutputFormat | string,\n options?: RenderOptions,\n): Promise<RenderResult> {\n const renderer = await resolveRenderer(format)\n return renderer.render(node, options)\n}\n\n/** @internal For testing — reset renderer registry to defaults. */\nexport function _resetRenderers(): void {\n renderers.clear()\n // Re-register built-in lazy loaders\n registerRenderer('html', () => import('./renderers/html').then((m) => m.htmlRenderer))\n registerRenderer('email', () => import('./renderers/email').then((m) => m.emailRenderer))\n registerRenderer('md', () => import('./renderers/markdown').then((m) => m.markdownRenderer))\n registerRenderer('text', () => import('./renderers/text').then((m) => m.textRenderer))\n registerRenderer('csv', () => import('./renderers/csv').then((m) => m.csvRenderer))\n registerRenderer('pdf', () => import('./renderers/pdf').then((m) => m.pdfRenderer))\n registerRenderer('docx', () => import('./renderers/docx').then((m) => m.docxRenderer))\n registerRenderer('xlsx', () => import('./renderers/xlsx').then((m) => m.xlsxRenderer))\n registerRenderer('pptx', () => import('./renderers/pptx').then((m) => m.pptxRenderer))\n registerRenderer('slack', () => import('./renderers/slack').then((m) => m.slackRenderer))\n registerRenderer('svg', () => import('./renderers/svg').then((m) => m.svgRenderer))\n registerRenderer('teams', () => import('./renderers/teams').then((m) => m.teamsRenderer))\n registerRenderer('discord', () => import('./renderers/discord').then((m) => m.discordRenderer))\n registerRenderer('telegram', () => import('./renderers/telegram').then((m) => m.telegramRenderer))\n registerRenderer('notion', () => import('./renderers/notion').then((m) => m.notionRenderer))\n registerRenderer('confluence', () =>\n import('./renderers/confluence').then((m) => m.confluenceRenderer),\n )\n registerRenderer('whatsapp', () => import('./renderers/whatsapp').then((m) => m.whatsappRenderer))\n registerRenderer('google-chat', () =>\n import('./renderers/google-chat').then((m) => m.googleChatRenderer),\n )\n}\n","import { render } from './render'\nimport type { DocNode, RenderOptions } from './types'\n\nconst FORMAT_MAP: Record<string, string> = {\n html: 'html',\n htm: 'html',\n pdf: 'pdf',\n docx: 'docx',\n doc: 'docx',\n xlsx: 'xlsx',\n xls: 'xlsx',\n pptx: 'pptx',\n ppt: 'pptx',\n md: 'md',\n txt: 'text',\n csv: 'csv',\n svg: 'svg',\n}\n\nconst MIME_TYPES: Record<string, string> = {\n html: 'text/html',\n pdf: 'application/pdf',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n email: 'text/html',\n md: 'text/markdown',\n text: 'text/plain',\n csv: 'text/csv',\n svg: 'image/svg+xml',\n}\n\n/**\n * Download a document in the browser.\n *\n * @example\n * ```tsx\n * await download(doc, 'report.pdf')\n * await download(doc, 'report.docx')\n * ```\n */\nexport async function download(\n node: DocNode,\n filename: string,\n options?: RenderOptions,\n): Promise<void> {\n const ext = filename.split('.').pop()?.toLowerCase()\n if (!ext) {\n throw new Error('[@pyreon/document] Filename must have an extension (e.g., report.pdf).')\n }\n\n const format = FORMAT_MAP[ext]\n if (!format) {\n throw new Error(\n `[@pyreon/document] Unknown file extension '.${ext}'. Supported: ${Object.keys(FORMAT_MAP).join(', ')}`,\n )\n }\n\n const result = await render(node, format, options)\n\n const blob =\n result instanceof Uint8Array\n ? new Blob([result as BlobPart])\n : new Blob([result], {\n type: MIME_TYPES[format] ?? 'application/octet-stream',\n })\n\n if (typeof document === 'undefined') {\n throw new Error('[@pyreon/document] download() requires a browser environment.')\n }\n\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n","import type {\n ButtonProps,\n CodeProps,\n ColumnProps,\n DividerProps,\n DocChild,\n DocNode,\n DocumentProps,\n HeadingProps,\n ImageProps,\n LinkProps,\n ListItemProps,\n ListProps,\n NodeType,\n PageProps,\n QuoteProps,\n RowProps,\n SectionProps,\n SpacerProps,\n TableProps,\n TextProps,\n} from './types'\n\n// ─── Node Constructor ───────────────────────────────────────────────────────\n\nfunction createNode(type: NodeType, props: object, children: unknown): DocNode {\n return {\n type,\n props: props as Record<string, unknown>,\n children: normalizeChildren(children),\n }\n}\n\nfunction normalizeChildren(children: unknown): DocChild[] {\n if (children == null || children === false) return []\n if (typeof children === 'string') return [children]\n if (typeof children === 'number') return [String(children)]\n if (Array.isArray(children)) return children.flatMap(normalizeChildren)\n if (isDocNode(children)) return [children]\n if (typeof children === 'object') {\n throw new Error(\n '[@pyreon/document] Invalid child: plain objects are not valid document children. Use a document node (Text, Heading, etc.) instead.',\n )\n }\n return [String(children)]\n}\n\n/** Type guard — checks if a value is a DocNode. */\nexport function isDocNode(value: unknown): value is DocNode {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n 'props' in value &&\n 'children' in value\n )\n}\n\n// ─── Document Primitives ────────────────────────────────────────────────────\n\n/**\n * Root document container. Holds metadata and pages.\n *\n * @example\n * ```tsx\n * <Document title=\"Invoice #1234\" author=\"Acme Corp\">\n * <Page>...</Page>\n * </Document>\n * ```\n */\nexport function Document(props: DocumentProps): DocNode {\n const { children, ...rest } = props\n return createNode('document', rest, children)\n}\nDocument._documentType = 'document' as const\n\n/**\n * Page container. Maps to a PDF page, DOCX section, or email block.\n *\n * @example\n * ```tsx\n * <Page size=\"A4\" margin={40}>\n * <Heading>Title</Heading>\n * </Page>\n * ```\n */\nexport function Page(props: PageProps): DocNode {\n const { children, ...rest } = props\n return createNode('page', rest, children)\n}\nPage._documentType = 'page' as const\n\n/**\n * Layout section — groups content with optional direction, padding, background.\n *\n * @example\n * ```tsx\n * <Section background=\"#f5f5f5\" padding={20} direction=\"row\" gap={12}>\n * <Text>Left</Text>\n * <Text>Right</Text>\n * </Section>\n * ```\n */\nexport function Section(props: SectionProps): DocNode {\n const { children, ...rest } = props\n return createNode('section', rest, children)\n}\nSection._documentType = 'section' as const\n\n/**\n * Horizontal layout container.\n *\n * @example\n * ```tsx\n * <Row gap={20}>\n * <Column width=\"60%\"><Text>Main</Text></Column>\n * <Column width=\"40%\"><Text>Side</Text></Column>\n * </Row>\n * ```\n */\nexport function Row(props: RowProps): DocNode {\n const { children, ...rest } = props\n return createNode('row', rest, children)\n}\nRow._documentType = 'row' as const\n\n/**\n * Column within a Row.\n */\nexport function Column(props: ColumnProps): DocNode {\n const { children, ...rest } = props\n return createNode('column', rest, children)\n}\nColumn._documentType = 'column' as const\n\n/**\n * Heading text (h1–h6).\n *\n * @example\n * ```tsx\n * <Heading level={1}>Invoice #1234</Heading>\n * <Heading level={2} color=\"#666\">Details</Heading>\n * ```\n */\nexport function Heading(props: HeadingProps): DocNode {\n const { children, ...rest } = props\n return createNode('heading', { level: 1, ...rest }, children)\n}\nHeading._documentType = 'heading' as const\n\n/**\n * Text paragraph with optional formatting.\n *\n * @example\n * ```tsx\n * <Text bold size={14} color=\"#333\">Hello World</Text>\n * <Text italic align=\"right\">Subtotal: $100</Text>\n * ```\n */\nexport function Text(props: TextProps): DocNode {\n const { children, ...rest } = props\n return createNode('text', rest, children)\n}\nText._documentType = 'text' as const\n\n/**\n * Hyperlink.\n *\n * @example\n * ```tsx\n * <Link href=\"https://example.com\">Visit site</Link>\n * ```\n */\nexport function Link(props: LinkProps): DocNode {\n const { children, ...rest } = props\n return createNode('link', rest, children)\n}\nLink._documentType = 'link' as const\n\n/**\n * Image with optional sizing and caption.\n *\n * @example\n * ```tsx\n * <Image src=\"/logo.png\" width={120} alt=\"Company Logo\" />\n * <Image src={chartDataUrl} width={500} caption=\"Revenue Chart\" />\n * ```\n */\nexport function Image(props: ImageProps): DocNode {\n return createNode('image', props, [])\n}\nImage._documentType = 'image' as const\n\n/**\n * Data table with columns and rows.\n *\n * @example\n * ```tsx\n * <Table\n * columns={['Name', 'Price', 'Qty']}\n * rows={[['Widget', '$10', '5'], ['Gadget', '$20', '3']]}\n * striped\n * headerStyle={{ background: '#1a1a2e', color: '#fff' }}\n * />\n * ```\n */\nexport function Table(props: TableProps): DocNode {\n return createNode('table', props, [])\n}\nTable._documentType = 'table' as const\n\n/**\n * Ordered or unordered list.\n *\n * @example\n * ```tsx\n * <List ordered>\n * <ListItem>First item</ListItem>\n * <ListItem>Second item</ListItem>\n * </List>\n * ```\n */\nexport function List(props: ListProps): DocNode {\n const { children, ...rest } = props\n return createNode('list', rest, children)\n}\nList._documentType = 'list' as const\n\n/**\n * Single list item within a List.\n */\nexport function ListItem(props: ListItemProps): DocNode {\n const { children } = props\n return createNode('list-item', {}, children)\n}\nListItem._documentType = 'list-item' as const\n\n/**\n * Code block with optional language hint.\n *\n * @example\n * ```tsx\n * <Code language=\"typescript\">const x = 42</Code>\n * ```\n */\nexport function Code(props: CodeProps): DocNode {\n const { children, ...rest } = props\n return createNode('code', rest, children)\n}\nCode._documentType = 'code' as const\n\n/**\n * Horizontal divider line.\n *\n * @example\n * ```tsx\n * <Divider color=\"#ddd\" thickness={2} />\n * ```\n */\nexport function Divider(props: DividerProps = {}): DocNode {\n return createNode('divider', props, [])\n}\nDivider._documentType = 'divider' as const\n\n/**\n * Page break — forces content after this point to the next page (PDF/DOCX)\n * or inserts a visual separator (HTML/email).\n *\n * @example\n * ```tsx\n * <PageBreak />\n * ```\n */\nexport function PageBreak(): DocNode {\n return createNode('page-break', {}, [])\n}\nPageBreak._documentType = 'page-break' as const\n\n/**\n * Vertical spacer.\n *\n * @example\n * ```tsx\n * <Spacer height={20} />\n * ```\n */\nexport function Spacer(props: SpacerProps): DocNode {\n return createNode('spacer', props, [])\n}\nSpacer._documentType = 'spacer' as const\n\n/**\n * CTA button — renders as a bulletproof button in email, styled link in PDF/DOCX.\n *\n * @example\n * ```tsx\n * <Button href=\"https://acme.com/pay\" background=\"#4f46e5\" color=\"#fff\">\n * Pay Now\n * </Button>\n * ```\n */\nexport function Button(props: ButtonProps): DocNode {\n const { children, ...rest } = props\n return createNode('button', rest, children)\n}\nButton._documentType = 'button' as const\n\n/**\n * Block quote.\n *\n * @example\n * ```tsx\n * <Quote borderColor=\"#4f46e5\">This is a quote.</Quote>\n * ```\n */\nexport function Quote(props: QuoteProps): DocNode {\n const { children, ...rest } = props\n return createNode('quote', rest, children)\n}\nQuote._documentType = 'quote' as const\n","import { download } from './download'\nimport {\n Button,\n Code,\n Divider,\n Document,\n Heading,\n Image,\n Link,\n List,\n ListItem,\n Page,\n PageBreak,\n Quote,\n Section,\n Spacer,\n Table,\n Text,\n} from './nodes'\nimport { render } from './render'\nimport type {\n ButtonProps,\n CodeProps,\n DividerProps,\n DocNode,\n DocumentBuilder,\n DocumentProps,\n HeadingProps,\n ImageProps,\n LinkProps,\n ListProps,\n QuoteProps,\n RenderOptions,\n TableProps,\n TextProps,\n} from './types'\n\n/**\n * Create a document using the builder pattern — no JSX needed.\n *\n * @example\n * ```ts\n * const doc = createDocument({ title: 'Report' })\n * .heading('Sales Report')\n * .text('Q4 performance summary.')\n * .table({ columns: ['Region', 'Revenue'], rows: [['US', '$1M']] })\n *\n * await doc.toPdf()\n * await doc.download('report.pdf')\n * ```\n */\nexport function createDocument(props: DocumentProps = {}): DocumentBuilder {\n const sections: DocNode[] = []\n\n function getNode(): DocNode {\n return Document({ ...props, children: [Page({ children: sections })] })\n }\n\n const builder: DocumentBuilder = {\n heading(text: string, p?: Omit<HeadingProps, 'children'>) {\n sections.push(Heading({ ...p, children: text }))\n return builder\n },\n\n text(text: string, p?: Omit<TextProps, 'children'>) {\n sections.push(Text({ ...p, children: text }))\n return builder\n },\n\n paragraph(text: string, p?: Omit<TextProps, 'children'>) {\n return builder.text(text, p)\n },\n\n image(src: string, p?: Omit<ImageProps, 'src'>) {\n sections.push(Image({ src, ...p }))\n return builder\n },\n\n table(p: TableProps) {\n sections.push(Table(p))\n return builder\n },\n\n list(items: string[], p?: Omit<ListProps, 'children'>) {\n sections.push(\n List({\n ...p,\n children: items.map((item) => ListItem({ children: item })),\n }),\n )\n return builder\n },\n\n code(text: string, p?: Omit<CodeProps, 'children'>) {\n sections.push(Code({ ...p, children: text }))\n return builder\n },\n\n divider(p?: DividerProps) {\n sections.push(Divider(p))\n return builder\n },\n\n spacer(height: number) {\n sections.push(Spacer({ height }))\n return builder\n },\n\n quote(text: string, p?: Omit<QuoteProps, 'children'>) {\n sections.push(Quote({ ...p, children: text }))\n return builder\n },\n\n button(text: string, p: Omit<ButtonProps, 'children'>) {\n sections.push(Button({ ...p, children: text }))\n return builder\n },\n\n link(text: string, p: Omit<LinkProps, 'children'>) {\n sections.push(Link({ ...p, children: text }))\n return builder\n },\n\n pageBreak() {\n sections.push(PageBreak())\n return builder\n },\n\n add(node) {\n if (Array.isArray(node)) {\n sections.push(...node)\n } else {\n sections.push(node)\n }\n return builder\n },\n\n section(children) {\n sections.push(Section({ children }))\n return builder\n },\n\n chart(instance: unknown, p?: { width?: number; height?: number; caption?: string }) {\n // Try to get data URL from chart instance\n const inst = instance as { getDataURL?: (opts: unknown) => string }\n if (inst?.getDataURL) {\n const dataUrl = inst.getDataURL({ type: 'png', pixelRatio: 2 })\n sections.push(\n Image({\n src: dataUrl,\n ...(p?.width != null ? { width: p.width } : {}),\n ...(p?.height != null ? { height: p.height } : {}),\n ...(p?.caption != null ? { caption: p.caption } : {}),\n }),\n )\n } else {\n sections.push(\n Text({\n children: '[Chart]',\n italic: true,\n color: '#999',\n } as TextProps & { children: string }),\n )\n }\n return builder\n },\n\n flow(instance: unknown, p?: { width?: number; height?: number; caption?: string }) {\n // Try to get SVG from flow instance\n const inst = instance as { toSVG?: () => string }\n if (inst?.toSVG) {\n const svg = inst.toSVG()\n sections.push(\n Image({\n src: `data:image/svg+xml,${encodeURIComponent(svg)}`,\n ...(p?.width != null ? { width: p.width } : {}),\n ...(p?.height != null ? { height: p.height } : {}),\n ...(p?.caption != null ? { caption: p.caption } : {}),\n }),\n )\n } else {\n sections.push(\n Text({\n children: '[Flow Diagram]',\n italic: true,\n color: '#999',\n } as TextProps & { children: string }),\n )\n }\n return builder\n },\n\n build() {\n return getNode()\n },\n\n async toHtml(options?: RenderOptions) {\n return render(getNode(), 'html', options) as Promise<string>\n },\n\n async toPdf(options?: RenderOptions) {\n return render(getNode(), 'pdf', options) as Promise<Uint8Array>\n },\n\n async toDocx(options?: RenderOptions) {\n return render(getNode(), 'docx', options) as Promise<Uint8Array>\n },\n\n async toEmail(options?: RenderOptions) {\n return render(getNode(), 'email', options) as Promise<string>\n },\n\n async toPptx(options?: RenderOptions) {\n return render(getNode(), 'pptx', options) as Promise<Uint8Array>\n },\n\n async toXlsx(options?: RenderOptions) {\n return render(getNode(), 'xlsx', options) as Promise<Uint8Array>\n },\n\n async toMarkdown(options?: RenderOptions) {\n return render(getNode(), 'md', options) as Promise<string>\n },\n\n async toText(options?: RenderOptions) {\n return render(getNode(), 'text', options) as Promise<string>\n },\n\n async toCsv(options?: RenderOptions) {\n return render(getNode(), 'csv', options) as Promise<string>\n },\n\n async toSlack(options?: RenderOptions) {\n return render(getNode(), 'slack', options) as Promise<string>\n },\n\n async toSvg(options?: RenderOptions) {\n return render(getNode(), 'svg', options) as Promise<string>\n },\n\n async toTeams(options?: RenderOptions) {\n return render(getNode(), 'teams', options) as Promise<string>\n },\n\n async toDiscord(options?: RenderOptions) {\n return render(getNode(), 'discord', options) as Promise<string>\n },\n\n async toTelegram(options?: RenderOptions) {\n return render(getNode(), 'telegram', options) as Promise<string>\n },\n\n async toNotion(options?: RenderOptions) {\n return render(getNode(), 'notion', options) as Promise<string>\n },\n\n async toConfluence(options?: RenderOptions) {\n return render(getNode(), 'confluence', options) as Promise<string>\n },\n\n async toWhatsApp(options?: RenderOptions) {\n return render(getNode(), 'whatsapp', options) as Promise<string>\n },\n\n async toGoogleChat(options?: RenderOptions) {\n return render(getNode(), 'google-chat', options) as Promise<string>\n },\n\n async download(filename: string, options?: RenderOptions) {\n return download(getNode(), filename, options)\n },\n }\n\n return builder\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-CdtlFGC0.js","names":[],"sources":["../src/renderers/markdown.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from \"../sanitize\"\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction renderChild(child: DocChild): string {\n if (typeof child === \"string\") return child\n return renderNode(child)\n}\n\nfunction renderChildren(children: DocChild[]): string {\n return children.map(renderChild).join(\"\")\n}\n\nfunction renderInline(children: DocChild[]): string {\n return children.map(renderChild).join(\"\")\n}\n\nfunction renderNode(node: DocNode): string {\n const p = node.props\n\n switch (node.type) {\n case \"document\":\n return renderChildren(node.children)\n\n case \"page\":\n return renderChildren(node.children)\n\n case \"section\":\n return `${renderChildren(node.children)}\\n`\n\n case \"row\":\n case \"column\":\n return renderChildren(node.children)\n\n case \"heading\": {\n const level = (p.level as number) ?? 1\n const prefix = \"#\".repeat(Math.min(Math.max(level, 1), 6))\n return `${prefix} ${renderInline(node.children)}\\n\\n`\n }\n\n case \"text\": {\n let text = renderInline(node.children)\n if (p.bold) text = `**${text}**`\n if (p.italic) text = `*${text}*`\n if (p.strikethrough) text = `~~${text}~~`\n return `${text}\\n\\n`\n }\n\n case \"link\":\n return `[${renderInline(node.children)}](${sanitizeHref(p.href as string)})`\n\n case \"image\": {\n const alt = (p.alt as string) ?? \"\"\n let md = `![${alt}](${sanitizeImageSrc(p.src as string)})`\n if (p.caption) md += `\\n*${p.caption}*`\n return `${md}\\n\\n`\n }\n\n case \"table\": {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n if (columns.length === 0) return \"\"\n\n // Header\n const header = `| ${columns.map((c) => c.header).join(\" | \")} |`\n\n // Separator with alignment\n const separator = `| ${columns\n .map((c) => {\n const align = c.align ?? \"left\"\n if (align === \"center\") return \":---:\"\n if (align === \"right\") return \"---:\"\n return \"---\"\n })\n .join(\" | \")} |`\n\n // Rows\n const body = rows\n .map((row) => `| ${row.map((cell) => String(cell ?? \"\")).join(\" | \")} |`)\n .join(\"\\n\")\n\n let md = `${header}\\n${separator}\\n${body}\\n\\n`\n if (p.caption) md = `*${p.caption}*\\n\\n${md}`\n return md\n }\n\n case \"list\": {\n const ordered = p.ordered as boolean | undefined\n return `${node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((item, i) => {\n const prefix = ordered ? `${i + 1}.` : \"-\"\n return `${prefix} ${renderInline(item.children)}`\n })\n .join(\"\\n\")}\\n\\n`\n }\n\n case \"list-item\":\n return renderInline(node.children)\n\n case \"code\": {\n const lang = (p.language as string) ?? \"\"\n const content = renderInline(node.children)\n return `\\`\\`\\`${lang}\\n${content}\\n\\`\\`\\`\\n\\n`\n }\n\n case \"divider\":\n return \"---\\n\\n\"\n\n case \"page-break\":\n return \"---\\n\\n\"\n\n case \"spacer\":\n return \"\\n\"\n\n case \"button\":\n return `[${renderInline(node.children)}](${sanitizeHref(p.href as string)})\\n\\n`\n\n case \"quote\":\n return `> ${renderInline(node.children)}\\n\\n`\n\n default:\n return renderChildren(node.children)\n }\n}\n\nexport const markdownRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n return `${renderNode(node).trim()}\\n`\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"markdown-CdtlFGC0.js","names":[],"sources":["../src/renderers/markdown.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction renderChild(child: DocChild): string {\n if (typeof child === 'string') return child\n return renderNode(child)\n}\n\nfunction renderChildren(children: DocChild[]): string {\n return children.map(renderChild).join('')\n}\n\nfunction renderInline(children: DocChild[]): string {\n return children.map(renderChild).join('')\n}\n\nfunction renderNode(node: DocNode): string {\n const p = node.props\n\n switch (node.type) {\n case 'document':\n return renderChildren(node.children)\n\n case 'page':\n return renderChildren(node.children)\n\n case 'section':\n return `${renderChildren(node.children)}\\n`\n\n case 'row':\n case 'column':\n return renderChildren(node.children)\n\n case 'heading': {\n const level = (p.level as number) ?? 1\n const prefix = '#'.repeat(Math.min(Math.max(level, 1), 6))\n return `${prefix} ${renderInline(node.children)}\\n\\n`\n }\n\n case 'text': {\n let text = renderInline(node.children)\n if (p.bold) text = `**${text}**`\n if (p.italic) text = `*${text}*`\n if (p.strikethrough) text = `~~${text}~~`\n return `${text}\\n\\n`\n }\n\n case 'link':\n return `[${renderInline(node.children)}](${sanitizeHref(p.href as string)})`\n\n case 'image': {\n const alt = (p.alt as string) ?? ''\n let md = `![${alt}](${sanitizeImageSrc(p.src as string)})`\n if (p.caption) md += `\\n*${p.caption}*`\n return `${md}\\n\\n`\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n if (columns.length === 0) return ''\n\n // Header\n const header = `| ${columns.map((c) => c.header).join(' | ')} |`\n\n // Separator with alignment\n const separator = `| ${columns\n .map((c) => {\n const align = c.align ?? 'left'\n if (align === 'center') return ':---:'\n if (align === 'right') return '---:'\n return '---'\n })\n .join(' | ')} |`\n\n // Rows\n const body = rows\n .map((row) => `| ${row.map((cell) => String(cell ?? '')).join(' | ')} |`)\n .join('\\n')\n\n let md = `${header}\\n${separator}\\n${body}\\n\\n`\n if (p.caption) md = `*${p.caption}*\\n\\n${md}`\n return md\n }\n\n case 'list': {\n const ordered = p.ordered as boolean | undefined\n return `${node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((item, i) => {\n const prefix = ordered ? `${i + 1}.` : '-'\n return `${prefix} ${renderInline(item.children)}`\n })\n .join('\\n')}\\n\\n`\n }\n\n case 'list-item':\n return renderInline(node.children)\n\n case 'code': {\n const lang = (p.language as string) ?? ''\n const content = renderInline(node.children)\n return `\\`\\`\\`${lang}\\n${content}\\n\\`\\`\\`\\n\\n`\n }\n\n case 'divider':\n return '---\\n\\n'\n\n case 'page-break':\n return '---\\n\\n'\n\n case 'spacer':\n return '\\n'\n\n case 'button':\n return `[${renderInline(node.children)}](${sanitizeHref(p.href as string)})\\n\\n`\n\n case 'quote':\n return `> ${renderInline(node.children)}\\n\\n`\n\n default:\n return renderChildren(node.children)\n }\n}\n\nexport const markdownRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n return `${renderNode(node).trim()}\\n`\n },\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"notion-iG2C5bEY.js","names":[],"sources":["../src/renderers/notion.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from \"../sanitize\"\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\n/**\n * Notion renderer — outputs Notion Block JSON for the Notion API.\n * Blocks can be appended to a page via `notion.blocks.children.append()`.\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === \"string\" ? c : getTextContent((c as DocNode).children)))\n .join(\"\")\n}\n\ninterface RichText {\n type: \"text\"\n text: { content: string; link?: { url: string } }\n annotations?: {\n bold?: boolean\n italic?: boolean\n strikethrough?: boolean\n underline?: boolean\n code?: boolean\n }\n}\n\nfunction textToRichText(text: string, annotations?: RichText[\"annotations\"]): RichText[] {\n return [\n {\n type: \"text\",\n text: { content: text },\n ...(annotations ? { annotations } : {}),\n },\n ]\n}\n\ninterface NotionBlock {\n object: \"block\"\n type: string\n [key: string]: unknown\n}\n\nfunction nodeToBlocks(node: DocNode): NotionBlock[] {\n const p = node.props\n const blocks: NotionBlock[] = []\n\n switch (node.type) {\n case \"document\":\n case \"page\":\n case \"section\":\n case \"row\":\n case \"column\":\n for (const child of node.children) {\n if (typeof child !== \"string\") {\n blocks.push(...nodeToBlocks(child))\n }\n }\n break\n\n case \"heading\": {\n const level = (p.level as number) ?? 1\n const text = getTextContent(node.children)\n const type = level <= 1 ? \"heading_1\" : level === 2 ? \"heading_2\" : \"heading_3\"\n blocks.push({\n object: \"block\",\n type,\n [type]: { rich_text: textToRichText(text) },\n })\n break\n }\n\n case \"text\": {\n const text = getTextContent(node.children)\n const annotations: RichText[\"annotations\"] = {}\n if (p.bold) annotations.bold = true\n if (p.italic) annotations.italic = true\n if (p.strikethrough) annotations.strikethrough = true\n if (p.underline) annotations.underline = true\n blocks.push({\n object: \"block\",\n type: \"paragraph\",\n paragraph: {\n rich_text: textToRichText(\n text,\n Object.keys(annotations).length > 0 ? annotations : undefined,\n ),\n },\n })\n break\n }\n\n case \"link\": {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n object: \"block\",\n type: \"paragraph\",\n paragraph: {\n rich_text: [{ type: \"text\", text: { content: text, link: { url: href } } }],\n },\n })\n break\n }\n\n case \"image\": {\n const src = sanitizeImageSrc(p.src as string)\n if (src.startsWith(\"http\")) {\n blocks.push({\n object: \"block\",\n type: \"image\",\n image: {\n type: \"external\",\n external: { url: src },\n ...(p.caption ? { caption: textToRichText(p.caption as string) } : {}),\n },\n })\n }\n break\n }\n\n case \"table\": {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n const tableRows: NotionBlock[] = []\n\n // Header row\n tableRows.push({\n object: \"block\",\n type: \"table_row\",\n table_row: {\n cells: columns.map((col) => textToRichText(col.header, { bold: true })),\n },\n })\n\n // Data rows\n for (const row of rows) {\n tableRows.push({\n object: \"block\",\n type: \"table_row\",\n table_row: {\n cells: columns.map((_, i) => textToRichText(String(row[i] ?? \"\"))),\n },\n })\n }\n\n blocks.push({\n object: \"block\",\n type: \"table\",\n table: {\n table_width: columns.length,\n has_column_header: true,\n children: tableRows,\n },\n })\n break\n }\n\n case \"list\": {\n const ordered = p.ordered as boolean | undefined\n const items = node.children.filter((c): c is DocNode => typeof c !== \"string\")\n for (const item of items) {\n const text = getTextContent(item.children)\n const type = ordered ? \"numbered_list_item\" : \"bulleted_list_item\"\n blocks.push({\n object: \"block\",\n type,\n [type]: { rich_text: textToRichText(text) },\n })\n }\n break\n }\n\n case \"code\": {\n const text = getTextContent(node.children)\n const lang = (p.language as string) ?? \"plain text\"\n blocks.push({\n object: \"block\",\n type: \"code\",\n code: {\n rich_text: textToRichText(text),\n language: lang,\n },\n })\n break\n }\n\n case \"divider\":\n case \"page-break\":\n blocks.push({ object: \"block\", type: \"divider\", divider: {} })\n break\n\n case \"spacer\":\n blocks.push({\n object: \"block\",\n type: \"paragraph\",\n paragraph: { rich_text: [] },\n })\n break\n\n case \"button\": {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n object: \"block\",\n type: \"paragraph\",\n paragraph: {\n rich_text: [\n {\n type: \"text\",\n text: { content: text, link: { url: href } },\n annotations: { bold: true },\n },\n ],\n },\n })\n break\n }\n\n case \"quote\": {\n const text = getTextContent(node.children)\n blocks.push({\n object: \"block\",\n type: \"quote\",\n quote: { rich_text: textToRichText(text) },\n })\n break\n }\n }\n\n return blocks\n}\n\nexport const notionRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const blocks = nodeToBlocks(node)\n return JSON.stringify({ children: blocks }, null, 2)\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"notion-iG2C5bEY.js","names":[],"sources":["../src/renderers/notion.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'\n\n/**\n * Notion renderer — outputs Notion Block JSON for the Notion API.\n * Blocks can be appended to a page via `notion.blocks.children.append()`.\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === 'string' ? c : getTextContent((c as DocNode).children)))\n .join('')\n}\n\ninterface RichText {\n type: 'text'\n text: { content: string; link?: { url: string } }\n annotations?: {\n bold?: boolean\n italic?: boolean\n strikethrough?: boolean\n underline?: boolean\n code?: boolean\n }\n}\n\nfunction textToRichText(text: string, annotations?: RichText['annotations']): RichText[] {\n return [\n {\n type: 'text',\n text: { content: text },\n ...(annotations ? { annotations } : {}),\n },\n ]\n}\n\ninterface NotionBlock {\n object: 'block'\n type: string\n [key: string]: unknown\n}\n\nfunction nodeToBlocks(node: DocNode): NotionBlock[] {\n const p = node.props\n const blocks: NotionBlock[] = []\n\n switch (node.type) {\n case 'document':\n case 'page':\n case 'section':\n case 'row':\n case 'column':\n for (const child of node.children) {\n if (typeof child !== 'string') {\n blocks.push(...nodeToBlocks(child))\n }\n }\n break\n\n case 'heading': {\n const level = (p.level as number) ?? 1\n const text = getTextContent(node.children)\n const type = level <= 1 ? 'heading_1' : level === 2 ? 'heading_2' : 'heading_3'\n blocks.push({\n object: 'block',\n type,\n [type]: { rich_text: textToRichText(text) },\n })\n break\n }\n\n case 'text': {\n const text = getTextContent(node.children)\n const annotations: RichText['annotations'] = {}\n if (p.bold) annotations.bold = true\n if (p.italic) annotations.italic = true\n if (p.strikethrough) annotations.strikethrough = true\n if (p.underline) annotations.underline = true\n blocks.push({\n object: 'block',\n type: 'paragraph',\n paragraph: {\n rich_text: textToRichText(\n text,\n Object.keys(annotations).length > 0 ? annotations : undefined,\n ),\n },\n })\n break\n }\n\n case 'link': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n object: 'block',\n type: 'paragraph',\n paragraph: {\n rich_text: [{ type: 'text', text: { content: text, link: { url: href } } }],\n },\n })\n break\n }\n\n case 'image': {\n const src = sanitizeImageSrc(p.src as string)\n if (src.startsWith('http')) {\n blocks.push({\n object: 'block',\n type: 'image',\n image: {\n type: 'external',\n external: { url: src },\n ...(p.caption ? { caption: textToRichText(p.caption as string) } : {}),\n },\n })\n }\n break\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n const tableRows: NotionBlock[] = []\n\n // Header row\n tableRows.push({\n object: 'block',\n type: 'table_row',\n table_row: {\n cells: columns.map((col) => textToRichText(col.header, { bold: true })),\n },\n })\n\n // Data rows\n for (const row of rows) {\n tableRows.push({\n object: 'block',\n type: 'table_row',\n table_row: {\n cells: columns.map((_, i) => textToRichText(String(row[i] ?? ''))),\n },\n })\n }\n\n blocks.push({\n object: 'block',\n type: 'table',\n table: {\n table_width: columns.length,\n has_column_header: true,\n children: tableRows,\n },\n })\n break\n }\n\n case 'list': {\n const ordered = p.ordered as boolean | undefined\n const items = node.children.filter((c): c is DocNode => typeof c !== 'string')\n for (const item of items) {\n const text = getTextContent(item.children)\n const type = ordered ? 'numbered_list_item' : 'bulleted_list_item'\n blocks.push({\n object: 'block',\n type,\n [type]: { rich_text: textToRichText(text) },\n })\n }\n break\n }\n\n case 'code': {\n const text = getTextContent(node.children)\n const lang = (p.language as string) ?? 'plain text'\n blocks.push({\n object: 'block',\n type: 'code',\n code: {\n rich_text: textToRichText(text),\n language: lang,\n },\n })\n break\n }\n\n case 'divider':\n case 'page-break':\n blocks.push({ object: 'block', type: 'divider', divider: {} })\n break\n\n case 'spacer':\n blocks.push({\n object: 'block',\n type: 'paragraph',\n paragraph: { rich_text: [] },\n })\n break\n\n case 'button': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n object: 'block',\n type: 'paragraph',\n paragraph: {\n rich_text: [\n {\n type: 'text',\n text: { content: text, link: { url: href } },\n annotations: { bold: true },\n },\n ],\n },\n })\n break\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n blocks.push({\n object: 'block',\n type: 'quote',\n quote: { rich_text: textToRichText(text) },\n })\n break\n }\n }\n\n return blocks\n}\n\nexport const notionRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const blocks = nodeToBlocks(node)\n return JSON.stringify({ children: blocks }, null, 2)\n },\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pdf-IuBgTb3T.js","names":[],"sources":["../src/renderers/pdf.ts"],"sourcesContent":["import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\n/**\n * PDF renderer — lazy-loads pdfmake on first use.\n * pdfmake handles pagination, tables, text wrapping, and font embedding.\n *\n * @example\n * ```ts\n * import { render, Document, Page, Heading } from '@pyreon/document'\n *\n * const doc = Document({\n * title: 'Report',\n * children: Page({ children: Heading({ children: 'Hello' }) }),\n * })\n * const pdf = await render(doc, 'pdf') // → Uint8Array\n * ```\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === \"string\" ? c : getTextContent((c as DocNode).children)))\n .join(\"\")\n}\n\ntype PdfContent = Record<string, unknown> | string | PdfContent[]\n\n/** pdfmake expects page sizes as `{ width, height }` objects. */\nconst PAGE_SIZES: Record<string, { width: number; height: number }> = {\n A3: { width: 841.89, height: 1190.55 },\n A4: { width: 595.28, height: 841.89 },\n A5: { width: 419.53, height: 595.28 },\n letter: { width: 612, height: 792 },\n legal: { width: 612, height: 1008 },\n tabloid: { width: 792, height: 1224 },\n}\n\n/**\n * Resolve an image `src` for pdfmake.\n *\n * - `data:` URIs are passed through directly (pdfmake supports base64).\n * - `http(s)://` URLs cannot be resolved at render time in the browser.\n * A placeholder text node is returned instead.\n * - Relative / absolute paths (e.g. `/logo.png`) cannot be resolved without\n * a server-side fetch, so they are skipped with a placeholder.\n */\nfunction resolveImageSrc(\n src: string,\n): { image: string } | { text: string; italics: true; color: string } {\n if (src.startsWith(\"data:\")) {\n return { image: src }\n }\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) {\n return { text: `[Image: ${src}]`, italics: true, color: \"#999999\" }\n }\n // Local path — cannot resolve in browser\n return { text: `[Image: ${src}]`, italics: true, color: \"#999999\" }\n}\n\nfunction nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {\n const p = node.props\n\n switch (node.type) {\n case \"document\":\n case \"page\":\n return node.children\n .map((c) => (typeof c === \"string\" ? c : nodeToContent(c)))\n .filter((c): c is PdfContent => c != null)\n\n case \"section\": {\n const content = node.children\n .map((c) => (typeof c === \"string\" ? c : nodeToContent(c)))\n .filter((c): c is PdfContent => c != null)\n .flat()\n\n if (p.direction === \"row\") {\n return {\n columns: node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((child) => ({\n stack: [nodeToContent(child)].flat().filter(Boolean),\n width: child.props.width === \"*\" || !child.props.width ? \"*\" : child.props.width,\n })),\n columnGap: (p.gap as number) ?? 0,\n }\n }\n\n return content\n }\n\n case \"row\": {\n return {\n columns: node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((child) => ({\n stack: [nodeToContent(child)].flat().filter(Boolean),\n width: child.props.width ?? \"*\",\n })),\n columnGap: (p.gap as number) ?? 0,\n }\n }\n\n case \"column\":\n return node.children\n .map((c) => (typeof c === \"string\" ? c : nodeToContent(c)))\n .filter((c): c is PdfContent => c != null)\n .flat()\n\n case \"heading\": {\n const level = (p.level as number) ?? 1\n const sizes: Record<number, number> = {\n 1: 24,\n 2: 20,\n 3: 18,\n 4: 16,\n 5: 14,\n 6: 12,\n }\n return {\n text: getTextContent(node.children),\n fontSize: sizes[level] ?? 18,\n bold: true,\n color: (p.color as string) ?? \"#000000\",\n alignment: (p.align as string) ?? \"left\",\n margin: [0, level === 1 ? 0 : 8, 0, 8],\n }\n }\n\n case \"text\":\n return {\n text: getTextContent(node.children),\n fontSize: (p.size as number) ?? 12,\n color: (p.color as string) ?? \"#333333\",\n bold: p.bold ?? false,\n italics: p.italic ?? false,\n decoration: p.underline ? \"underline\" : p.strikethrough ? \"lineThrough\" : undefined,\n alignment: (p.align as string) ?? \"left\",\n lineHeight: (p.lineHeight as number) ?? 1.4,\n margin: [0, 0, 0, 8],\n }\n\n case \"link\":\n return {\n text: getTextContent(node.children),\n link: p.href as string,\n color: (p.color as string) ?? \"#4f46e5\",\n decoration: \"underline\",\n }\n\n case \"image\": {\n const src = p.src as string\n const resolved = resolveImageSrc(src)\n\n if (\"image\" in resolved) {\n const result: Record<string, unknown> = {\n image: resolved.image,\n fit: [p.width ?? 500, p.height ?? 400],\n margin: [0, 0, 0, 8],\n }\n if (p.align === \"center\") result.alignment = \"center\"\n if (p.align === \"right\") result.alignment = \"right\"\n return result\n }\n\n // Placeholder for non-resolvable images\n return { ...resolved, margin: [0, 0, 0, 8] }\n }\n\n case \"table\": {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n const hs = p.headerStyle as { background?: string; color?: string } | undefined\n\n const headerRow = columns.map((col) => ({\n text: col.header,\n bold: true,\n fillColor: hs?.background ?? \"#f5f5f5\",\n color: hs?.color ?? \"#000000\",\n alignment: col.align ?? \"left\",\n }))\n\n const dataRows = rows.map((row, rowIdx) =>\n columns.map((col, colIdx) => ({\n text: String(row[colIdx] ?? \"\"),\n alignment: col.align ?? \"left\",\n fillColor: p.striped && rowIdx % 2 === 1 ? \"#f9f9f9\" : undefined,\n })),\n )\n\n const widths = columns.map((col) => {\n if (!col.width) return \"*\"\n if (typeof col.width === \"string\" && col.width.endsWith(\"%\")) {\n return col.width\n }\n return col.width\n })\n\n return {\n table: {\n headerRows: 1,\n widths,\n body: [headerRow, ...dataRows],\n },\n layout: p.bordered ? undefined : \"lightHorizontalLines\",\n unbreakable: p.keepTogether ?? false,\n margin: [0, 0, 0, 12],\n }\n }\n\n case \"list\": {\n const items = node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((item) => getTextContent(item.children))\n\n return p.ordered ? { ol: items, margin: [0, 0, 0, 8] } : { ul: items, margin: [0, 0, 0, 8] }\n }\n\n case \"list-item\":\n return getTextContent(node.children)\n\n case \"code\":\n return {\n text: getTextContent(node.children),\n font: \"Courier\",\n fontSize: 10,\n background: \"#f5f5f5\",\n margin: [0, 0, 0, 8],\n }\n\n case \"page-break\":\n return { text: \"\", pageBreak: \"after\" }\n\n case \"divider\":\n return {\n canvas: [\n {\n type: \"line\",\n x1: 0,\n y1: 0,\n x2: 515,\n y2: 0,\n lineWidth: (p.thickness as number) ?? 1,\n lineColor: (p.color as string) ?? \"#dddddd\",\n },\n ],\n margin: [0, 8, 0, 8],\n }\n\n case \"spacer\":\n return { text: \"\", margin: [0, (p.height as number) ?? 12, 0, 0] }\n\n case \"button\":\n return {\n text: getTextContent(node.children),\n link: p.href as string,\n bold: true,\n color: (p.color as string) ?? \"#ffffff\",\n background: (p.background as string) ?? \"#4f46e5\",\n margin: [0, 8, 0, 8],\n }\n\n case \"quote\":\n return {\n table: {\n widths: [4, \"*\"],\n body: [\n [\n { text: \"\", fillColor: (p.borderColor as string) ?? \"#dddddd\" },\n {\n text: getTextContent(node.children),\n italics: true,\n color: \"#555555\",\n margin: [8, 4, 0, 4],\n },\n ],\n ],\n },\n layout: \"noBorders\",\n margin: [0, 4, 0, 8],\n }\n\n default:\n return null\n }\n}\n\nfunction resolveMargin(\n margin: number | [number, number] | [number, number, number, number] | undefined,\n): [number, number, number, number] {\n if (margin == null) return [40, 40, 40, 40]\n if (typeof margin === \"number\") return [margin, margin, margin, margin]\n if (margin.length === 2) return [margin[1], margin[0], margin[1], margin[0]]\n return margin\n}\n\n/**\n * Render header/footer DocNodes into pdfmake content for page headers/footers.\n *\n * pdfmake header/footer functions receive `(currentPage, pageCount, pageSize)`\n * and must return a content object. We flatten the DocNode into static content.\n */\nfunction renderHeaderFooter(node: DocNode | undefined): PdfContent | undefined {\n if (!node) return undefined\n const content = nodeToContent(node)\n if (content == null) return undefined\n if (Array.isArray(content)) return { stack: content, margin: [40, 10, 40, 0] }\n if (typeof content === \"object\") return { ...content, margin: [40, 10, 40, 0] }\n return { text: content, margin: [40, 10, 40, 0] }\n}\n\nexport const pdfRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {\n // Lazy-load pdfmake — handle ESM/CJS interop\n let pdfMakeModule: any\n let pdfFontsModule: any\n try {\n pdfMakeModule = await import(\"pdfmake/build/pdfmake\")\n pdfFontsModule = await import(\"pdfmake/build/vfs_fonts\")\n } catch {\n throw new Error(\n '[@pyreon/document] PDF renderer requires \"pdfmake\" package. Install it: bun add pdfmake',\n )\n }\n\n // Resolve the actual exports (handle .default for ESM wrappers).\n // pdfmake's default export is a singleton instance of browser_extensions_pdfmake.\n // ESM interop may wrap it in an extra .default layer.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- pdfmake types are incomplete\n let pdfMake: any = pdfMakeModule.default ?? pdfMakeModule\n if (pdfMake.default && typeof pdfMake.default.createPdf === \"function\") {\n pdfMake = pdfMake.default\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- pdfmake types are incomplete\n const pdfFonts: any = pdfFontsModule.default ?? pdfFontsModule\n\n // Assign virtual filesystem for fonts\n if (pdfMake.vfs == null) {\n pdfMake.vfs = pdfFonts.pdfMake?.vfs ?? pdfFonts.vfs\n }\n\n // Find page config\n const pageNode = node.children.find(\n (c): c is DocNode => typeof c !== \"string\" && c.type === \"page\",\n )\n const pageSize = (pageNode?.props.size as string) ?? \"A4\"\n const pageOrientation = (pageNode?.props.orientation as string) ?? \"portrait\"\n const pageMargin = resolveMargin(\n pageNode?.props.margin as\n | number\n | [number, number]\n | [number, number, number, number]\n | undefined,\n )\n\n const content = [nodeToContent(node)].flat().filter(Boolean) as PdfContent[]\n\n // Build header/footer from PageProps if present\n const headerFn = renderHeaderFooter(pageNode?.props.header as DocNode | undefined)\n const footerFn = renderHeaderFooter(pageNode?.props.footer as DocNode | undefined)\n\n const docDefinition: Record<string, unknown> = {\n pageSize: PAGE_SIZES[pageSize] ?? PAGE_SIZES.A4,\n pageOrientation,\n pageMargins: pageMargin,\n info: {\n title: (node.props.title as string) ?? \"\",\n author: (node.props.author as string) ?? \"\",\n subject: (node.props.subject as string) ?? \"\",\n keywords: (node.props.keywords as string[])?.join(\", \") ?? \"\",\n },\n content,\n defaultStyle: {\n fontSize: 12,\n lineHeight: 1.4,\n },\n // Keep sections together — break before a heading if it would be\n // orphaned at the bottom of a page.\n pageBreakBefore: (\n currentNode: { headlineLevel?: number },\n helpers: { getFollowingNodesOnPage?: () => unknown[] },\n ) => {\n if (currentNode.headlineLevel && helpers.getFollowingNodesOnPage) {\n const following = helpers.getFollowingNodesOnPage()\n if (following.length === 0) return true\n }\n return false\n },\n }\n\n if (headerFn) docDefinition.header = headerFn\n if (footerFn) docDefinition.footer = footerFn\n\n try {\n const pdf = pdfMake.createPdf(docDefinition)\n const buffer = await pdf.getBuffer()\n return new Uint8Array(buffer)\n } catch (err) {\n throw new Error(`[@pyreon/document] PDF generation failed: ${err}`)\n }\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pdf-IuBgTb3T.js","names":[],"sources":["../src/renderers/pdf.ts"],"sourcesContent":["import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'\n\n/**\n * PDF renderer — lazy-loads pdfmake on first use.\n * pdfmake handles pagination, tables, text wrapping, and font embedding.\n *\n * @example\n * ```ts\n * import { render, Document, Page, Heading } from '@pyreon/document'\n *\n * const doc = Document({\n * title: 'Report',\n * children: Page({ children: Heading({ children: 'Hello' }) }),\n * })\n * const pdf = await render(doc, 'pdf') // → Uint8Array\n * ```\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === 'string' ? c : getTextContent((c as DocNode).children)))\n .join('')\n}\n\ntype PdfContent = Record<string, unknown> | string | PdfContent[]\n\n/** pdfmake expects page sizes as `{ width, height }` objects. */\nconst PAGE_SIZES: Record<string, { width: number; height: number }> = {\n A3: { width: 841.89, height: 1190.55 },\n A4: { width: 595.28, height: 841.89 },\n A5: { width: 419.53, height: 595.28 },\n letter: { width: 612, height: 792 },\n legal: { width: 612, height: 1008 },\n tabloid: { width: 792, height: 1224 },\n}\n\n/**\n * Resolve an image `src` for pdfmake.\n *\n * - `data:` URIs are passed through directly (pdfmake supports base64).\n * - `http(s)://` URLs cannot be resolved at render time in the browser.\n * A placeholder text node is returned instead.\n * - Relative / absolute paths (e.g. `/logo.png`) cannot be resolved without\n * a server-side fetch, so they are skipped with a placeholder.\n */\nfunction resolveImageSrc(\n src: string,\n): { image: string } | { text: string; italics: true; color: string } {\n if (src.startsWith('data:')) {\n return { image: src }\n }\n if (src.startsWith('http://') || src.startsWith('https://')) {\n return { text: `[Image: ${src}]`, italics: true, color: '#999999' }\n }\n // Local path — cannot resolve in browser\n return { text: `[Image: ${src}]`, italics: true, color: '#999999' }\n}\n\nfunction nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {\n const p = node.props\n\n switch (node.type) {\n case 'document':\n case 'page':\n return node.children\n .map((c) => (typeof c === 'string' ? c : nodeToContent(c)))\n .filter((c): c is PdfContent => c != null)\n\n case 'section': {\n const content = node.children\n .map((c) => (typeof c === 'string' ? c : nodeToContent(c)))\n .filter((c): c is PdfContent => c != null)\n .flat()\n\n if (p.direction === 'row') {\n return {\n columns: node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((child) => ({\n stack: [nodeToContent(child)].flat().filter(Boolean),\n width: child.props.width === '*' || !child.props.width ? '*' : child.props.width,\n })),\n columnGap: (p.gap as number) ?? 0,\n }\n }\n\n return content\n }\n\n case 'row': {\n return {\n columns: node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((child) => ({\n stack: [nodeToContent(child)].flat().filter(Boolean),\n width: child.props.width ?? '*',\n })),\n columnGap: (p.gap as number) ?? 0,\n }\n }\n\n case 'column':\n return node.children\n .map((c) => (typeof c === 'string' ? c : nodeToContent(c)))\n .filter((c): c is PdfContent => c != null)\n .flat()\n\n case 'heading': {\n const level = (p.level as number) ?? 1\n const sizes: Record<number, number> = {\n 1: 24,\n 2: 20,\n 3: 18,\n 4: 16,\n 5: 14,\n 6: 12,\n }\n return {\n text: getTextContent(node.children),\n fontSize: sizes[level] ?? 18,\n bold: true,\n color: (p.color as string) ?? '#000000',\n alignment: (p.align as string) ?? 'left',\n margin: [0, level === 1 ? 0 : 8, 0, 8],\n }\n }\n\n case 'text':\n return {\n text: getTextContent(node.children),\n fontSize: (p.size as number) ?? 12,\n color: (p.color as string) ?? '#333333',\n bold: p.bold ?? false,\n italics: p.italic ?? false,\n decoration: p.underline ? 'underline' : p.strikethrough ? 'lineThrough' : undefined,\n alignment: (p.align as string) ?? 'left',\n lineHeight: (p.lineHeight as number) ?? 1.4,\n margin: [0, 0, 0, 8],\n }\n\n case 'link':\n return {\n text: getTextContent(node.children),\n link: p.href as string,\n color: (p.color as string) ?? '#4f46e5',\n decoration: 'underline',\n }\n\n case 'image': {\n const src = p.src as string\n const resolved = resolveImageSrc(src)\n\n if ('image' in resolved) {\n const result: Record<string, unknown> = {\n image: resolved.image,\n fit: [p.width ?? 500, p.height ?? 400],\n margin: [0, 0, 0, 8],\n }\n if (p.align === 'center') result.alignment = 'center'\n if (p.align === 'right') result.alignment = 'right'\n return result\n }\n\n // Placeholder for non-resolvable images\n return { ...resolved, margin: [0, 0, 0, 8] }\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n const hs = p.headerStyle as { background?: string; color?: string } | undefined\n\n const headerRow = columns.map((col) => ({\n text: col.header,\n bold: true,\n fillColor: hs?.background ?? '#f5f5f5',\n color: hs?.color ?? '#000000',\n alignment: col.align ?? 'left',\n }))\n\n const dataRows = rows.map((row, rowIdx) =>\n columns.map((col, colIdx) => ({\n text: String(row[colIdx] ?? ''),\n alignment: col.align ?? 'left',\n fillColor: p.striped && rowIdx % 2 === 1 ? '#f9f9f9' : undefined,\n })),\n )\n\n const widths = columns.map((col) => {\n if (!col.width) return '*'\n if (typeof col.width === 'string' && col.width.endsWith('%')) {\n return col.width\n }\n return col.width\n })\n\n return {\n table: {\n headerRows: 1,\n widths,\n body: [headerRow, ...dataRows],\n },\n layout: p.bordered ? undefined : 'lightHorizontalLines',\n unbreakable: p.keepTogether ?? false,\n margin: [0, 0, 0, 12],\n }\n }\n\n case 'list': {\n const items = node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((item) => getTextContent(item.children))\n\n return p.ordered ? { ol: items, margin: [0, 0, 0, 8] } : { ul: items, margin: [0, 0, 0, 8] }\n }\n\n case 'list-item':\n return getTextContent(node.children)\n\n case 'code':\n return {\n text: getTextContent(node.children),\n font: 'Courier',\n fontSize: 10,\n background: '#f5f5f5',\n margin: [0, 0, 0, 8],\n }\n\n case 'page-break':\n return { text: '', pageBreak: 'after' }\n\n case 'divider':\n return {\n canvas: [\n {\n type: 'line',\n x1: 0,\n y1: 0,\n x2: 515,\n y2: 0,\n lineWidth: (p.thickness as number) ?? 1,\n lineColor: (p.color as string) ?? '#dddddd',\n },\n ],\n margin: [0, 8, 0, 8],\n }\n\n case 'spacer':\n return { text: '', margin: [0, (p.height as number) ?? 12, 0, 0] }\n\n case 'button':\n return {\n text: getTextContent(node.children),\n link: p.href as string,\n bold: true,\n color: (p.color as string) ?? '#ffffff',\n background: (p.background as string) ?? '#4f46e5',\n margin: [0, 8, 0, 8],\n }\n\n case 'quote':\n return {\n table: {\n widths: [4, '*'],\n body: [\n [\n { text: '', fillColor: (p.borderColor as string) ?? '#dddddd' },\n {\n text: getTextContent(node.children),\n italics: true,\n color: '#555555',\n margin: [8, 4, 0, 4],\n },\n ],\n ],\n },\n layout: 'noBorders',\n margin: [0, 4, 0, 8],\n }\n\n default:\n return null\n }\n}\n\nfunction resolveMargin(\n margin: number | [number, number] | [number, number, number, number] | undefined,\n): [number, number, number, number] {\n if (margin == null) return [40, 40, 40, 40]\n if (typeof margin === 'number') return [margin, margin, margin, margin]\n if (margin.length === 2) return [margin[1], margin[0], margin[1], margin[0]]\n return margin\n}\n\n/**\n * Render header/footer DocNodes into pdfmake content for page headers/footers.\n *\n * pdfmake header/footer functions receive `(currentPage, pageCount, pageSize)`\n * and must return a content object. We flatten the DocNode into static content.\n */\nfunction renderHeaderFooter(node: DocNode | undefined): PdfContent | undefined {\n if (!node) return undefined\n const content = nodeToContent(node)\n if (content == null) return undefined\n if (Array.isArray(content)) return { stack: content, margin: [40, 10, 40, 0] }\n if (typeof content === 'object') return { ...content, margin: [40, 10, 40, 0] }\n return { text: content, margin: [40, 10, 40, 0] }\n}\n\nexport const pdfRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {\n // Lazy-load pdfmake — handle ESM/CJS interop\n let pdfMakeModule: any\n let pdfFontsModule: any\n try {\n pdfMakeModule = await import('pdfmake/build/pdfmake')\n pdfFontsModule = await import('pdfmake/build/vfs_fonts')\n } catch {\n throw new Error(\n '[@pyreon/document] PDF renderer requires \"pdfmake\" package. Install it: bun add pdfmake',\n )\n }\n\n // Resolve the actual exports (handle .default for ESM wrappers).\n // pdfmake's default export is a singleton instance of browser_extensions_pdfmake.\n // ESM interop may wrap it in an extra .default layer.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- pdfmake types are incomplete\n let pdfMake: any = pdfMakeModule.default ?? pdfMakeModule\n if (pdfMake.default && typeof pdfMake.default.createPdf === 'function') {\n pdfMake = pdfMake.default\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- pdfmake types are incomplete\n const pdfFonts: any = pdfFontsModule.default ?? pdfFontsModule\n\n // Assign virtual filesystem for fonts\n if (pdfMake.vfs == null) {\n pdfMake.vfs = pdfFonts.pdfMake?.vfs ?? pdfFonts.vfs\n }\n\n // Find page config\n const pageNode = node.children.find(\n (c): c is DocNode => typeof c !== 'string' && c.type === 'page',\n )\n const pageSize = (pageNode?.props.size as string) ?? 'A4'\n const pageOrientation = (pageNode?.props.orientation as string) ?? 'portrait'\n const pageMargin = resolveMargin(\n pageNode?.props.margin as\n | number\n | [number, number]\n | [number, number, number, number]\n | undefined,\n )\n\n const content = [nodeToContent(node)].flat().filter(Boolean) as PdfContent[]\n\n // Build header/footer from PageProps if present\n const headerFn = renderHeaderFooter(pageNode?.props.header as DocNode | undefined)\n const footerFn = renderHeaderFooter(pageNode?.props.footer as DocNode | undefined)\n\n const docDefinition: Record<string, unknown> = {\n pageSize: PAGE_SIZES[pageSize] ?? PAGE_SIZES.A4,\n pageOrientation,\n pageMargins: pageMargin,\n info: {\n title: (node.props.title as string) ?? '',\n author: (node.props.author as string) ?? '',\n subject: (node.props.subject as string) ?? '',\n keywords: (node.props.keywords as string[])?.join(', ') ?? '',\n },\n content,\n defaultStyle: {\n fontSize: 12,\n lineHeight: 1.4,\n },\n // Keep sections together — break before a heading if it would be\n // orphaned at the bottom of a page.\n pageBreakBefore: (\n currentNode: { headlineLevel?: number },\n helpers: { getFollowingNodesOnPage?: () => unknown[] },\n ) => {\n if (currentNode.headlineLevel && helpers.getFollowingNodesOnPage) {\n const following = helpers.getFollowingNodesOnPage()\n if (following.length === 0) return true\n }\n return false\n },\n }\n\n if (headerFn) docDefinition.header = headerFn\n if (footerFn) docDefinition.footer = footerFn\n\n try {\n const pdf = pdfMake.createPdf(docDefinition)\n const buffer = await pdf.getBuffer()\n return new Uint8Array(buffer)\n } catch (err) {\n throw new Error(`[@pyreon/document] PDF generation failed: ${err}`)\n }\n },\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pptx-DXiMiYFM.js","names":[],"sources":["../src/renderers/pptx.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc, sanitizeXmlColor } from \"../sanitize\"\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\n/**\n * PPTX renderer — lazy-loads pptxgenjs on first use.\n * Each `<Page>` becomes a slide. Document nodes map to PPTX elements.\n *\n * @example\n * ```ts\n * import { render, Document, Page, Heading, Text } from '@pyreon/document'\n *\n * const doc = Document({\n * title: 'Presentation',\n * children: [\n * Page({ children: [Heading({ children: 'Slide 1' }), Text({ children: 'Hello' })] }),\n * Page({ children: [Heading({ children: 'Slide 2' })] }),\n * ],\n * })\n * const pptx = await render(doc, 'pptx') // → Uint8Array\n * ```\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === \"string\" ? c : getTextContent((c as DocNode).children)))\n .join(\"\")\n}\n\n/** Vertical position tracker for placing elements on a slide. */\ninterface SlideContext {\n slide: PptxSlide\n y: number\n}\n\n// Duck-typed pptxgenjs interfaces to avoid hard dependency on types\ninterface PptxSlide {\n addText(text: string | PptxTextProps[], opts?: Record<string, unknown>): void\n addImage(opts: Record<string, unknown>): void\n addTable(rows: unknown[][], opts?: Record<string, unknown>): void\n}\n\ninterface PptxTextProps {\n text: string\n options?: Record<string, unknown>\n}\n\ninterface PptxGen {\n addSlide(): PptxSlide\n write(outputType: string): Promise<unknown>\n title: string\n author: string\n subject: string\n}\n\nconst HEADING_SIZES: Record<number, number> = {\n 1: 28,\n 2: 24,\n 3: 20,\n 4: 18,\n 5: 16,\n 6: 14,\n}\n\nconst SLIDE_WIDTH = 10 // inches\nconst CONTENT_MARGIN = 0.5\nconst CONTENT_WIDTH = SLIDE_WIDTH - CONTENT_MARGIN * 2\n\nfunction processNode(node: DocNode, ctx: SlideContext): void {\n const p = node.props\n\n switch (node.type) {\n case \"heading\": {\n const level = (p.level as number) ?? 1\n const fontSize = HEADING_SIZES[level] ?? 20\n ctx.slide.addText(getTextContent(node.children), {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.6,\n fontSize,\n bold: true,\n color: sanitizeXmlColor((p.color as string) ?? \"#000000\"),\n align: (p.align as string) ?? \"left\",\n })\n ctx.y += 0.7\n break\n }\n\n case \"text\": {\n const text = getTextContent(node.children)\n ctx.slide.addText(text, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.4,\n fontSize: (p.size as number) ?? 14,\n bold: p.bold ?? false,\n italic: p.italic ?? false,\n underline: p.underline ? { style: \"sng\" } : undefined,\n strike: p.strikethrough ? \"sngStrike\" : undefined,\n color: sanitizeXmlColor((p.color as string) ?? \"#333333\"),\n align: (p.align as string) ?? \"left\",\n })\n ctx.y += 0.5\n break\n }\n\n case \"image\": {\n const src = sanitizeImageSrc(p.src as string)\n const w = Math.min(((p.width as number) ?? 400) / 96, CONTENT_WIDTH)\n const h = ((p.height as number) ?? 300) / 96\n\n if (src.startsWith(\"data:\")) {\n ctx.slide.addImage({\n data: src,\n x: CONTENT_MARGIN,\n y: ctx.y,\n w,\n h,\n })\n ctx.y += h + 0.2\n }\n // HTTP URLs and local paths are not supported — skip silently\n break\n }\n\n case \"table\": {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n const hs = p.headerStyle as { background?: string; color?: string } | undefined\n\n const headerRow = columns.map((col) => ({\n text: col.header,\n options: {\n bold: true,\n fill: { color: sanitizeXmlColor(hs?.background ?? \"#f5f5f5\") },\n color: sanitizeXmlColor(hs?.color ?? \"#000000\"),\n align: col.align ?? \"left\",\n fontSize: 12,\n },\n }))\n\n const dataRows = rows.map((row, rowIdx) =>\n columns.map((col, colIdx) => ({\n text: String(row[colIdx] ?? \"\"),\n options: {\n align: col.align ?? \"left\",\n fontSize: 11,\n fill: p.striped && rowIdx % 2 === 1 ? { color: \"F9F9F9\" } : undefined,\n },\n })),\n )\n\n const allRows = [headerRow, ...dataRows]\n const rowHeight = 0.35\n const tableHeight = allRows.length * rowHeight\n\n ctx.slide.addTable(allRows, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n border: { pt: 0.5, color: \"DDDDDD\" },\n rowH: rowHeight,\n })\n ctx.y += tableHeight + 0.2\n break\n }\n\n case \"list\": {\n const items = node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((item) => getTextContent(item.children))\n\n const isOrdered = p.ordered as boolean\n const listText = items.map((item, i) => ({\n text: isOrdered ? `${i + 1}. ${item}\\n` : `\\u2022 ${item}\\n`,\n options: { fontSize: 13, bullet: false },\n }))\n\n ctx.slide.addText(listText, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: items.length * 0.35,\n })\n ctx.y += items.length * 0.35 + 0.1\n break\n }\n\n case \"code\": {\n const text = getTextContent(node.children)\n ctx.slide.addText(text, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.5,\n fontSize: 10,\n fontFace: \"Courier New\",\n fill: { color: \"F5F5F5\" },\n color: \"333333\",\n })\n ctx.y += 0.6\n break\n }\n\n case \"quote\": {\n const text = getTextContent(node.children)\n ctx.slide.addText(text, {\n x: CONTENT_MARGIN + 0.3,\n y: ctx.y,\n w: CONTENT_WIDTH - 0.3,\n h: 0.5,\n fontSize: 13,\n italic: true,\n color: \"555555\",\n })\n ctx.y += 0.6\n break\n }\n\n case \"link\": {\n ctx.slide.addText(getTextContent(node.children), {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.4,\n fontSize: 13,\n color: \"4F46E5\",\n underline: { style: \"sng\" },\n hyperlink: { url: sanitizeHref(p.href as string) },\n })\n ctx.y += 0.5\n break\n }\n\n case \"button\": {\n ctx.slide.addText(getTextContent(node.children), {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: 3,\n h: 0.5,\n fontSize: 14,\n bold: true,\n color: sanitizeXmlColor((p.color as string) ?? \"#ffffff\"),\n fill: {\n color: sanitizeXmlColor((p.background as string) ?? \"#4f46e5\"),\n },\n align: \"center\",\n hyperlink: { url: sanitizeHref(p.href as string) },\n })\n ctx.y += 0.6\n break\n }\n\n case \"spacer\": {\n ctx.y += ((p.height as number) ?? 12) / 72\n break\n }\n\n case \"divider\": {\n // Render as a thin line using a text element with top border\n ctx.slide.addText(\"\", {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.02,\n fill: { color: sanitizeXmlColor((p.color as string) ?? \"#DDDDDD\") },\n })\n ctx.y += 0.2\n break\n }\n\n // Container types — recurse into children\n case \"section\":\n case \"row\":\n case \"column\":\n for (const child of node.children) {\n if (typeof child !== \"string\") {\n processNode(child, ctx)\n }\n }\n break\n\n default:\n break\n }\n}\n\nfunction processSlide(pageNode: DocNode, pptx: PptxGen): void {\n const slide = pptx.addSlide()\n const ctx: SlideContext = { slide, y: CONTENT_MARGIN }\n\n for (const child of pageNode.children) {\n if (typeof child !== \"string\") {\n processNode(child, ctx)\n }\n }\n}\n\nexport const pptxRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {\n let PptxGenJS: any\n try {\n PptxGenJS = await import(\"pptxgenjs\")\n } catch {\n throw new Error(\n '[@pyreon/document] PPTX renderer requires \"pptxgenjs\" package. Install it: bun add pptxgenjs',\n )\n }\n const PptxGenClass = PptxGenJS.default ?? PptxGenJS\n\n const pptx = new PptxGenClass() as PptxGen\n\n // Set metadata\n if (node.props.title) pptx.title = node.props.title as string\n if (node.props.author) pptx.author = node.props.author as string\n if (node.props.subject) pptx.subject = node.props.subject as string\n\n // Collect pages — each becomes a slide\n const pages: DocNode[] = []\n for (const child of node.children) {\n if (typeof child !== \"string\" && child.type === \"page\") {\n pages.push(child)\n }\n }\n\n // If no explicit pages, treat entire document content as one slide\n if (pages.length === 0) {\n const syntheticPage: DocNode = {\n type: \"page\",\n props: {},\n children: node.children,\n }\n pages.push(syntheticPage)\n }\n\n for (const page of pages) {\n processSlide(page, pptx)\n }\n\n const output = await pptx.write(\"arraybuffer\")\n return new Uint8Array(output as ArrayBuffer)\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pptx-DXiMiYFM.js","names":[],"sources":["../src/renderers/pptx.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc, sanitizeXmlColor } from '../sanitize'\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'\n\n/**\n * PPTX renderer — lazy-loads pptxgenjs on first use.\n * Each `<Page>` becomes a slide. Document nodes map to PPTX elements.\n *\n * @example\n * ```ts\n * import { render, Document, Page, Heading, Text } from '@pyreon/document'\n *\n * const doc = Document({\n * title: 'Presentation',\n * children: [\n * Page({ children: [Heading({ children: 'Slide 1' }), Text({ children: 'Hello' })] }),\n * Page({ children: [Heading({ children: 'Slide 2' })] }),\n * ],\n * })\n * const pptx = await render(doc, 'pptx') // → Uint8Array\n * ```\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === 'string' ? c : getTextContent((c as DocNode).children)))\n .join('')\n}\n\n/** Vertical position tracker for placing elements on a slide. */\ninterface SlideContext {\n slide: PptxSlide\n y: number\n}\n\n// Duck-typed pptxgenjs interfaces to avoid hard dependency on types\ninterface PptxSlide {\n addText(text: string | PptxTextProps[], opts?: Record<string, unknown>): void\n addImage(opts: Record<string, unknown>): void\n addTable(rows: unknown[][], opts?: Record<string, unknown>): void\n}\n\ninterface PptxTextProps {\n text: string\n options?: Record<string, unknown>\n}\n\ninterface PptxGen {\n addSlide(): PptxSlide\n write(outputType: string): Promise<unknown>\n title: string\n author: string\n subject: string\n}\n\nconst HEADING_SIZES: Record<number, number> = {\n 1: 28,\n 2: 24,\n 3: 20,\n 4: 18,\n 5: 16,\n 6: 14,\n}\n\nconst SLIDE_WIDTH = 10 // inches\nconst CONTENT_MARGIN = 0.5\nconst CONTENT_WIDTH = SLIDE_WIDTH - CONTENT_MARGIN * 2\n\nfunction processNode(node: DocNode, ctx: SlideContext): void {\n const p = node.props\n\n switch (node.type) {\n case 'heading': {\n const level = (p.level as number) ?? 1\n const fontSize = HEADING_SIZES[level] ?? 20\n ctx.slide.addText(getTextContent(node.children), {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.6,\n fontSize,\n bold: true,\n color: sanitizeXmlColor((p.color as string) ?? '#000000'),\n align: (p.align as string) ?? 'left',\n })\n ctx.y += 0.7\n break\n }\n\n case 'text': {\n const text = getTextContent(node.children)\n ctx.slide.addText(text, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.4,\n fontSize: (p.size as number) ?? 14,\n bold: p.bold ?? false,\n italic: p.italic ?? false,\n underline: p.underline ? { style: 'sng' } : undefined,\n strike: p.strikethrough ? 'sngStrike' : undefined,\n color: sanitizeXmlColor((p.color as string) ?? '#333333'),\n align: (p.align as string) ?? 'left',\n })\n ctx.y += 0.5\n break\n }\n\n case 'image': {\n const src = sanitizeImageSrc(p.src as string)\n const w = Math.min(((p.width as number) ?? 400) / 96, CONTENT_WIDTH)\n const h = ((p.height as number) ?? 300) / 96\n\n if (src.startsWith('data:')) {\n ctx.slide.addImage({\n data: src,\n x: CONTENT_MARGIN,\n y: ctx.y,\n w,\n h,\n })\n ctx.y += h + 0.2\n }\n // HTTP URLs and local paths are not supported — skip silently\n break\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n const hs = p.headerStyle as { background?: string; color?: string } | undefined\n\n const headerRow = columns.map((col) => ({\n text: col.header,\n options: {\n bold: true,\n fill: { color: sanitizeXmlColor(hs?.background ?? '#f5f5f5') },\n color: sanitizeXmlColor(hs?.color ?? '#000000'),\n align: col.align ?? 'left',\n fontSize: 12,\n },\n }))\n\n const dataRows = rows.map((row, rowIdx) =>\n columns.map((col, colIdx) => ({\n text: String(row[colIdx] ?? ''),\n options: {\n align: col.align ?? 'left',\n fontSize: 11,\n fill: p.striped && rowIdx % 2 === 1 ? { color: 'F9F9F9' } : undefined,\n },\n })),\n )\n\n const allRows = [headerRow, ...dataRows]\n const rowHeight = 0.35\n const tableHeight = allRows.length * rowHeight\n\n ctx.slide.addTable(allRows, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n border: { pt: 0.5, color: 'DDDDDD' },\n rowH: rowHeight,\n })\n ctx.y += tableHeight + 0.2\n break\n }\n\n case 'list': {\n const items = node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((item) => getTextContent(item.children))\n\n const isOrdered = p.ordered as boolean\n const listText = items.map((item, i) => ({\n text: isOrdered ? `${i + 1}. ${item}\\n` : `\\u2022 ${item}\\n`,\n options: { fontSize: 13, bullet: false },\n }))\n\n ctx.slide.addText(listText, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: items.length * 0.35,\n })\n ctx.y += items.length * 0.35 + 0.1\n break\n }\n\n case 'code': {\n const text = getTextContent(node.children)\n ctx.slide.addText(text, {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.5,\n fontSize: 10,\n fontFace: 'Courier New',\n fill: { color: 'F5F5F5' },\n color: '333333',\n })\n ctx.y += 0.6\n break\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n ctx.slide.addText(text, {\n x: CONTENT_MARGIN + 0.3,\n y: ctx.y,\n w: CONTENT_WIDTH - 0.3,\n h: 0.5,\n fontSize: 13,\n italic: true,\n color: '555555',\n })\n ctx.y += 0.6\n break\n }\n\n case 'link': {\n ctx.slide.addText(getTextContent(node.children), {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.4,\n fontSize: 13,\n color: '4F46E5',\n underline: { style: 'sng' },\n hyperlink: { url: sanitizeHref(p.href as string) },\n })\n ctx.y += 0.5\n break\n }\n\n case 'button': {\n ctx.slide.addText(getTextContent(node.children), {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: 3,\n h: 0.5,\n fontSize: 14,\n bold: true,\n color: sanitizeXmlColor((p.color as string) ?? '#ffffff'),\n fill: {\n color: sanitizeXmlColor((p.background as string) ?? '#4f46e5'),\n },\n align: 'center',\n hyperlink: { url: sanitizeHref(p.href as string) },\n })\n ctx.y += 0.6\n break\n }\n\n case 'spacer': {\n ctx.y += ((p.height as number) ?? 12) / 72\n break\n }\n\n case 'divider': {\n // Render as a thin line using a text element with top border\n ctx.slide.addText('', {\n x: CONTENT_MARGIN,\n y: ctx.y,\n w: CONTENT_WIDTH,\n h: 0.02,\n fill: { color: sanitizeXmlColor((p.color as string) ?? '#DDDDDD') },\n })\n ctx.y += 0.2\n break\n }\n\n // Container types — recurse into children\n case 'section':\n case 'row':\n case 'column':\n for (const child of node.children) {\n if (typeof child !== 'string') {\n processNode(child, ctx)\n }\n }\n break\n\n default:\n break\n }\n}\n\nfunction processSlide(pageNode: DocNode, pptx: PptxGen): void {\n const slide = pptx.addSlide()\n const ctx: SlideContext = { slide, y: CONTENT_MARGIN }\n\n for (const child of pageNode.children) {\n if (typeof child !== 'string') {\n processNode(child, ctx)\n }\n }\n}\n\nexport const pptxRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {\n let PptxGenJS: any\n try {\n PptxGenJS = await import('pptxgenjs')\n } catch {\n throw new Error(\n '[@pyreon/document] PPTX renderer requires \"pptxgenjs\" package. Install it: bun add pptxgenjs',\n )\n }\n const PptxGenClass = PptxGenJS.default ?? PptxGenJS\n\n const pptx = new PptxGenClass() as PptxGen\n\n // Set metadata\n if (node.props.title) pptx.title = node.props.title as string\n if (node.props.author) pptx.author = node.props.author as string\n if (node.props.subject) pptx.subject = node.props.subject as string\n\n // Collect pages — each becomes a slide\n const pages: DocNode[] = []\n for (const child of node.children) {\n if (typeof child !== 'string' && child.type === 'page') {\n pages.push(child)\n }\n }\n\n // If no explicit pages, treat entire document content as one slide\n if (pages.length === 0) {\n const syntheticPage: DocNode = {\n type: 'page',\n props: {},\n children: node.children,\n }\n pages.push(syntheticPage)\n }\n\n for (const page of pages) {\n processSlide(page, pptx)\n }\n\n const output = await pptx.write('arraybuffer')\n return new Uint8Array(output as ArrayBuffer)\n },\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize-O_3j1mNJ.js","names":[],"sources":["../src/sanitize.ts"],"sourcesContent":["/**\n * Shared sanitization utilities for document renderers.\n * Prevents XSS via CSS injection, XML injection, and javascript: protocol attacks.\n */\n\n/**\n * Sanitize a CSS value — strips characters that could break out of a CSS property.\n * Blocks: semicolons, braces, angle brackets, quotes, backslashes, expressions.\n */\nexport function sanitizeCss(value: string | undefined): string {\n if (value == null) return \"\"\n // Remove anything that could break out of a CSS value\n return value\n .replace(/[;{}()<>\\\\'\"]/g, \"\")\n .replace(/expression\\s*\\(/gi, \"\")\n .replace(/url\\s*\\(/gi, \"\")\n .replace(/javascript\\s*:/gi, \"\")\n}\n\n/**\n * Sanitize a color value — only allows hex colors, named colors, rgb/rgba, hsl/hsla.\n * Returns the value if valid, empty string if not.\n */\nexport function sanitizeColor(value: string | undefined): string {\n if (value == null) return \"\"\n const trimmed = value.trim()\n // Hex: #fff, #ffffff, #ffffffff\n if (/^#[0-9a-fA-F]{3,8}$/.test(trimmed)) return trimmed\n // Named colors (common subset)\n if (/^[a-zA-Z]{1,20}$/.test(trimmed)) return trimmed\n // rgb/rgba/hsl/hsla\n if (/^(rgb|hsl)a?\\(\\s*[\\d.,\\s%]+\\)$/.test(trimmed)) return trimmed\n // transparent, inherit, currentColor\n if (/^(transparent|inherit|currentColor|initial|unset)$/i.test(trimmed)) return trimmed\n return \"\"\n}\n\n/**\n * Sanitize a color for XML attributes (DOCX/PPTX) — only hex without #.\n * Returns 6-char hex string or default.\n */\nexport function sanitizeXmlColor(value: string | undefined, fallback = \"000000\"): string {\n if (value == null) return fallback\n const hex = value.replace(\"#\", \"\")\n if (/^[0-9a-fA-F]{3,8}$/.test(hex)) return hex\n return fallback\n}\n\n/**\n * Sanitize a URL — blocks javascript:, data: (except images), and vbscript: protocols.\n * Returns the URL if safe, empty string if not.\n */\nexport function sanitizeHref(url: string | undefined): string {\n if (url == null) return \"\"\n const trimmed = url.trim()\n // Block dangerous protocols\n const lower = trimmed.toLowerCase().replace(/\\s/g, \"\")\n if (lower.startsWith(\"javascript:\")) return \"\"\n if (lower.startsWith(\"vbscript:\")) return \"\"\n if (lower.startsWith(\"data:\") && !lower.startsWith(\"data:image/\")) return \"\"\n return trimmed\n}\n\n/**\n * Sanitize an image src — allows http(s), data:image, and relative paths.\n * Blocks javascript:, vbscript:, and non-image data: URIs.\n */\nexport function sanitizeImageSrc(src: string | undefined): string {\n if (src == null) return \"\"\n const trimmed = src.trim()\n const lower = trimmed.toLowerCase().replace(/\\s/g, \"\")\n if (lower.startsWith(\"javascript:\")) return \"\"\n if (lower.startsWith(\"vbscript:\")) return \"\"\n if (lower.startsWith(\"data:\") && !lower.startsWith(\"data:image/\")) return \"\"\n return trimmed\n}\n\n/**\n * Sanitize a style attribute value — validates it's safe CSS.\n */\nexport function sanitizeStyle(value: string | undefined): string {\n if (value == null) return \"\"\n return sanitizeCss(value)\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,YAAY,OAAmC;AAC7D,KAAI,SAAS,KAAM,QAAO;AAE1B,QAAO,MACJ,QAAQ,kBAAkB,GAAG,CAC7B,QAAQ,qBAAqB,GAAG,CAChC,QAAQ,cAAc,GAAG,CACzB,QAAQ,oBAAoB,GAAG;;;;;;AAOpC,SAAgB,cAAc,OAAmC;AAC/D,KAAI,SAAS,KAAM,QAAO;CAC1B,MAAM,UAAU,MAAM,MAAM;AAE5B,KAAI,sBAAsB,KAAK,QAAQ,CAAE,QAAO;AAEhD,KAAI,mBAAmB,KAAK,QAAQ,CAAE,QAAO;AAE7C,KAAI,iCAAiC,KAAK,QAAQ,CAAE,QAAO;AAE3D,KAAI,sDAAsD,KAAK,QAAQ,CAAE,QAAO;AAChF,QAAO;;;;;;AAOT,SAAgB,iBAAiB,OAA2B,WAAW,UAAkB;AACvF,KAAI,SAAS,KAAM,QAAO;CAC1B,MAAM,MAAM,MAAM,QAAQ,KAAK,GAAG;AAClC,KAAI,qBAAqB,KAAK,IAAI,CAAE,QAAO;AAC3C,QAAO;;;;;;AAOT,SAAgB,aAAa,KAAiC;AAC5D,KAAI,OAAO,KAAM,QAAO;CACxB,MAAM,UAAU,IAAI,MAAM;CAE1B,MAAM,QAAQ,QAAQ,aAAa,CAAC,QAAQ,OAAO,GAAG;AACtD,KAAI,MAAM,WAAW,cAAc,CAAE,QAAO;AAC5C,KAAI,MAAM,WAAW,YAAY,CAAE,QAAO;AAC1C,KAAI,MAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,WAAW,cAAc,CAAE,QAAO;AAC1E,QAAO;;;;;;AAOT,SAAgB,iBAAiB,KAAiC;AAChE,KAAI,OAAO,KAAM,QAAO;CACxB,MAAM,UAAU,IAAI,MAAM;CAC1B,MAAM,QAAQ,QAAQ,aAAa,CAAC,QAAQ,OAAO,GAAG;AACtD,KAAI,MAAM,WAAW,cAAc,CAAE,QAAO;AAC5C,KAAI,MAAM,WAAW,YAAY,CAAE,QAAO;AAC1C,KAAI,MAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,WAAW,cAAc,CAAE,QAAO;AAC1E,QAAO;;;;;AAMT,SAAgB,cAAc,OAAmC;AAC/D,KAAI,SAAS,KAAM,QAAO;AAC1B,QAAO,YAAY,MAAM"}
1
+ {"version":3,"file":"sanitize-O_3j1mNJ.js","names":[],"sources":["../src/sanitize.ts"],"sourcesContent":["/**\n * Shared sanitization utilities for document renderers.\n * Prevents XSS via CSS injection, XML injection, and javascript: protocol attacks.\n */\n\n/**\n * Sanitize a CSS value — strips characters that could break out of a CSS property.\n * Blocks: semicolons, braces, angle brackets, quotes, backslashes, expressions.\n */\nexport function sanitizeCss(value: string | undefined): string {\n if (value == null) return ''\n // Remove anything that could break out of a CSS value\n return value\n .replace(/[;{}()<>\\\\'\"]/g, '')\n .replace(/expression\\s*\\(/gi, '')\n .replace(/url\\s*\\(/gi, '')\n .replace(/javascript\\s*:/gi, '')\n}\n\n/**\n * Sanitize a color value — only allows hex colors, named colors, rgb/rgba, hsl/hsla.\n * Returns the value if valid, empty string if not.\n */\nexport function sanitizeColor(value: string | undefined): string {\n if (value == null) return ''\n const trimmed = value.trim()\n // Hex: #fff, #ffffff, #ffffffff\n if (/^#[0-9a-fA-F]{3,8}$/.test(trimmed)) return trimmed\n // Named colors (common subset)\n if (/^[a-zA-Z]{1,20}$/.test(trimmed)) return trimmed\n // rgb/rgba/hsl/hsla\n if (/^(rgb|hsl)a?\\(\\s*[\\d.,\\s%]+\\)$/.test(trimmed)) return trimmed\n // transparent, inherit, currentColor\n if (/^(transparent|inherit|currentColor|initial|unset)$/i.test(trimmed)) return trimmed\n return ''\n}\n\n/**\n * Sanitize a color for XML attributes (DOCX/PPTX) — only hex without #.\n * Returns 6-char hex string or default.\n */\nexport function sanitizeXmlColor(value: string | undefined, fallback = '000000'): string {\n if (value == null) return fallback\n const hex = value.replace('#', '')\n if (/^[0-9a-fA-F]{3,8}$/.test(hex)) return hex\n return fallback\n}\n\n/**\n * Sanitize a URL — blocks javascript:, data: (except images), and vbscript: protocols.\n * Returns the URL if safe, empty string if not.\n */\nexport function sanitizeHref(url: string | undefined): string {\n if (url == null) return ''\n const trimmed = url.trim()\n // Block dangerous protocols\n const lower = trimmed.toLowerCase().replace(/\\s/g, '')\n if (lower.startsWith('javascript:')) return ''\n if (lower.startsWith('vbscript:')) return ''\n if (lower.startsWith('data:') && !lower.startsWith('data:image/')) return ''\n return trimmed\n}\n\n/**\n * Sanitize an image src — allows http(s), data:image, and relative paths.\n * Blocks javascript:, vbscript:, and non-image data: URIs.\n */\nexport function sanitizeImageSrc(src: string | undefined): string {\n if (src == null) return ''\n const trimmed = src.trim()\n const lower = trimmed.toLowerCase().replace(/\\s/g, '')\n if (lower.startsWith('javascript:')) return ''\n if (lower.startsWith('vbscript:')) return ''\n if (lower.startsWith('data:') && !lower.startsWith('data:image/')) return ''\n return trimmed\n}\n\n/**\n * Sanitize a style attribute value — validates it's safe CSS.\n */\nexport function sanitizeStyle(value: string | undefined): string {\n if (value == null) return ''\n return sanitizeCss(value)\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,YAAY,OAAmC;AAC7D,KAAI,SAAS,KAAM,QAAO;AAE1B,QAAO,MACJ,QAAQ,kBAAkB,GAAG,CAC7B,QAAQ,qBAAqB,GAAG,CAChC,QAAQ,cAAc,GAAG,CACzB,QAAQ,oBAAoB,GAAG;;;;;;AAOpC,SAAgB,cAAc,OAAmC;AAC/D,KAAI,SAAS,KAAM,QAAO;CAC1B,MAAM,UAAU,MAAM,MAAM;AAE5B,KAAI,sBAAsB,KAAK,QAAQ,CAAE,QAAO;AAEhD,KAAI,mBAAmB,KAAK,QAAQ,CAAE,QAAO;AAE7C,KAAI,iCAAiC,KAAK,QAAQ,CAAE,QAAO;AAE3D,KAAI,sDAAsD,KAAK,QAAQ,CAAE,QAAO;AAChF,QAAO;;;;;;AAOT,SAAgB,iBAAiB,OAA2B,WAAW,UAAkB;AACvF,KAAI,SAAS,KAAM,QAAO;CAC1B,MAAM,MAAM,MAAM,QAAQ,KAAK,GAAG;AAClC,KAAI,qBAAqB,KAAK,IAAI,CAAE,QAAO;AAC3C,QAAO;;;;;;AAOT,SAAgB,aAAa,KAAiC;AAC5D,KAAI,OAAO,KAAM,QAAO;CACxB,MAAM,UAAU,IAAI,MAAM;CAE1B,MAAM,QAAQ,QAAQ,aAAa,CAAC,QAAQ,OAAO,GAAG;AACtD,KAAI,MAAM,WAAW,cAAc,CAAE,QAAO;AAC5C,KAAI,MAAM,WAAW,YAAY,CAAE,QAAO;AAC1C,KAAI,MAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,WAAW,cAAc,CAAE,QAAO;AAC1E,QAAO;;;;;;AAOT,SAAgB,iBAAiB,KAAiC;AAChE,KAAI,OAAO,KAAM,QAAO;CACxB,MAAM,UAAU,IAAI,MAAM;CAC1B,MAAM,QAAQ,QAAQ,aAAa,CAAC,QAAQ,OAAO,GAAG;AACtD,KAAI,MAAM,WAAW,cAAc,CAAE,QAAO;AAC5C,KAAI,MAAM,WAAW,YAAY,CAAE,QAAO;AAC1C,KAAI,MAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,WAAW,cAAc,CAAE,QAAO;AAC1E,QAAO;;;;;AAMT,SAAgB,cAAc,OAAmC;AAC/D,KAAI,SAAS,KAAM,QAAO;AAC1B,QAAO,YAAY,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"slack-BI3EQwYm.js","names":[],"sources":["../src/renderers/slack.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from \"../sanitize\"\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\n/**\n * Slack Block Kit renderer — outputs JSON that can be posted via Slack's API.\n * Maps document nodes to Slack blocks (section, header, divider, image, etc.).\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === \"string\" ? c : getTextContent((c as DocNode).children)))\n .join(\"\")\n}\n\ninterface SlackBlock {\n type: string\n [key: string]: unknown\n}\n\nfunction mrkdwn(text: string): { type: \"mrkdwn\"; text: string } {\n return { type: \"mrkdwn\", text }\n}\n\nfunction plainText(text: string): { type: \"plain_text\"; text: string } {\n return { type: \"plain_text\", text }\n}\n\nfunction nodeToBlocks(node: DocNode): SlackBlock[] {\n const p = node.props\n const blocks: SlackBlock[] = []\n\n switch (node.type) {\n case \"document\":\n case \"page\":\n case \"section\":\n case \"row\":\n case \"column\":\n for (const child of node.children) {\n if (typeof child !== \"string\") {\n blocks.push(...nodeToBlocks(child))\n }\n }\n break\n\n case \"heading\":\n blocks.push({\n type: \"header\",\n text: plainText(getTextContent(node.children)),\n })\n break\n\n case \"text\": {\n let text = getTextContent(node.children)\n if (p.bold) text = `*${text}*`\n if (p.italic) text = `_${text}_`\n if (p.strikethrough) text = `~${text}~`\n blocks.push({\n type: \"section\",\n text: mrkdwn(text),\n })\n break\n }\n\n case \"link\": {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n type: \"section\",\n text: mrkdwn(`<${href}|${text}>`),\n })\n break\n }\n\n case \"image\": {\n const src = sanitizeImageSrc(p.src as string)\n // Slack only supports public URLs for images\n if (src.startsWith(\"http\")) {\n blocks.push({\n type: \"image\",\n image_url: src,\n alt_text: (p.alt as string) ?? \"Image\",\n ...(p.caption ? { title: plainText(p.caption as string) } : {}),\n })\n }\n break\n }\n\n case \"table\": {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n // Slack doesn't have native tables — render as formatted text\n const header = columns.map((c) => `*${c.header}*`).join(\" | \")\n const separator = columns.map(() => \"---\").join(\" | \")\n const body = rows.map((row) => row.map((cell) => String(cell ?? \"\")).join(\" | \")).join(\"\\n\")\n\n let text = `${header}\\n${separator}\\n${body}`\n if (p.caption) text = `_${p.caption}_\\n${text}`\n\n blocks.push({\n type: \"section\",\n text: mrkdwn(`\\`\\`\\`\\n${text}\\n\\`\\`\\``),\n })\n break\n }\n\n case \"list\": {\n const ordered = p.ordered as boolean | undefined\n const items = node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((item, i) => {\n const prefix = ordered ? `${i + 1}.` : \"•\"\n return `${prefix} ${getTextContent(item.children)}`\n })\n .join(\"\\n\")\n blocks.push({\n type: \"section\",\n text: mrkdwn(items),\n })\n break\n }\n\n case \"code\": {\n const text = getTextContent(node.children)\n const lang = (p.language as string) ?? \"\"\n blocks.push({\n type: \"section\",\n text: mrkdwn(`\\`\\`\\`${lang}\\n${text}\\n\\`\\`\\``),\n })\n break\n }\n\n case \"divider\":\n case \"page-break\":\n blocks.push({ type: \"divider\" })\n break\n\n case \"spacer\":\n // No equivalent in Slack — skip\n break\n\n case \"button\": {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n type: \"actions\",\n elements: [\n {\n type: \"button\",\n text: plainText(text),\n url: href,\n style: \"primary\",\n },\n ],\n })\n break\n }\n\n case \"quote\": {\n const text = getTextContent(node.children)\n blocks.push({\n type: \"section\",\n text: mrkdwn(`> ${text}`),\n })\n break\n }\n }\n\n return blocks\n}\n\nexport const slackRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const blocks = nodeToBlocks(node)\n return JSON.stringify({ blocks }, null, 2)\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slack-BI3EQwYm.js","names":[],"sources":["../src/renderers/slack.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'\n\n/**\n * Slack Block Kit renderer — outputs JSON that can be posted via Slack's API.\n * Maps document nodes to Slack blocks (section, header, divider, image, etc.).\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === 'string' ? c : getTextContent((c as DocNode).children)))\n .join('')\n}\n\ninterface SlackBlock {\n type: string\n [key: string]: unknown\n}\n\nfunction mrkdwn(text: string): { type: 'mrkdwn'; text: string } {\n return { type: 'mrkdwn', text }\n}\n\nfunction plainText(text: string): { type: 'plain_text'; text: string } {\n return { type: 'plain_text', text }\n}\n\nfunction nodeToBlocks(node: DocNode): SlackBlock[] {\n const p = node.props\n const blocks: SlackBlock[] = []\n\n switch (node.type) {\n case 'document':\n case 'page':\n case 'section':\n case 'row':\n case 'column':\n for (const child of node.children) {\n if (typeof child !== 'string') {\n blocks.push(...nodeToBlocks(child))\n }\n }\n break\n\n case 'heading':\n blocks.push({\n type: 'header',\n text: plainText(getTextContent(node.children)),\n })\n break\n\n case 'text': {\n let text = getTextContent(node.children)\n if (p.bold) text = `*${text}*`\n if (p.italic) text = `_${text}_`\n if (p.strikethrough) text = `~${text}~`\n blocks.push({\n type: 'section',\n text: mrkdwn(text),\n })\n break\n }\n\n case 'link': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n type: 'section',\n text: mrkdwn(`<${href}|${text}>`),\n })\n break\n }\n\n case 'image': {\n const src = sanitizeImageSrc(p.src as string)\n // Slack only supports public URLs for images\n if (src.startsWith('http')) {\n blocks.push({\n type: 'image',\n image_url: src,\n alt_text: (p.alt as string) ?? 'Image',\n ...(p.caption ? { title: plainText(p.caption as string) } : {}),\n })\n }\n break\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n // Slack doesn't have native tables — render as formatted text\n const header = columns.map((c) => `*${c.header}*`).join(' | ')\n const separator = columns.map(() => '---').join(' | ')\n const body = rows.map((row) => row.map((cell) => String(cell ?? '')).join(' | ')).join('\\n')\n\n let text = `${header}\\n${separator}\\n${body}`\n if (p.caption) text = `_${p.caption}_\\n${text}`\n\n blocks.push({\n type: 'section',\n text: mrkdwn(`\\`\\`\\`\\n${text}\\n\\`\\`\\``),\n })\n break\n }\n\n case 'list': {\n const ordered = p.ordered as boolean | undefined\n const items = node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((item, i) => {\n const prefix = ordered ? `${i + 1}.` : '•'\n return `${prefix} ${getTextContent(item.children)}`\n })\n .join('\\n')\n blocks.push({\n type: 'section',\n text: mrkdwn(items),\n })\n break\n }\n\n case 'code': {\n const text = getTextContent(node.children)\n const lang = (p.language as string) ?? ''\n blocks.push({\n type: 'section',\n text: mrkdwn(`\\`\\`\\`${lang}\\n${text}\\n\\`\\`\\``),\n })\n break\n }\n\n case 'divider':\n case 'page-break':\n blocks.push({ type: 'divider' })\n break\n\n case 'spacer':\n // No equivalent in Slack — skip\n break\n\n case 'button': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n blocks.push({\n type: 'actions',\n elements: [\n {\n type: 'button',\n text: plainText(text),\n url: href,\n style: 'primary',\n },\n ],\n })\n break\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n blocks.push({\n type: 'section',\n text: mrkdwn(`> ${text}`),\n })\n break\n }\n }\n\n return blocks\n}\n\nexport const slackRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const blocks = nodeToBlocks(node)\n return JSON.stringify({ blocks }, null, 2)\n },\n}\n"],"mappings":"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"}