@pyreon/document 0.7.0 → 0.8.0

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 (75) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/{confluence-Va8e7RxQ.js → confluence-Bd3ua1Ut.js} +6 -4
  3. package/lib/confluence-Bd3ua1Ut.js.map +1 -0
  4. package/lib/{csv-2c38ub-Y.js → csv-COrS4qdy.js} +1 -1
  5. package/lib/{csv-2c38ub-Y.js.map → csv-COrS4qdy.js.map} +1 -1
  6. package/lib/{discord-DAoUZqvE.js → discord-BLUnkEh9.js} +6 -4
  7. package/lib/discord-BLUnkEh9.js.map +1 -0
  8. package/lib/{docx-CorFwEH9.js → docx-BEBOihjl.js} +27 -26
  9. package/lib/docx-BEBOihjl.js.map +1 -0
  10. package/lib/{email-Bn_Brjdp.js → email-D0bbfWq4.js} +15 -13
  11. package/lib/email-D0bbfWq4.js.map +1 -0
  12. package/lib/{google-chat-B6I017I1.js → google-chat-CkKCBUWC.js} +6 -4
  13. package/lib/google-chat-CkKCBUWC.js.map +1 -0
  14. package/lib/{html-De_iS_f0.js → html-B5biprN2.js} +15 -13
  15. package/lib/html-B5biprN2.js.map +1 -0
  16. package/lib/index.js +44 -42
  17. package/lib/index.js.map +1 -1
  18. package/lib/{markdown-BYC_3C9i.js → markdown-CdtlFGC0.js} +6 -4
  19. package/lib/markdown-CdtlFGC0.js.map +1 -0
  20. package/lib/{notion-DHaQHO6P.js → notion-iG2C5bEY.js} +6 -4
  21. package/lib/notion-iG2C5bEY.js.map +1 -0
  22. package/lib/{pdf-CDPc5Itc.js → pdf-DIUQUEdj.js} +1 -1
  23. package/lib/{pdf-CDPc5Itc.js.map → pdf-DIUQUEdj.js.map} +1 -1
  24. package/lib/{pptx-DKQU6bjq.js → pptx-Dd33oL3_.js} +13 -11
  25. package/lib/pptx-Dd33oL3_.js.map +1 -0
  26. package/lib/sanitize-O_3j1mNJ.js +73 -0
  27. package/lib/sanitize-O_3j1mNJ.js.map +1 -0
  28. package/lib/{slack-CJRJgkag.js → slack-BI3EQwYm.js} +6 -4
  29. package/lib/slack-BI3EQwYm.js.map +1 -0
  30. package/lib/{svg-BM8biZmL.js → svg-BKxumy-p.js} +14 -12
  31. package/lib/svg-BKxumy-p.js.map +1 -0
  32. package/lib/{teams-S99tonRG.js → teams-Cwz9lce0.js} +6 -4
  33. package/lib/teams-Cwz9lce0.js.map +1 -0
  34. package/lib/{telegram-CbEO_2PN.js → telegram-gYFqyMXb.js} +5 -3
  35. package/lib/telegram-gYFqyMXb.js.map +1 -0
  36. package/lib/{text-B5U8ucRr.js → text-l1XNXBOC.js} +1 -1
  37. package/lib/{text-B5U8ucRr.js.map → text-l1XNXBOC.js.map} +1 -1
  38. package/lib/types/index.d.ts.map +1 -1
  39. package/lib/{whatsapp-DJ2D1jGG.js → whatsapp-CjSGoOKx.js} +5 -3
  40. package/lib/whatsapp-CjSGoOKx.js.map +1 -0
  41. package/lib/{xlsx-D47x-gZ5.js → xlsx-Bb5TWyXQ.js} +1 -1
  42. package/lib/{xlsx-D47x-gZ5.js.map → xlsx-Bb5TWyXQ.js.map} +1 -1
  43. package/package.json +3 -3
  44. package/src/builder.ts +6 -6
  45. package/src/download.ts +6 -0
  46. package/src/nodes.ts +5 -0
  47. package/src/renderers/confluence.ts +4 -3
  48. package/src/renderers/discord.ts +4 -3
  49. package/src/renderers/docx.ts +44 -32
  50. package/src/renderers/email.ts +15 -12
  51. package/src/renderers/google-chat.ts +4 -3
  52. package/src/renderers/html.ts +20 -12
  53. package/src/renderers/markdown.ts +4 -3
  54. package/src/renderers/notion.ts +4 -3
  55. package/src/renderers/pptx.ts +11 -10
  56. package/src/renderers/slack.ts +4 -3
  57. package/src/renderers/svg.ts +12 -11
  58. package/src/renderers/teams.ts +4 -3
  59. package/src/renderers/telegram.ts +3 -2
  60. package/src/renderers/whatsapp.ts +3 -2
  61. package/src/sanitize.ts +88 -0
  62. package/lib/confluence-Va8e7RxQ.js.map +0 -1
  63. package/lib/discord-DAoUZqvE.js.map +0 -1
  64. package/lib/docx-CorFwEH9.js.map +0 -1
  65. package/lib/email-Bn_Brjdp.js.map +0 -1
  66. package/lib/google-chat-B6I017I1.js.map +0 -1
  67. package/lib/html-De_iS_f0.js.map +0 -1
  68. package/lib/markdown-BYC_3C9i.js.map +0 -1
  69. package/lib/notion-DHaQHO6P.js.map +0 -1
  70. package/lib/pptx-DKQU6bjq.js.map +0 -1
  71. package/lib/slack-CJRJgkag.js.map +0 -1
  72. package/lib/svg-BM8biZmL.js.map +0 -1
  73. package/lib/teams-S99tonRG.js.map +0 -1
  74. package/lib/telegram-CbEO_2PN.js.map +0 -1
  75. package/lib/whatsapp-DJ2D1jGG.js.map +0 -1
@@ -1,3 +1,5 @@
1
+ import { n as sanitizeHref, r as sanitizeImageSrc } from "./sanitize-O_3j1mNJ.js";
2
+
1
3
  //#region src/renderers/confluence.ts
