@pyreon/document 0.0.1 → 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.
- package/lib/analysis/index.js.html +1 -1
- package/lib/{confluence-Va8e7RxQ.js → confluence-Bd3ua1Ut.js} +6 -4
- package/lib/confluence-Bd3ua1Ut.js.map +1 -0
- package/lib/{csv-2c38ub-Y.js → csv-COrS4qdy.js} +1 -1
- package/lib/{csv-2c38ub-Y.js.map → csv-COrS4qdy.js.map} +1 -1
- package/lib/{discord-DAoUZqvE.js → discord-BLUnkEh9.js} +6 -4
- package/lib/discord-BLUnkEh9.js.map +1 -0
- package/lib/{docx-CorFwEH9.js → docx-BEBOihjl.js} +27 -26
- package/lib/docx-BEBOihjl.js.map +1 -0
- package/lib/{email-Bn_Brjdp.js → email-D0bbfWq4.js} +15 -13
- package/lib/email-D0bbfWq4.js.map +1 -0
- package/lib/{google-chat-B6I017I1.js → google-chat-CkKCBUWC.js} +6 -4
- package/lib/google-chat-CkKCBUWC.js.map +1 -0
- package/lib/{html-De_iS_f0.js → html-B5biprN2.js} +15 -13
- package/lib/html-B5biprN2.js.map +1 -0
- package/lib/index.js +44 -42
- package/lib/index.js.map +1 -1
- package/lib/{markdown-BYC_3C9i.js → markdown-CdtlFGC0.js} +6 -4
- package/lib/markdown-CdtlFGC0.js.map +1 -0
- package/lib/{notion-DHaQHO6P.js → notion-iG2C5bEY.js} +6 -4
- package/lib/notion-iG2C5bEY.js.map +1 -0
- package/lib/{pdf-CDPc5Itc.js → pdf-DIUQUEdj.js} +1 -1
- package/lib/{pdf-CDPc5Itc.js.map → pdf-DIUQUEdj.js.map} +1 -1
- package/lib/{pptx-DKQU6bjq.js → pptx-Dd33oL3_.js} +13 -11
- package/lib/pptx-Dd33oL3_.js.map +1 -0
- package/lib/sanitize-O_3j1mNJ.js +73 -0
- package/lib/sanitize-O_3j1mNJ.js.map +1 -0
- package/lib/{slack-CJRJgkag.js → slack-BI3EQwYm.js} +6 -4
- package/lib/slack-BI3EQwYm.js.map +1 -0
- package/lib/{svg-BM8biZmL.js → svg-BKxumy-p.js} +14 -12
- package/lib/svg-BKxumy-p.js.map +1 -0
- package/lib/{teams-S99tonRG.js → teams-Cwz9lce0.js} +6 -4
- package/lib/teams-Cwz9lce0.js.map +1 -0
- package/lib/{telegram-CbEO_2PN.js → telegram-gYFqyMXb.js} +5 -3
- package/lib/telegram-gYFqyMXb.js.map +1 -0
- package/lib/{text-B5U8ucRr.js → text-l1XNXBOC.js} +1 -1
- package/lib/{text-B5U8ucRr.js.map → text-l1XNXBOC.js.map} +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/{whatsapp-DJ2D1jGG.js → whatsapp-CjSGoOKx.js} +5 -3
- package/lib/whatsapp-CjSGoOKx.js.map +1 -0
- package/lib/{xlsx-D47x-gZ5.js → xlsx-Bb5TWyXQ.js} +1 -1
- package/lib/{xlsx-D47x-gZ5.js.map → xlsx-Bb5TWyXQ.js.map} +1 -1
- package/package.json +3 -13
- package/src/builder.ts +6 -6
- package/src/download.ts +6 -0
- package/src/nodes.ts +5 -0
- package/src/renderers/confluence.ts +4 -3
- package/src/renderers/discord.ts +4 -3
- package/src/renderers/docx.ts +44 -32
- package/src/renderers/email.ts +15 -12
- package/src/renderers/google-chat.ts +4 -3
- package/src/renderers/html.ts +20 -12
- package/src/renderers/markdown.ts +4 -3
- package/src/renderers/notion.ts +4 -3
- package/src/renderers/pptx.ts +11 -10
- package/src/renderers/slack.ts +4 -3
- package/src/renderers/svg.ts +12 -11
- package/src/renderers/teams.ts +4 -3
- package/src/renderers/telegram.ts +3 -2
- package/src/renderers/whatsapp.ts +3 -2
- package/src/sanitize.ts +88 -0
- package/lib/confluence-Va8e7RxQ.js.map +0 -1
- package/lib/discord-DAoUZqvE.js.map +0 -1
- package/lib/docx-CorFwEH9.js.map +0 -1
- package/lib/email-Bn_Brjdp.js.map +0 -1
- package/lib/google-chat-B6I017I1.js.map +0 -1
- package/lib/html-De_iS_f0.js.map +0 -1
- package/lib/markdown-BYC_3C9i.js.map +0 -1
- package/lib/notion-DHaQHO6P.js.map +0 -1
- package/lib/pptx-DKQU6bjq.js.map +0 -1
- package/lib/slack-CJRJgkag.js.map +0 -1
- package/lib/svg-BM8biZmL.js.map +0 -1
- package/lib/teams-S99tonRG.js.map +0 -1
- package/lib/telegram-CbEO_2PN.js.map +0 -1
- 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-
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csv-
|
|
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-
|
|
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
|
|
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
|
-
|
|
123
|
-
strike: p.strikethrough,
|
|
124
|
-
|
|
125
|
-
color: p.color
|
|
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
|
|
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
|
|
208
|
+
color: sanitizeXmlColor(hs?.color)
|
|
207
209
|
})],
|
|
208
210
|
alignment: alignmentMap(col.align)
|
|
209
211
|
})],
|
|
210
|
-
|
|
211
|
-
fill: hs.background
|
|
212
|
+
...hs?.background ? { shading: {
|
|
213
|
+
fill: sanitizeXmlColor(hs.background),
|
|
212
214
|
type: docx.ShadingType.SOLID
|
|
213
|
-
} :
|
|
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
|
-
|
|
225
|
+
...p.striped && rowIdx % 2 === 1 ? { shading: {
|
|
224
226
|
fill: "F9F9F9",
|
|
225
227
|
type: docx.ShadingType.SOLID
|
|
226
|
-
} :
|
|
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
|
|
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
|
-
|
|
312
|
+
...ordered ? { numbering: {
|
|
311
313
|
reference: listRef,
|
|
312
314
|
level
|
|
313
|
-
} :
|
|
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
|
|
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
|
-
|
|
440
|
-
|
|
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-
|
|
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"> </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"> </td></tr></table>`;
|
|
112
114
|
case "spacer": return `<div style="height:${p.height}px;line-height:${p.height}px;font-size:0"> </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-
|
|
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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\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\"> </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\"> </td></tr></table>`\n\n case 'spacer':\n return `<div style=\"height:${p.height}px;line-height:${p.height}px;font-size:0\"> </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-
|
|
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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"}
|