2
4
  /**
3
5
  * Atlassian Document Format (ADF) renderer — for Jira and Confluence.
@@ -56,7 +58,7 @@ function nodeToAdf(node) {
56
58
  break;
57
59
  }
58
60
  case "link": {
59
- const href = p.href;
61
+ const href = sanitizeHref(p.href);
60
62
  const text = getTextContent(node.children);
61
63
  result.push({
62
64
  type: "paragraph",
@@ -68,7 +70,7 @@ function nodeToAdf(node) {
68
70
  break;
69
71
  }
70
72
  case "image": {
71
- const src = p.src;
73
+ const src = sanitizeImageSrc(p.src);
72
74
  if (src.startsWith("http")) result.push({
73
75
  type: "mediaSingle",
74
76
  attrs: { layout: "center" },
@@ -153,7 +155,7 @@ function nodeToAdf(node) {
153
155
  });
154
156
  break;
155
157
  case "button": {
156
- const href = p.href;
158
+ const href = sanitizeHref(p.href);
157
159
  const text = getTextContent(node.children);
158
160
  result.push({
159
161
  type: "paragraph",
@@ -189,4 +191,4 @@ const confluenceRenderer = { async render(node, _options) {
189
191
 
190
192
  //#endregion
191
193
  export { confluenceRenderer };
192
- //# sourceMappingURL=confluence-Va8e7RxQ.js.map
194
+ //# sourceMappingURL=confluence-Bd3ua1Ut.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confluence-Bd3ua1Ut.js","names":[],"sources":["../src/renderers/confluence.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type {\n DocChild,\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\n/**\n * Atlassian Document Format (ADF) renderer — for Jira and Confluence.\n * ADF is the JSON format used by Atlassian's Document API.\n * Can be posted to Confluence pages, Jira issue descriptions, and comments.\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) =>\n typeof c === 'string' ? c : getTextContent((c as DocNode).children),\n )\n .join('')\n}\n\ninterface AdfNode {\n type: string\n content?: AdfNode[]\n text?: string\n marks?: { type: string; attrs?: Record<string, unknown> }[]\n attrs?: Record<string, unknown>\n}\n\nfunction textNode(text: string, marks?: AdfNode['marks']): AdfNode {\n return { type: 'text', text, ...(marks && marks.length > 0 ? { marks } : {}) }\n}\n\nfunction nodeToAdf(node: DocNode): AdfNode[] {\n const p = node.props\n const result: AdfNode[] = []\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 result.push(...nodeToAdf(child))\n }\n }\n break\n\n case 'heading': {\n const level = Math.min(Math.max((p.level as number) ?? 1, 1), 6)\n const text = getTextContent(node.children)\n result.push({\n type: 'heading',\n attrs: { level },\n content: [textNode(text, [{ type: 'strong' }])],\n })\n break\n }\n\n case 'text': {\n const text = getTextContent(node.children)\n const marks: AdfNode['marks'] = []\n if (p.bold) marks.push({ type: 'strong' })\n if (p.italic) marks.push({ type: 'em' })\n if (p.underline) marks.push({ type: 'underline' })\n if (p.strikethrough) marks.push({ type: 'strike' })\n if (p.color)\n marks.push({ type: 'textColor', attrs: { color: p.color as string } })\n result.push({\n type: 'paragraph',\n content: [textNode(text, marks)],\n })\n break\n }\n\n case 'link': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n result.push({\n type: 'paragraph',\n content: [textNode(text, [{ type: 'link', attrs: { href } }])],\n })\n break\n }\n\n case 'image': {\n const src = sanitizeImageSrc(p.src as string)\n if (src.startsWith('http')) {\n result.push({\n type: 'mediaSingle',\n attrs: { layout: 'center' },\n content: [\n {\n type: 'media',\n attrs: {\n type: 'external',\n url: src,\n width: (p.width as number) ?? undefined,\n height: (p.height as number) ?? undefined,\n },\n },\n ],\n })\n }\n break\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (p.rows ?? []) as (string | number)[][]\n\n const headerRow: AdfNode = {\n type: 'tableRow',\n content: columns.map((col) => ({\n type: 'tableHeader',\n content: [\n {\n type: 'paragraph',\n content: [textNode(col.header, [{ type: 'strong' }])],\n },\n ],\n })),\n }\n\n const dataRows = rows.map((row) => ({\n type: 'tableRow' as const,\n content: columns.map((_, i) => ({\n type: 'tableCell' as const,\n content: [\n {\n type: 'paragraph' as const,\n content: [textNode(String(row[i] ?? ''))],\n },\n ],\n })),\n }))\n\n result.push({\n type: 'table',\n attrs: { isNumberColumnEnabled: false, layout: 'default' },\n content: [headerRow, ...dataRows],\n })\n break\n }\n\n case 'list': {\n const ordered = p.ordered as boolean | undefined\n const type = ordered ? 'orderedList' : 'bulletList'\n const items = node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((item) => ({\n type: 'listItem' as const,\n content: [\n {\n type: 'paragraph' as const,\n content: [textNode(getTextContent(item.children))],\n },\n ],\n }))\n result.push({ type, content: items })\n break\n }\n\n case 'code': {\n const text = getTextContent(node.children)\n const lang = (p.language as string) ?? null\n result.push({\n type: 'codeBlock',\n attrs: { language: lang },\n content: [textNode(text)],\n })\n break\n }\n\n case 'divider':\n case 'page-break':\n result.push({ type: 'rule' })\n break\n\n case 'spacer':\n result.push({ type: 'paragraph', content: [] })\n break\n\n case 'button': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n result.push({\n type: 'paragraph',\n content: [\n textNode(text, [\n { type: 'link', attrs: { href } },\n { type: 'strong' },\n ]),\n ],\n })\n break\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n result.push({\n type: 'blockquote',\n content: [{ type: 'paragraph', content: [textNode(text)] }],\n })\n break\n }\n }\n\n return result\n}\n\nexport const confluenceRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const content = nodeToAdf(node)\n const adf = {\n version: 1,\n type: 'doc',\n content,\n }\n return JSON.stringify(adf, null, 2)\n },\n}\n"],"mappings":"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"}
@@ -29,4 +29,4 @@ const csvRenderer = { async render(node, _options) {
29
29
 
30
30
  //#endregion
31
31
  export { csvRenderer };
32
- //# sourceMappingURL=csv-2c38ub-Y.js.map
32
+ //# sourceMappingURL=csv-COrS4qdy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"csv-2c38ub-Y.js","names":[],"sources":["../src/renderers/csv.ts"],"sourcesContent":["import type {\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction escapeCsv(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value\n}\n\nfunction findTables(node: DocNode): DocNode[] {\n const tables: DocNode[] = []\n if (node.type === 'table') {\n tables.push(node)\n }\n for (const child of node.children) {\n if (typeof child !== 'string') {\n tables.push(...findTables(child))\n }\n }\n return tables\n}\n\nfunction tableToCsv(node: DocNode): string {\n const columns = ((node.props.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (node.props.rows ?? []) as (string | number)[][]\n\n const lines: string[] = []\n\n // Caption as comment\n if (node.props.caption) {\n lines.push(`# ${node.props.caption}`)\n }\n\n // Header\n lines.push(columns.map((c) => escapeCsv(c.header)).join(','))\n\n // Rows\n for (const row of rows) {\n lines.push(row.map((cell) => escapeCsv(String(cell ?? ''))).join(','))\n }\n\n return lines.join('\\n')\n}\n\nexport const csvRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const tables = findTables(node)\n\n if (tables.length === 0) {\n return '# No tables found in document\\n'\n }\n\n // If multiple tables, separate with blank lines\n return `${tables.map(tableToCsv).join('\\n\\n')}\\n`\n },\n}\n"],"mappings":";AAOA,SAAS,cAAc,KAAwC;AAC7D,QAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ,KAAK,GAAG;;AAGrD,SAAS,UAAU,OAAuB;AACxC,KAAI,MAAM,SAAS,IAAI,IAAI,MAAM,SAAS,KAAI,IAAI,MAAM,SAAS,KAAK,CACpE,QAAO,IAAI,MAAM,QAAQ,MAAM,OAAK,CAAC;AAEvC,QAAO;;AAGT,SAAS,WAAW,MAA0B;CAC5C,MAAM,SAAoB,EAAE;AAC5B,KAAI,KAAK,SAAS,QAChB,QAAO,KAAK,KAAK;AAEnB,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,OAAO,UAAU,SACnB,QAAO,KAAK,GAAG,WAAW,MAAM,CAAC;AAGrC,QAAO;;AAGT,SAAS,WAAW,MAAuB;CACzC,MAAM,WAAY,KAAK,MAAM,WAAW,EAAE,EAA+B,IACvE,cACD;CACD,MAAM,OAAQ,KAAK,MAAM,QAAQ,EAAE;CAEnC,MAAM,QAAkB,EAAE;AAG1B,KAAI,KAAK,MAAM,QACb,OAAM,KAAK,KAAK,KAAK,MAAM,UAAU;AAIvC,OAAM,KAAK,QAAQ,KAAK,MAAM,UAAU,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;AAG7D,MAAK,MAAM,OAAO,KAChB,OAAM,KAAK,IAAI,KAAK,SAAS,UAAU,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAGxE,QAAO,MAAM,KAAK,KAAK;;AAGzB,MAAa,cAAgC,EAC3C,MAAM,OAAO,MAAe,UAA2C;CACrE,MAAM,SAAS,WAAW,KAAK;AAE/B,KAAI,OAAO,WAAW,EACpB,QAAO;AAIT,QAAO,GAAG,OAAO,IAAI,WAAW,CAAC,KAAK,OAAO,CAAC;GAEjD"}
1
+ {"version":3,"file":"csv-COrS4qdy.js","names":[],"sources":["../src/renderers/csv.ts"],"sourcesContent":["import type {\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction escapeCsv(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value\n}\n\nfunction findTables(node: DocNode): DocNode[] {\n const tables: DocNode[] = []\n if (node.type === 'table') {\n tables.push(node)\n }\n for (const child of node.children) {\n if (typeof child !== 'string') {\n tables.push(...findTables(child))\n }\n }\n return tables\n}\n\nfunction tableToCsv(node: DocNode): string {\n const columns = ((node.props.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (node.props.rows ?? []) as (string | number)[][]\n\n const lines: string[] = []\n\n // Caption as comment\n if (node.props.caption) {\n lines.push(`# ${node.props.caption}`)\n }\n\n // Header\n lines.push(columns.map((c) => escapeCsv(c.header)).join(','))\n\n // Rows\n for (const row of rows) {\n lines.push(row.map((cell) => escapeCsv(String(cell ?? ''))).join(','))\n }\n\n return lines.join('\\n')\n}\n\nexport const csvRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const tables = findTables(node)\n\n if (tables.length === 0) {\n return '# No tables found in document\\n'\n }\n\n // If multiple tables, separate with blank lines\n return `${tables.map(tableToCsv).join('\\n\\n')}\\n`\n },\n}\n"],"mappings":";AAOA,SAAS,cAAc,KAAwC;AAC7D,QAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ,KAAK,GAAG;;AAGrD,SAAS,UAAU,OAAuB;AACxC,KAAI,MAAM,SAAS,IAAI,IAAI,MAAM,SAAS,KAAI,IAAI,MAAM,SAAS,KAAK,CACpE,QAAO,IAAI,MAAM,QAAQ,MAAM,OAAK,CAAC;AAEvC,QAAO;;AAGT,SAAS,WAAW,MAA0B;CAC5C,MAAM,SAAoB,EAAE;AAC5B,KAAI,KAAK,SAAS,QAChB,QAAO,KAAK,KAAK;AAEnB,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,OAAO,UAAU,SACnB,QAAO,KAAK,GAAG,WAAW,MAAM,CAAC;AAGrC,QAAO;;AAGT,SAAS,WAAW,MAAuB;CACzC,MAAM,WAAY,KAAK,MAAM,WAAW,EAAE,EAA+B,IACvE,cACD;CACD,MAAM,OAAQ,KAAK,MAAM,QAAQ,EAAE;CAEnC,MAAM,QAAkB,EAAE;AAG1B,KAAI,KAAK,MAAM,QACb,OAAM,KAAK,KAAK,KAAK,MAAM,UAAU;AAIvC,OAAM,KAAK,QAAQ,KAAK,MAAM,UAAU,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;AAG7D,MAAK,MAAM,OAAO,KAChB,OAAM,KAAK,IAAI,KAAK,SAAS,UAAU,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAGxE,QAAO,MAAM,KAAK,KAAK;;AAGzB,MAAa,cAAgC,EAC3C,MAAM,OAAO,MAAe,UAA2C;CACrE,MAAM,SAAS,WAAW,KAAK;AAE/B,KAAI,OAAO,WAAW,EACpB,QAAO;AAIT,QAAO,GAAG,OAAO,IAAI,WAAW,CAAC,KAAK,OAAO,CAAC;GAEjD"}
@@ -1,3 +1,5 @@
1
+ import { n as sanitizeHref, r as sanitizeImageSrc } from "./sanitize-O_3j1mNJ.js";
2
+
1
3
  //#region src/renderers/discord.ts
2
4
  /**
3
5
  * Discord renderer — outputs embed JSON for Discord webhooks/bots.
@@ -15,7 +17,7 @@ function extractMeta(node) {
15
17
  if ((node.props.level ?? 1) === 1) return { title: getTextContent(node.children) };
16
18
  }
17
19
  if (node.type === "image") {
18
- const src = node.props.src;
20
+ const src = sanitizeImageSrc(node.props.src);
19
21
  if (src.startsWith("http")) return { imageUrl: src };
20
22
  }
21
23
  for (const child of node.children) if (typeof child !== "string") {
@@ -55,7 +57,7 @@ function nodeToMarkdown(node, meta) {
55
57
  break;
56
58
  }
57
59
  case "link": {
58
- const href = p.href;
60
+ const href = sanitizeHref(p.href);
59
61
  const text = getTextContent(node.children);
60
62
  content += `[${text}](${href})\n\n`;
61
63
  break;
@@ -100,7 +102,7 @@ function nodeToMarkdown(node, meta) {
100
102
  content += "───────────\n\n";
101
103
  break;
102
104
  case "button": {
103
- const href = p.href;
105
+ const href = sanitizeHref(p.href);
104
106
  const text = getTextContent(node.children);
105
107
  content += `[**${text}**](${href})\n\n`;
106
108
  break;
@@ -131,4 +133,4 @@ const discordRenderer = { async render(node, _options) {
131
133
 
132
134
  //#endregion
133
135
  export { discordRenderer };
134
- //# sourceMappingURL=discord-DAoUZqvE.js.map
136
+ //# sourceMappingURL=discord-BLUnkEh9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-BLUnkEh9.js","names":[],"sources":["../src/renderers/discord.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type {\n DocChild,\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\n/**\n * Discord renderer — outputs embed JSON for Discord webhooks/bots.\n * Uses Discord's markdown subset and embed structure.\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) =>\n typeof c === 'string' ? c : getTextContent((c as DocNode).children),\n )\n .join('')\n}\n\ninterface DiscordField {\n name: string\n value: string\n inline?: boolean\n}\n\n/** Extract the first h1 title and first HTTP image from the tree. */\nfunction extractMeta(node: DocNode): { title?: string; imageUrl?: string } {\n if (node.type === 'heading') {\n const level = (node.props.level as number) ?? 1\n if (level === 1) return { title: getTextContent(node.children) }\n }\n if (node.type === 'image') {\n const src = sanitizeImageSrc(node.props.src as string)\n if (src.startsWith('http')) return { imageUrl: src }\n }\n for (const child of node.children) {\n if (typeof child !== 'string') {\n const result = extractMeta(child)\n if (result.title || result.imageUrl) return result\n }\n }\n return {}\n}\n\nfunction nodeToMarkdown(\n node: DocNode,\n meta: { title?: string },\n): { content: string; fields: DiscordField[] } {\n const p = node.props\n let content = ''\n const fields: DiscordField[] = []\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 const result = nodeToMarkdown(child, meta)\n content += result.content\n fields.push(...result.fields)\n }\n }\n break\n\n case 'heading': {\n const text = getTextContent(node.children)\n const level = (p.level as number) ?? 1\n // Skip the first h1 — it's used as embed title\n if (level === 1 && text === meta.title) {\n break\n }\n content += `**${text}**\\n\\n`\n break\n }\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 content += `${text}\\n\\n`\n break\n }\n\n case 'link': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n content += `[${text}](${href})\\n\\n`\n break\n }\n\n case 'image':\n // Image handled via extractMeta — embedded as embed.image\n break\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (p.rows ?? []) as (string | number)[][]\n\n // Use Discord embed fields for small tables\n if (columns.length <= 3 && rows.length <= 10) {\n for (const col of columns) {\n const colIdx = columns.indexOf(col)\n const values = rows.map((row) => String(row[colIdx] ?? '')).join('\\n')\n fields.push({\n name: col.header,\n value: values || '-',\n inline: true,\n })\n }\n } else {\n // Fallback to code block for large tables\n const header = columns.map((c) => c.header).join(' | ')\n const separator = columns.map(() => '---').join(' | ')\n const body = rows\n .map((row) => row.map((c) => String(c ?? '')).join(' | '))\n .join('\\n')\n content += `\\`\\`\\`\\n${header}\\n${separator}\\n${body}\\n\\`\\`\\`\\n\\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 content += `${items}\\n\\n`\n break\n }\n\n case 'code': {\n const lang = (p.language as string) ?? ''\n const text = getTextContent(node.children)\n content += `\\`\\`\\`${lang}\\n${text}\\n\\`\\`\\`\\n\\n`\n break\n }\n\n case 'divider':\n case 'page-break':\n content += '───────────\\n\\n'\n break\n\n case 'button': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n content += `[**${text}**](${href})\\n\\n`\n break\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n content += `> ${text}\\n\\n`\n break\n }\n }\n\n return { content, fields }\n}\n\nexport const discordRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const meta = extractMeta(node)\n const { content, fields } = nodeToMarkdown(node, meta)\n\n const embed: Record<string, unknown> = {\n title: meta.title ?? (node.props.title as string) ?? undefined,\n description: content.trim() || undefined,\n color: 0x4f46e5,\n }\n\n if (fields.length > 0) embed.fields = fields\n if (meta.imageUrl) embed.image = { url: meta.imageUrl }\n\n return JSON.stringify({ embeds: [embed] }, null, 2)\n },\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { a as sanitizeXmlColor, n as sanitizeHref } from "./sanitize-O_3j1mNJ.js";
2
+
1
3
  //#region src/renderers/docx.ts
2
4
  /**
3
5
  * DOCX renderer — lazy-loads the 'docx' npm package on first use.
@@ -106,7 +108,7 @@ function renderHeading(ctx, n) {
106
108
  children: [new docx.TextRun({
107
109
  text: getTextContent(n.children),
108
110
  bold: true,
109
- color: p.color?.replace("#", "") ?? "000000"
111
+ color: sanitizeXmlColor(p.color)
110
112
  })],
111
113
  alignment: alignmentMap(p.align)
112
114
  }));
@@ -117,12 +119,12 @@ function renderTextNode(ctx, n) {
117
119
  children.push(new docx.Paragraph({
118
120
  children: [new docx.TextRun({
119
121
  text: getTextContent(n.children),
120
- bold: p.bold,
121
- italics: p.italic,
122
- underline: p.underline ? {} : void 0,
123
- strike: p.strikethrough,
124
- size: p.size ? p.size * 2 : void 0,
125
- color: p.color?.replace("#", "") ?? "333333"
122
+ ...p.bold != null ? { bold: p.bold } : {},
123
+ ...p.italic != null ? { italics: p.italic } : {},
124
+ ...p.underline ? { underline: {} } : {},
125
+ ...p.strikethrough != null ? { strike: p.strikethrough } : {},
126
+ ...p.size != null ? { size: p.size * 2 } : {},
127
+ color: sanitizeXmlColor(p.color, "333333")
126
128
  })],
127
129
  alignment: alignmentMap(p.align),
128
130
  spacing: { after: 120 }
@@ -132,10 +134,10 @@ function renderLink(ctx, n) {
132
134
  const { docx, children } = ctx;
133
135
  const p = n.props;
134
136
  children.push(new docx.Paragraph({ children: [new docx.ExternalHyperlink({
135
- link: p.href,
137
+ link: sanitizeHref(p.href),
136
138
  children: [new docx.TextRun({
137
139
  text: getTextContent(n.children),
138
- color: p.color?.replace("#", "") ?? "4f46e5",
140
+ color: sanitizeXmlColor(p.color, "4f46e5"),
139
141
  underline: { type: docx.UnderlineType.SINGLE }
140
142
  })]
141
143
  })] }));
@@ -203,15 +205,15 @@ function renderDocxTable(ctx, n) {
203
205
  children: [new docx.TextRun({
204
206
  text: col.header,
205
207
  bold: true,
206
- color: hs?.color?.replace("#", "") ?? "000000"
208
+ color: sanitizeXmlColor(hs?.color)
207
209
  })],
208
210
  alignment: alignmentMap(col.align)
209
211
  })],
210
- shading: hs?.background ? {
211
- fill: hs.background.replace("#", ""),
212
+ ...hs?.background ? { shading: {
213
+ fill: sanitizeXmlColor(hs.background),
212
214
  type: docx.ShadingType.SOLID
213
- } : void 0,
214
- borders: cellBorders,
215
+ } } : {},
216
+ ...cellBorders != null ? { borders: cellBorders } : {},
215
217
  width: getColumnWidth(col.width)
216
218
  }))
217
219
  });
@@ -220,11 +222,11 @@ function renderDocxTable(ctx, n) {
220
222
  children: [new docx.TextRun({ text: String(row[colIdx] ?? "") })],
221
223
  alignment: alignmentMap(col.align)
222
224
  })],
223
- shading: p.striped && rowIdx % 2 === 1 ? {
225
+ ...p.striped && rowIdx % 2 === 1 ? { shading: {
224
226
  fill: "F9F9F9",
225
227
  type: docx.ShadingType.SOLID
226
- } : void 0,
227
- borders: cellBorders,
228
+ } } : {},
229
+ ...cellBorders != null ? { borders: cellBorders } : {},
228
230
  width: getColumnWidth(col.width)
229
231
  })) }));
230
232
  if (p.caption) children.push(new docx.Paragraph({
@@ -262,7 +264,7 @@ function renderButtonOrQuote(ctx, n) {
262
264
  const text = getTextContent(n.children);
263
265
  if (n.type === "button") children.push(new docx.Paragraph({
264
266
  children: [new docx.ExternalHyperlink({
265
- link: p.href,
267
+ link: sanitizeHref(p.href),
266
268
  children: [new docx.TextRun({
267
269
  text,
268
270
  bold: true,
@@ -282,7 +284,7 @@ function renderButtonOrQuote(ctx, n) {
282
284
  border: { left: {
283
285
  style: docx.BorderStyle.SINGLE,
284
286
  size: 6,
285
- color: p.borderColor?.replace("#", "") ?? "DDDDDD"
287
+ color: sanitizeXmlColor(p.borderColor, "DDDDDD")
286
288
  } },
287
289
  spacing: { after: 120 }
288
290
  }));
@@ -307,11 +309,10 @@ const docxRenderer = { async render(node, _options) {
307
309
  const textChildren = item.children.filter((c) => typeof c === "string" || c.type !== "list");
308
310
  children.push(new docx.Paragraph({
309
311
  children: [new docx.TextRun({ text: getTextContent(textChildren) })],
310
- numbering: ordered ? {
312
+ ...ordered ? { numbering: {
311
313
  reference: listRef,
312
314
  level
313
- } : void 0,
314
- bullet: ordered ? void 0 : { level }
315
+ } } : { bullet: { level } }
315
316
  }));
316
317
  if (nestedList) {
317
318
  const nestedOrdered = nestedList.props.ordered;
@@ -373,7 +374,7 @@ const docxRenderer = { async render(node, _options) {
373
374
  border: { bottom: {
374
375
  style: docx.BorderStyle.SINGLE,
375
376
  size: n.props.thickness ?? 1,
376
- color: n.props.color?.replace("#", "") ?? "DDDDDD"
377
+ color: sanitizeXmlColor(n.props.color, "DDDDDD")
377
378
  } },
378
379
  spacing: {
379
380
  before: 120,
@@ -436,8 +437,8 @@ const docxRenderer = { async render(node, _options) {
436
437
  numbering: numberingConfigs.length > 0 ? { config: numberingConfigs } : void 0,
437
438
  sections: [{
438
439
  properties: sectionProperties,
439
- headers: headerContent ? { default: new docx.Header({ children: headerContent }) } : void 0,
440
- footers: footerContent ? { default: new docx.Footer({ children: footerContent }) } : void 0,
440
+ ...headerContent ? { headers: { default: new docx.Header({ children: headerContent }) } } : {},
441
+ ...footerContent ? { footers: { default: new docx.Footer({ children: footerContent }) } } : {},
441
442
  children
442
443
  }]
443
444
  });
@@ -447,4 +448,4 @@ const docxRenderer = { async render(node, _options) {
447
448
 
448
449
  //#endregion
449
450
  export { docxRenderer };
450
- //# sourceMappingURL=docx-CorFwEH9.js.map
451
+ //# sourceMappingURL=docx-BEBOihjl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docx-BEBOihjl.js","names":[],"sources":["../src/renderers/docx.ts"],"sourcesContent":["import { sanitizeHref, sanitizeXmlColor } from '../sanitize'\nimport type {\n DocChild,\n DocNode,\n DocumentRenderer,\n PageOrientation,\n PageSize,\n RenderOptions,\n TableColumn,\n} from '../types'\n\n/**\n * DOCX renderer — lazy-loads the 'docx' npm package on first use.\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) =>\n typeof c === 'string' ? c : getTextContent((c as DocNode).children),\n )\n .join('')\n}\n\n/** Parse a data URL and return the base64 data and media type, or null for external URLs. */\nfunction parseDataUrl(src: string): { data: string; mime: string } | null {\n const match = src.match(/^data:(image\\/[^;]+);base64,(.+)$/)\n if (!match) return null\n return { mime: match[1]!, data: match[2]! }\n}\n\n/** Convert page size name to DOCX page dimensions in twips (1 inch = 1440 twips). */\nfunction getPageSize(\n size?: PageSize,\n orientation?: PageOrientation,\n): { width: number; height: number } | undefined {\n if (!size) return undefined\n const sizes: Record<string, { width: number; height: number }> = {\n A4: { width: 11906, height: 16838 },\n A3: { width: 16838, height: 23811 },\n A5: { width: 8391, height: 11906 },\n letter: { width: 12240, height: 15840 },\n legal: { width: 12240, height: 20160 },\n tabloid: { width: 15840, height: 24480 },\n }\n const dims = sizes[size]\n if (!dims) return undefined\n if (orientation === 'landscape') {\n return { width: dims.height, height: dims.width }\n }\n return dims\n}\n\n/** Convert margin prop to DOCX section margin (in twips, 1pt ~= 20 twips). */\nfunction getPageMargins(\n margin?: number | [number, number] | [number, number, number, number],\n): object | undefined {\n if (margin == null) return undefined\n if (typeof margin === 'number') {\n const twips = margin * 20\n return { top: twips, right: twips, bottom: twips, left: twips }\n }\n if (margin.length === 2) {\n return {\n top: margin[0] * 20,\n right: margin[1] * 20,\n bottom: margin[0] * 20,\n left: margin[1] * 20,\n }\n }\n return {\n top: margin[0] * 20,\n right: margin[1] * 20,\n bottom: margin[2] * 20,\n left: margin[3] * 20,\n }\n}\n\n/** Map percentage column width to DOCX table column width. */\nfunction getColumnWidth(\n width?: number | string,\n): { size: number; type: unknown } | undefined {\n if (width == null) return undefined\n if (typeof width === 'number') return undefined\n const match = width.match(/^(\\d+)%$/)\n if (!match) return undefined\n return { size: Number.parseInt(match[1]!, 10) * 100, type: 'pct' as unknown }\n}\n\n/** Shared context passed to per-node-type render helpers. */\ninterface DocxCtx {\n docx: typeof import('docx')\n children: unknown[]\n alignmentMap: (align?: string) => unknown\n processListItems: (\n n: DocNode,\n listRef: string,\n level: number,\n ordered: boolean,\n ) => void\n nextListId: () => string\n}\n\nfunction renderHeading(ctx: DocxCtx, n: DocNode): void {\n const { docx, children, alignmentMap } = ctx\n const p = n.props\n const level = (p.level as number) ?? 1\n const headingMap: Record<number, unknown> = {\n 1: docx.HeadingLevel.HEADING_1,\n 2: docx.HeadingLevel.HEADING_2,\n 3: docx.HeadingLevel.HEADING_3,\n 4: docx.HeadingLevel.HEADING_4,\n 5: docx.HeadingLevel.HEADING_5,\n 6: docx.HeadingLevel.HEADING_6,\n }\n children.push(\n new docx.Paragraph({\n heading: (headingMap[level] ?? docx.HeadingLevel.HEADING_1) as any,\n children: [\n new docx.TextRun({\n text: getTextContent(n.children),\n bold: true,\n color: sanitizeXmlColor(p.color as string),\n }),\n ],\n alignment: alignmentMap(p.align as string) as any,\n }),\n )\n}\n\nfunction renderTextNode(ctx: DocxCtx, n: DocNode): void {\n const { docx, children, alignmentMap } = ctx\n const p = n.props\n children.push(\n new docx.Paragraph({\n children: [\n new docx.TextRun({\n text: getTextContent(n.children),\n ...(p.bold != null ? { bold: p.bold as boolean } : {}),\n ...(p.italic != null ? { italics: p.italic as boolean } : {}),\n ...(p.underline ? { underline: {} } : {}),\n ...(p.strikethrough != null\n ? { strike: p.strikethrough as boolean }\n : {}),\n ...(p.size != null ? { size: (p.size as number) * 2 } : {}),\n color: sanitizeXmlColor(p.color as string, '333333'),\n }),\n ],\n alignment: alignmentMap(p.align as string) as any,\n spacing: { after: 120 },\n }),\n )\n}\n\nfunction renderLink(ctx: DocxCtx, n: DocNode): void {\n const { docx, children } = ctx\n const p = n.props\n children.push(\n new docx.Paragraph({\n children: [\n new docx.ExternalHyperlink({\n link: sanitizeHref(p.href as string),\n children: [\n new docx.TextRun({\n text: getTextContent(n.children),\n color: sanitizeXmlColor(p.color as string, '4f46e5'),\n underline: { type: docx.UnderlineType.SINGLE },\n }),\n ],\n }),\n ],\n }),\n )\n}\n\nfunction renderImage(ctx: DocxCtx, n: DocNode): void {\n const { docx, children, alignmentMap } = ctx\n const p = n.props\n const src = p.src as string\n const parsed = parseDataUrl(src)\n\n if (parsed) {\n const imgWidth = (p.width as number) ?? 300\n const imgHeight = (p.height as number) ?? 200\n children.push(\n new docx.Paragraph({\n children: [\n new docx.ImageRun({\n data: Buffer.from(parsed.data, 'base64'),\n transformation: { width: imgWidth, height: imgHeight },\n type: parsed.mime === 'image/png' ? 'png' : 'jpg',\n }),\n ],\n alignment: alignmentMap(p.align as string) as any,\n }),\n )\n if (p.caption) {\n children.push(\n new docx.Paragraph({\n children: [\n new docx.TextRun({\n text: p.caption as string,\n italics: true,\n size: 20,\n color: '666666',\n }),\n ],\n alignment: alignmentMap(p.align as string) as any,\n spacing: { after: 120 },\n }),\n )\n }\n } else {\n const alt = (p.alt as string) ?? 'Image'\n const caption = p.caption ? ` — ${p.caption}` : ''\n children.push(\n new docx.Paragraph({\n children: [\n new docx.TextRun({\n text: `[${alt}${caption}]`,\n italics: true,\n color: '999999',\n }),\n ],\n }),\n )\n }\n}\n\nfunction renderDocxTable(ctx: DocxCtx, n: DocNode): void {\n const { docx, children, alignmentMap } = ctx\n const p = n.props\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (p.rows ?? []) as (string | number)[][]\n const hs = p.headerStyle as\n | { background?: string; color?: string }\n | undefined\n const bordered = p.bordered as boolean | undefined\n const borderStyle = bordered\n ? { style: docx.BorderStyle.SINGLE, size: 1, color: 'DDDDDD' }\n : undefined\n const cellBorders = borderStyle\n ? {\n top: borderStyle,\n bottom: borderStyle,\n left: borderStyle,\n right: borderStyle,\n }\n : undefined\n\n const headerRow = new docx.TableRow({\n tableHeader: true,\n children: columns.map(\n (col) =>\n new docx.TableCell({\n children: [\n new docx.Paragraph({\n children: [\n new docx.TextRun({\n text: col.header,\n bold: true,\n color: sanitizeXmlColor(hs?.color),\n }),\n ],\n alignment: alignmentMap(col.align) as any,\n }),\n ],\n ...(hs?.background\n ? {\n shading: {\n fill: sanitizeXmlColor(hs.background),\n type: docx.ShadingType.SOLID,\n },\n }\n : {}),\n ...(cellBorders != null ? { borders: cellBorders } : {}),\n width: getColumnWidth(col.width as string | undefined) as any,\n }),\n ),\n })\n\n const dataRows = rows.map(\n (row, rowIdx) =>\n new docx.TableRow({\n children: columns.map(\n (col, colIdx) =>\n new docx.TableCell({\n children: [\n new docx.Paragraph({\n children: [\n new docx.TextRun({ text: String(row[colIdx] ?? '') }),\n ],\n alignment: alignmentMap(col.align) as any,\n }),\n ],\n ...(p.striped && rowIdx % 2 === 1\n ? { shading: { fill: 'F9F9F9', type: docx.ShadingType.SOLID } }\n : {}),\n ...(cellBorders != null ? { borders: cellBorders } : {}),\n width: getColumnWidth(col.width as string | undefined) as any,\n }),\n ),\n }),\n )\n\n if (p.caption) {\n children.push(\n new docx.Paragraph({\n children: [\n new docx.TextRun({\n text: p.caption as string,\n italics: true,\n size: 20,\n }),\n ],\n spacing: { after: 60 },\n }),\n )\n }\n\n children.push(\n new docx.Table({\n rows: [headerRow, ...dataRows],\n width: { size: 100, type: docx.WidthType.PERCENTAGE },\n }),\n )\n children.push(new docx.Paragraph({ text: '', spacing: { after: 120 } }))\n}\n\nfunction renderList(ctx: DocxCtx, n: DocNode): void {\n const { docx, children, processListItems, nextListId } = ctx\n const ordered = n.props.ordered as boolean | undefined\n const listRef = nextListId()\n processListItems(n, listRef, 0, ordered ?? false)\n children.push(new docx.Paragraph({ text: '', spacing: { after: 60 } }))\n}\n\nfunction renderButtonOrQuote(ctx: DocxCtx, n: DocNode): void {\n const { docx, children } = ctx\n const p = n.props\n const text = getTextContent(n.children)\n if (n.type === 'button') {\n children.push(\n new docx.Paragraph({\n children: [\n new docx.ExternalHyperlink({\n link: sanitizeHref(p.href as string),\n children: [\n new docx.TextRun({\n text,\n bold: true,\n color: '4F46E5',\n underline: { type: docx.UnderlineType.SINGLE },\n }),\n ],\n }),\n ],\n spacing: { after: 120 },\n }),\n )\n } else {\n children.push(\n new docx.Paragraph({\n children: [new docx.TextRun({ text, italics: true, color: '555555' })],\n indent: { left: 720 },\n border: {\n left: {\n style: docx.BorderStyle.SINGLE,\n size: 6,\n color: sanitizeXmlColor(p.borderColor as string, 'DDDDDD'),\n },\n },\n spacing: { after: 120 },\n }),\n )\n }\n}\n\nexport const docxRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {\n const docx = await import('docx')\n const children: unknown[] = []\n let listCounter = 0\n\n function alignmentMap(align?: string): unknown {\n if (!align) return undefined\n const map: Record<string, unknown> = {\n left: docx.AlignmentType.LEFT,\n center: docx.AlignmentType.CENTER,\n right: docx.AlignmentType.RIGHT,\n justify: docx.AlignmentType.JUSTIFIED,\n }\n return map[align]\n }\n\n function processListItems(\n n: DocNode,\n listRef: string,\n level: number,\n ordered: boolean,\n ): void {\n const items = n.children.filter(\n (c): c is DocNode => typeof c !== 'string',\n )\n for (const item of items) {\n const nestedList = item.children.find(\n (c): c is DocNode =>\n typeof c !== 'string' && (c as DocNode).type === 'list',\n )\n const textChildren = item.children.filter(\n (c) => typeof c === 'string' || (c as DocNode).type !== 'list',\n )\n children.push(\n new docx.Paragraph({\n children: [\n new docx.TextRun({ text: getTextContent(textChildren) }),\n ],\n ...(ordered\n ? { numbering: { reference: listRef, level } }\n : { bullet: { level } }),\n }),\n )\n if (nestedList) {\n const nestedOrdered = (nestedList as DocNode).props.ordered as\n | boolean\n | undefined\n processListItems(\n nestedList as DocNode,\n listRef,\n level + 1,\n nestedOrdered ?? false,\n )\n }\n }\n }\n\n const ctx: DocxCtx = {\n docx,\n children,\n alignmentMap,\n processListItems,\n nextListId: () => `list-${listCounter++}`,\n }\n\n function processNode(n: DocNode): void {\n switch (n.type) {\n case 'document':\n case 'page':\n case 'section':\n case 'row':\n case 'column':\n for (const child of n.children) {\n if (typeof child !== 'string') processNode(child)\n else children.push(new docx.Paragraph({ text: child }))\n }\n break\n case 'heading':\n renderHeading(ctx, n)\n break\n case 'text':\n renderTextNode(ctx, n)\n break\n case 'link':\n renderLink(ctx, n)\n break\n case 'image':\n renderImage(ctx, n)\n break\n case 'table':\n renderDocxTable(ctx, n)\n break\n case 'list':\n renderList(ctx, n)\n break\n case 'code':\n children.push(\n new docx.Paragraph({\n children: [\n new docx.TextRun({\n text: getTextContent(n.children),\n font: 'Courier New',\n size: 20,\n }),\n ],\n shading: { fill: 'F5F5F5', type: docx.ShadingType.SOLID },\n spacing: { after: 120 },\n }),\n )\n break\n case 'divider':\n children.push(\n new docx.Paragraph({\n border: {\n bottom: {\n style: docx.BorderStyle.SINGLE,\n size: (n.props.thickness as number | undefined) ?? 1,\n color: sanitizeXmlColor(n.props.color as string, 'DDDDDD'),\n },\n },\n spacing: { before: 120, after: 120 },\n }),\n )\n break\n case 'spacer':\n children.push(\n new docx.Paragraph({\n text: '',\n spacing: { after: (n.props.height as number) * 20 },\n }),\n )\n break\n case 'button':\n case 'quote':\n renderButtonOrQuote(ctx, n)\n break\n }\n }\n\n processNode(node)\n\n // Build numbering configs for all lists\n const numberingConfigs: unknown[] = []\n for (let i = 0; i < listCounter; i++) {\n numberingConfigs.push({\n reference: `list-${i}`,\n levels: Array.from({ length: 9 }, (_, level) => ({\n level,\n format: docx.LevelFormat.DECIMAL,\n text: `%${level + 1}.`,\n alignment: docx.AlignmentType.LEFT,\n style: {\n paragraph: { indent: { left: 720 * (level + 1), hanging: 360 } },\n },\n })),\n })\n }\n\n // Extract page properties from first page node\n const pageNode =\n node.type === 'document'\n ? (node.children.find(\n (c): c is DocNode =>\n typeof c !== 'string' && (c as DocNode).type === 'page',\n ) as DocNode | undefined)\n : node.type === 'page'\n ? node\n : undefined\n\n const pageProps = pageNode?.props ?? {}\n const pageDims = getPageSize(\n pageProps.size as PageSize | undefined,\n pageProps.orientation as PageOrientation | undefined,\n )\n const pageMargins = getPageMargins(\n pageProps.margin as\n | number\n | [number, number]\n | [number, number, number, number]\n | undefined,\n )\n\n function buildHeaderFooter(\n contentNode: DocNode | undefined,\n ): unknown[] | undefined {\n if (!contentNode) return undefined\n const text = getTextContent(contentNode.children)\n if (!text) return undefined\n return [\n new docx.Paragraph({\n children: [new docx.TextRun({ text, size: 18, color: '999999' })],\n alignment: docx.AlignmentType.CENTER,\n }),\n ]\n }\n\n const headerContent = buildHeaderFooter(\n pageProps.header as DocNode | undefined,\n )\n const footerContent = buildHeaderFooter(\n pageProps.footer as DocNode | undefined,\n )\n\n const sectionProperties: Record<string, unknown> = {}\n if (pageDims) {\n sectionProperties.page = { size: pageDims, margin: pageMargins }\n } else if (pageMargins) {\n sectionProperties.page = { margin: pageMargins }\n }\n\n const doc = new docx.Document({\n numbering: (numberingConfigs.length > 0\n ? { config: numberingConfigs }\n : undefined) as any,\n sections: [\n {\n properties: sectionProperties,\n ...(headerContent\n ? {\n headers: {\n default: new docx.Header({ children: headerContent as any }),\n },\n }\n : {}),\n ...(footerContent\n ? {\n footers: {\n default: new docx.Footer({ children: footerContent as any }),\n },\n }\n : {}),\n children: children as any,\n },\n ],\n })\n\n const buffer = await docx.Packer.toBuffer(doc)\n return new Uint8Array(buffer)\n },\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { n as sanitizeHref, r as sanitizeImageSrc, t as sanitizeColor } from "./sanitize-O_3j1mNJ.js";
2
+
1
3
  //#region src/renderers/email.ts
2
4
  /**
3
5
  * Email renderer — generates table-based HTML with inline styles
@@ -32,7 +34,7 @@ function renderNode(node) {
32
34
  case "document": return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${p.title ? `<title>${esc(p.title)}</title>` : ""}<!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]--></head><body style="margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,Helvetica,sans-serif">${p.subject ? `<div style="display:none;max-height:0;overflow:hidden">${esc(p.subject)}</div>` : ""}<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#f4f4f4"><tr><td align="center" style="padding:20px 0"><table width="600" cellpadding="0" cellspacing="0" border="0" style="background-color:#ffffff;max-width:600px;width:100%"><tr><td>${renderChildren(node.children)}</td></tr></table></td></tr></table></body></html>`;
33
35
  case "page": return renderChildren(node.children);
34
36
  case "section": {
35
- const bg = p.background ? `background-color:${p.background};` : "";
37
+ const bg = p.background ? `background-color:${sanitizeColor(p.background)};` : "";
36
38
  const pad = p.padding ? `padding:${typeof p.padding === "number" ? `${p.padding}px` : Array.isArray(p.padding) ? p.padding.map((v) => `${v}px`).join(" ") : "0"}` : "padding:0";
37
39
  const radius = p.borderRadius ? `border-radius:${p.borderRadius}px;` : "";
38
40
  if (p.direction === "row") {
@@ -57,13 +59,13 @@ function renderNode(node) {
57
59
  4: 18,
58
60
  5: 16,
59
61
  6: 14
60
- }[level] ?? 24}px;color:${p.color ?? "#000000"};text-align:${p.align ?? "left"};font-weight:bold;line-height:1.3">${renderChildren(node.children)}</h${level}>`;
62
+ }[level] ?? 24}px;color:${sanitizeColor(p.color ?? "#000000")};text-align:${p.align ?? "left"};font-weight:bold;line-height:1.3">${renderChildren(node.children)}</h${level}>`;
61
63
  }
62
- case "text": return `<p style="margin:0 0 12px 0;font-size:${p.size ?? 14}px;color:${p.color ?? "#333333"};font-weight:${p.bold ? "bold" : "normal"};font-style:${p.italic ? "italic" : "normal"};text-decoration:${p.underline ? "underline" : p.strikethrough ? "line-through" : "none"};text-align:${p.align ?? "left"};line-height:${p.lineHeight ?? 1.5}">${renderChildren(node.children)}</p>`;
63
- case "link": return `<a href="${esc(p.href)}" style="color:${p.color ?? "#4f46e5"};text-decoration:underline" target="_blank">${renderChildren(node.children)}</a>`;
64
+ case "text": return `<p style="margin:0 0 12px 0;font-size:${p.size ?? 14}px;color:${sanitizeColor(p.color ?? "#333333")};font-weight:${p.bold ? "bold" : "normal"};font-style:${p.italic ? "italic" : "normal"};text-decoration:${p.underline ? "underline" : p.strikethrough ? "line-through" : "none"};text-align:${p.align ?? "left"};line-height:${p.lineHeight ?? 1.5}">${renderChildren(node.children)}</p>`;
65
+ case "link": return `<a href="${esc(sanitizeHref(p.href))}" style="color:${sanitizeColor(p.color ?? "#4f46e5")};text-decoration:underline" target="_blank">${renderChildren(node.children)}</a>`;
64
66
  case "image": {
65
67
  const align = p.align ?? "left";
66
- const img = `<img src="${esc(p.src)}"${p.width ? ` width="${p.width}"` : ""}${p.height ? ` height="${p.height}"` : ""} alt="${esc(p.alt ?? "")}" style="display:block;outline:none;border:none;text-decoration:none${p.width ? `;max-width:${p.width}px` : ""}" />`;
68
+ const img = `<img src="${esc(sanitizeImageSrc(p.src))}"${p.width ? ` width="${p.width}"` : ""}${p.height ? ` height="${p.height}"` : ""} alt="${esc(p.alt ?? "")}" style="display:block;outline:none;border:none;text-decoration:none${p.width ? `;max-width:${p.width}px` : ""}" />`;
67
69
  if (p.caption) return `<table cellpadding="0" cellspacing="0" border="0"${align === "center" ? " align=\"center\"" : ""}><tr><td>${img}</td></tr><tr><td style="font-size:12px;color:#666;padding-top:4px;text-align:center">${esc(p.caption)}</td></tr></table>`;
68
70
  if (align === "center") return `<div style="text-align:center">${img}</div>`;
69
71
  if (align === "right") return `<div style="text-align:right">${img}</div>`;
@@ -78,8 +80,8 @@ function renderNode(node) {
78
80
  if (p.caption) html += `<caption style="font-size:12px;color:#666;padding:8px;text-align:left">${esc(p.caption)}</caption>`;
79
81
  html += "<tr>";
80
82
  for (const col of columns) {
81
- const bg = hs?.background ? `background-color:${hs.background};` : "background-color:#f5f5f5;";
82
- const color = hs?.color ? `color:${hs.color};` : "";
83
+ const bg = hs?.background ? `background-color:${sanitizeColor(hs.background)};` : "background-color:#f5f5f5;";
84
+ const color = hs?.color ? `color:${sanitizeColor(hs.color)};` : "";
83
85
  const align = col.align ? `text-align:${col.align};` : "";
84
86
  const width = col.width ? `width:${typeof col.width === "number" ? `${col.width}px` : col.width};` : "";
85
87
  html += `<th style="${bg}${color}font-weight:bold;${align}${width}padding:8px;border-bottom:2px solid #ddd">${esc(col.header)}</th>`;
@@ -105,20 +107,20 @@ function renderNode(node) {
105
107
  case "list-item": return `<li style="margin:0 0 4px 0;font-size:14px;color:#333">${renderChildren(node.children)}</li>`;
106
108
  case "code": return `<pre style="background-color:#f5f5f5;padding:12px;border-radius:4px;font-family:Courier New,monospace;font-size:13px;color:#333;overflow-x:auto;margin:0 0 12px 0"><code>${esc(renderChildren(node.children))}</code></pre>`;
107
109
  case "divider": {
108
- const color = p.color ?? "#dddddd";
110
+ const color = sanitizeColor(p.color ?? "#dddddd");
109
111
  return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:16px 0"><tr><td style="border-top:${p.thickness ?? 1}px solid ${color};font-size:0;line-height:0">&nbsp;</td></tr></table>`;
110
112
  }
111
113
  case "page-break": return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0"><tr><td style="border-top:2px solid #dddddd;font-size:0;line-height:0">&nbsp;</td></tr></table>`;
112
114
  case "spacer": return `<div style="height:${p.height}px;line-height:${p.height}px;font-size:0">&nbsp;</div>`;
113
115
  case "button": {
114
- const bg = p.background ?? "#4f46e5";
115
- const color = p.color ?? "#ffffff";
116
+ const bg = sanitizeColor(p.background ?? "#4f46e5");
117
+ const color = sanitizeColor(p.color ?? "#ffffff");
116
118
  const radius = p.borderRadius ?? 4;
117
- const href = esc(p.href);
119
+ const href = esc(sanitizeHref(p.href));
118
120
  const text = renderChildren(node.children);
119
121
  return `<div style="text-align:${p.align ?? "left"};margin:12px 0"><!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${href}" style="height:44px;v-text-anchor:middle;width:200px" arcsize="10%" strokecolor="${bg}" fillcolor="${bg}"><w:anchorlock/><center style="color:${color};font-family:Arial,sans-serif;font-size:14px;font-weight:bold">${text}</center></v:roundrect><![endif]--><!--[if !mso]><!--><a href="${href}" style="display:inline-block;background-color:${bg};color:${color};padding:12px 24px;border-radius:${radius}px;text-decoration:none;font-weight:bold;font-size:14px;font-family:Arial,sans-serif" target="_blank">${text}</a><!--<![endif]--></div>`;
120
122
  }
121
- case "quote": return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:12px 0"><tr><td style="border-left:4px solid ${p.borderColor ?? "#dddddd"};padding:12px 20px;color:#555555;font-style:italic">${renderChildren(node.children)}</td></tr></table>`;
123
+ case "quote": return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:12px 0"><tr><td style="border-left:4px solid ${sanitizeColor(p.borderColor ?? "#dddddd")};padding:12px 20px;color:#555555;font-style:italic">${renderChildren(node.children)}</td></tr></table>`;
122
124
  default: return renderChildren(node.children);
123
125
  }
124
126
  }
@@ -128,4 +130,4 @@ const emailRenderer = { async render(node, _options) {
128
130
 
129
131
  //#endregion
130
132
  export { emailRenderer };
131
- //# sourceMappingURL=email-Bn_Brjdp.js.map
133
+ //# sourceMappingURL=email-D0bbfWq4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-D0bbfWq4.js","names":[],"sources":["../src/renderers/email.ts"],"sourcesContent":["import { sanitizeColor, sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type {\n DocChild,\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\n/**\n * Email renderer — generates table-based HTML with inline styles\n * that works across Gmail, Outlook, Apple Mail, and other email clients.\n *\n * Key constraints:\n * - No CSS classes (Gmail strips <style> tags)\n * - Table-based layout (no flexbox/grid)\n * - All styles inline\n * - VML buttons for Outlook\n * - Max width 600px for compatibility\n */\n\nfunction esc(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\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 esc(child)\n return renderNode(child)\n}\n\nfunction renderChildren(children: DocChild[]): string {\n return children.map(renderChild).join('')\n}\n\nfunction wrapInTable(content: string, style = ''): string {\n return `<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"${style ? ` style=\"${style}\"` : ''}><tr><td>${content}</td></tr></table>`\n}\n\nfunction renderNode(node: DocNode): string {\n const p = node.props\n\n switch (node.type) {\n case 'document': {\n const title = p.title ? `<title>${esc(p.title as string)}</title>` : ''\n const preview = p.subject\n ? `<div style=\"display:none;max-height:0;overflow:hidden\">${esc(p.subject as string)}</div>`\n : ''\n return `<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">${title}<!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]--></head><body style=\"margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,Helvetica,sans-serif\">${preview}<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background-color:#f4f4f4\"><tr><td align=\"center\" style=\"padding:20px 0\"><table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background-color:#ffffff;max-width:600px;width:100%\"><tr><td>${renderChildren(node.children)}</td></tr></table></td></tr></table></body></html>`\n }\n\n case 'page':\n // In email, pages are just content sections\n return renderChildren(node.children)\n\n case 'section': {\n const bg = p.background\n ? `background-color:${sanitizeColor(p.background as string)};`\n : ''\n const pad = p.padding\n ? `padding:${typeof p.padding === 'number' ? `${p.padding}px` : Array.isArray(p.padding) ? (p.padding as number[]).map((v) => `${v}px`).join(' ') : '0'}`\n : 'padding:0'\n const radius = p.borderRadius ? `border-radius:${p.borderRadius}px;` : ''\n\n if (p.direction === 'row') {\n // Row layout via nested table\n const children = node.children.filter(\n (c): c is DocNode => typeof c !== 'string',\n )\n const colWidth = Math.floor(100 / Math.max(children.length, 1))\n return `<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"${bg}${radius}${pad}\"><tr>${children.map((child) => `<td width=\"${colWidth}%\" valign=\"top\" style=\"padding:${(p.gap as number | undefined) ? `0 ${(p.gap as number) / 2}px` : '0'}\">${renderNode(child)}</td>`).join('')}</tr></table>`\n }\n\n return wrapInTable(renderChildren(node.children), `${bg}${radius}${pad}`)\n }\n\n case 'row': {\n const children = node.children.filter(\n (c): c is DocNode => typeof c !== 'string',\n )\n const gap = (p.gap as number) ?? 0\n return `<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>${children.map((child) => `<td valign=\"top\" style=\"padding:0 ${gap / 2}px\">${renderNode(child)}</td>`).join('')}</tr></table>`\n }\n\n case 'column':\n return renderChildren(node.children)\n\n case 'heading': {\n const level = (p.level as number) ?? 1\n const sizes: Record<number, number> = {\n 1: 28,\n 2: 24,\n 3: 20,\n 4: 18,\n 5: 16,\n 6: 14,\n }\n const size = sizes[level] ?? 24\n const color = sanitizeColor((p.color as string) ?? '#000000')\n const align = (p.align as string) ?? 'left'\n return `<h${level} style=\"margin:0 0 12px 0;font-size:${size}px;color:${color};text-align:${align};font-weight:bold;line-height:1.3\">${renderChildren(node.children)}</h${level}>`\n }\n\n case 'text': {\n const size = (p.size as number) ?? 14\n const color = sanitizeColor((p.color as string) ?? '#333333')\n const weight = p.bold ? 'bold' : 'normal'\n const style = p.italic ? 'italic' : 'normal'\n const decoration = p.underline\n ? 'underline'\n : p.strikethrough\n ? 'line-through'\n : 'none'\n const align = (p.align as string) ?? 'left'\n const lh = (p.lineHeight as number) ?? 1.5\n return `<p style=\"margin:0 0 12px 0;font-size:${size}px;color:${color};font-weight:${weight};font-style:${style};text-decoration:${decoration};text-align:${align};line-height:${lh}\">${renderChildren(node.children)}</p>`\n }\n\n case 'link':\n return `<a href=\"${esc(sanitizeHref(p.href as string))}\" style=\"color:${sanitizeColor((p.color as string) ?? '#4f46e5')};text-decoration:underline\" target=\"_blank\">${renderChildren(node.children)}</a>`\n\n case 'image': {\n const align = (p.align as string) ?? 'left'\n const img = `<img src=\"${esc(sanitizeImageSrc(p.src as string))}\"${p.width ? ` width=\"${p.width}\"` : ''}${p.height ? ` height=\"${p.height}\"` : ''} alt=\"${esc((p.alt as string) ?? '')}\" style=\"display:block;outline:none;border:none;text-decoration:none${p.width ? `;max-width:${p.width}px` : ''}\" />`\n if (p.caption) {\n return `<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\"${align === 'center' ? ' align=\"center\"' : ''}><tr><td>${img}</td></tr><tr><td style=\"font-size:12px;color:#666;padding-top:4px;text-align:center\">${esc(p.caption as string)}</td></tr></table>`\n }\n if (align === 'center')\n return `<div style=\"text-align:center\">${img}</div>`\n if (align === 'right') return `<div style=\"text-align:right\">${img}</div>`\n return img\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (p.rows ?? []) as (string | number)[][]\n const hs = p.headerStyle as\n | { background?: string; color?: string; bold?: boolean }\n | undefined\n const striped = p.striped as boolean | undefined\n\n let html =\n '<table width=\"100%\" cellpadding=\"8\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse\">'\n if (p.caption)\n html += `<caption style=\"font-size:12px;color:#666;padding:8px;text-align:left\">${esc(p.caption as string)}</caption>`\n\n html += '<tr>'\n for (const col of columns) {\n const bg = hs?.background\n ? `background-color:${sanitizeColor(hs.background)};`\n : 'background-color:#f5f5f5;'\n const color = hs?.color ? `color:${sanitizeColor(hs.color)};` : ''\n const align = col.align ? `text-align:${col.align};` : ''\n const width = col.width\n ? `width:${typeof col.width === 'number' ? `${col.width}px` : col.width};`\n : ''\n html += `<th style=\"${bg}${color}font-weight:bold;${align}${width}padding:8px;border-bottom:2px solid #ddd\">${esc(col.header)}</th>`\n }\n html += '</tr>'\n\n for (let i = 0; i < rows.length; i++) {\n const bg = striped && i % 2 === 1 ? 'background-color:#f9f9f9;' : ''\n html += '<tr>'\n for (let j = 0; j < columns.length; j++) {\n const col = columns[j]\n const align = col?.align ? `text-align:${col.align};` : ''\n html += `<td style=\"${bg}${align}padding:8px;border-bottom:1px solid #eee\">${esc(String(rows[i]?.[j] ?? ''))}</td>`\n }\n html += '</tr>'\n }\n html += '</table>'\n return html\n }\n\n case 'list': {\n const tag = p.ordered ? 'ol' : 'ul'\n return `<${tag} style=\"margin:0 0 12px 0;padding-left:24px\">${renderChildren(node.children)}</${tag}>`\n }\n\n case 'list-item':\n return `<li style=\"margin:0 0 4px 0;font-size:14px;color:#333\">${renderChildren(node.children)}</li>`\n\n case 'code':\n return `<pre style=\"background-color:#f5f5f5;padding:12px;border-radius:4px;font-family:Courier New,monospace;font-size:13px;color:#333;overflow-x:auto;margin:0 0 12px 0\"><code>${esc(renderChildren(node.children))}</code></pre>`\n\n case 'divider': {\n const color = sanitizeColor((p.color as string) ?? '#dddddd')\n const thickness = (p.thickness as number) ?? 1\n return `<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin:16px 0\"><tr><td style=\"border-top:${thickness}px solid ${color};font-size:0;line-height:0\">&nbsp;</td></tr></table>`\n }\n\n case 'page-break':\n return `<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin:24px 0\"><tr><td style=\"border-top:2px solid #dddddd;font-size:0;line-height:0\">&nbsp;</td></tr></table>`\n\n case 'spacer':\n return `<div style=\"height:${p.height}px;line-height:${p.height}px;font-size:0\">&nbsp;</div>`\n\n case 'button': {\n const bg = sanitizeColor((p.background as string) ?? '#4f46e5')\n const color = sanitizeColor((p.color as string) ?? '#ffffff')\n const radius = (p.borderRadius as number) ?? 4\n const href = esc(sanitizeHref(p.href as string))\n const text = renderChildren(node.children)\n const align = (p.align as string) ?? 'left'\n\n // Bulletproof button — works in Outlook via VML, CSS everywhere else\n return `<div style=\"text-align:${align};margin:12px 0\"><!--[if mso]><v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" href=\"${href}\" style=\"height:44px;v-text-anchor:middle;width:200px\" arcsize=\"10%\" strokecolor=\"${bg}\" fillcolor=\"${bg}\"><w:anchorlock/><center style=\"color:${color};font-family:Arial,sans-serif;font-size:14px;font-weight:bold\">${text}</center></v:roundrect><![endif]--><!--[if !mso]><!--><a href=\"${href}\" style=\"display:inline-block;background-color:${bg};color:${color};padding:12px 24px;border-radius:${radius}px;text-decoration:none;font-weight:bold;font-size:14px;font-family:Arial,sans-serif\" target=\"_blank\">${text}</a><!--<![endif]--></div>`\n }\n\n case 'quote': {\n const borderColor = sanitizeColor((p.borderColor as string) ?? '#dddddd')\n return `<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin:12px 0\"><tr><td style=\"border-left:4px solid ${borderColor};padding:12px 20px;color:#555555;font-style:italic\">${renderChildren(node.children)}</td></tr></table>`\n }\n\n default:\n return renderChildren(node.children)\n }\n}\n\nexport const emailRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n return renderNode(node)\n },\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { n as sanitizeHref, r as sanitizeImageSrc } from "./sanitize-O_3j1mNJ.js";
2
+
1
3
  //#region src/renderers/google-chat.ts
2
4
  /**
3
5
  * Google Chat renderer — outputs Card V2 JSON for Google Chat API.
@@ -38,13 +40,13 @@ function nodeToWidgets(node) {
38
40
  break;
39
41
  }
40
42
  case "link": {
41
- const href = p.href;
43
+ const href = sanitizeHref(p.href);
42
44
  const text = getTextContent(node.children);
43
45
  widgets.push({ textParagraph: { text: `<a href="${href}">${text}</a>` } });
44
46
  break;
45
47
  }
46
48
  case "image": {
47
- const src = p.src;
49
+ const src = sanitizeImageSrc(p.src);
48
50
  if (src.startsWith("http")) widgets.push({ image: {
49
51
  imageUrl: src,
50
52
  altText: p.alt ?? "Image"
@@ -78,7 +80,7 @@ function nodeToWidgets(node) {
78
80
  break;
79
81
  case "spacer": break;
80
82
  case "button": {
81
- const href = p.href;
83
+ const href = sanitizeHref(p.href);
82
84
  const text = getTextContent(node.children);
83
85
  widgets.push({ buttonList: { buttons: [{
84
86
  text,
@@ -122,4 +124,4 @@ const googleChatRenderer = { async render(node, _options) {
122
124
 
123
125
  //#endregion
124
126
  export { googleChatRenderer };
125
- //# sourceMappingURL=google-chat-B6I017I1.js.map
127
+ //# sourceMappingURL=google-chat-CkKCBUWC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-chat-CkKCBUWC.js","names":[],"sources":["../src/renderers/google-chat.ts"],"sourcesContent":["import { sanitizeHref, sanitizeImageSrc } from '../sanitize'\nimport type {\n DocChild,\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\n/**\n * Google Chat renderer — outputs Card V2 JSON for Google Chat API.\n * Cards can be sent via webhooks, Chat API, or Apps Script.\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) =>\n typeof c === 'string' ? c : getTextContent((c as DocNode).children),\n )\n .join('')\n}\n\ninterface CardWidget {\n [key: string]: unknown\n}\n\nfunction nodeToWidgets(node: DocNode): CardWidget[] {\n const p = node.props\n const widgets: CardWidget[] = []\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 widgets.push(...nodeToWidgets(child))\n }\n }\n break\n\n case 'heading': {\n const text = getTextContent(node.children)\n widgets.push({\n decoratedText: {\n topLabel: '',\n text: `<b>${text}</b>`,\n wrapText: true,\n },\n })\n break\n }\n\n case 'text': {\n let text = getTextContent(node.children)\n if (p.bold) text = `<b>${text}</b>`\n if (p.italic) text = `<i>${text}</i>`\n if (p.strikethrough) text = `<s>${text}</s>`\n widgets.push({\n textParagraph: { text },\n })\n break\n }\n\n case 'link': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n widgets.push({\n textParagraph: { text: `<a href=\"${href}\">${text}</a>` },\n })\n break\n }\n\n case 'image': {\n const src = sanitizeImageSrc(p.src as string)\n if (src.startsWith('http')) {\n widgets.push({\n image: {\n imageUrl: src,\n altText: (p.alt as string) ?? 'Image',\n },\n })\n }\n break\n }\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (p.rows ?? []) as (string | number)[][]\n\n // Google Chat Cards don't have native tables — use grid or formatted text\n const header = columns.map((c) => `<b>${c.header}</b>`).join(' | ')\n const body = rows\n .map((row) => row.map((c) => String(c ?? '')).join(' | '))\n .join('\\n')\n\n widgets.push({\n textParagraph: { text: `${header}\\n${body}` },\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 widgets.push({\n textParagraph: { text: items },\n })\n break\n }\n\n case 'code': {\n const text = getTextContent(node.children)\n widgets.push({\n textParagraph: {\n text: `<font color=\"#333333\"><code>${text}</code></font>`,\n },\n })\n break\n }\n\n case 'divider':\n case 'page-break':\n widgets.push({ divider: {} })\n break\n\n case 'spacer':\n // No direct equivalent — skip\n break\n\n case 'button': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n widgets.push({\n buttonList: {\n buttons: [\n {\n text,\n onClick: { openLink: { url: href } },\n color: {\n red: 0.31,\n green: 0.27,\n blue: 0.89,\n alpha: 1,\n },\n },\n ],\n },\n })\n break\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n widgets.push({\n textParagraph: { text: `<i>\"${text}\"</i>` },\n })\n break\n }\n }\n\n return widgets\n}\n\nexport const googleChatRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n const widgets = nodeToWidgets(node)\n\n // Extract title from first heading or document title\n let title = (node.props.title as string) ?? ''\n if (!title) {\n const firstHeading = node.children.find(\n (c): c is DocNode => typeof c !== 'string' && c.type === 'heading',\n )\n if (firstHeading) title = getTextContent(firstHeading.children)\n }\n\n const card = {\n cardsV2: [\n {\n cardId: 'document',\n card: {\n header: title\n ? { title, subtitle: (node.props.subject as string) ?? undefined }\n : undefined,\n sections: [\n {\n widgets,\n },\n ],\n },\n },\n ],\n }\n\n return JSON.stringify(card, null, 2)\n },\n}\n"],"mappings":"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"}