@pyreon/document 0.7.0 → 0.9.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 (76) hide show
  1. package/README.md +68 -0
  2. package/lib/analysis/index.js.html +1 -1
  3. package/lib/{confluence-Va8e7RxQ.js → confluence-Bd3ua1Ut.js} +6 -4
  4. package/lib/confluence-Bd3ua1Ut.js.map +1 -0
  5. package/lib/{csv-2c38ub-Y.js → csv-COrS4qdy.js} +1 -1
  6. package/lib/{csv-2c38ub-Y.js.map → csv-COrS4qdy.js.map} +1 -1
  7. package/lib/{discord-DAoUZqvE.js → discord-BLUnkEh9.js} +6 -4
  8. package/lib/discord-BLUnkEh9.js.map +1 -0
  9. package/lib/{docx-CorFwEH9.js → docx-BEBOihjl.js} +27 -26
  10. package/lib/docx-BEBOihjl.js.map +1 -0
  11. package/lib/{email-Bn_Brjdp.js → email-D0bbfWq4.js} +15 -13
  12. package/lib/email-D0bbfWq4.js.map +1 -0
  13. package/lib/{google-chat-B6I017I1.js → google-chat-CkKCBUWC.js} +6 -4
  14. package/lib/google-chat-CkKCBUWC.js.map +1 -0
  15. package/lib/{html-De_iS_f0.js → html-B5biprN2.js} +15 -13
  16. package/lib/html-B5biprN2.js.map +1 -0
  17. package/lib/index.js +44 -42
  18. package/lib/index.js.map +1 -1
  19. package/lib/{markdown-BYC_3C9i.js → markdown-CdtlFGC0.js} +6 -4
  20. package/lib/markdown-CdtlFGC0.js.map +1 -0
  21. package/lib/{notion-DHaQHO6P.js → notion-iG2C5bEY.js} +6 -4
  22. package/lib/notion-iG2C5bEY.js.map +1 -0
  23. package/lib/{pdf-CDPc5Itc.js → pdf-DIUQUEdj.js} +1 -1
  24. package/lib/{pdf-CDPc5Itc.js.map → pdf-DIUQUEdj.js.map} +1 -1
  25. package/lib/{pptx-DKQU6bjq.js → pptx-Dd33oL3_.js} +13 -11
  26. package/lib/pptx-Dd33oL3_.js.map +1 -0
  27. package/lib/sanitize-O_3j1mNJ.js +73 -0
  28. package/lib/sanitize-O_3j1mNJ.js.map +1 -0
  29. package/lib/{slack-CJRJgkag.js → slack-BI3EQwYm.js} +6 -4
  30. package/lib/slack-BI3EQwYm.js.map +1 -0
  31. package/lib/{svg-BM8biZmL.js → svg-BKxumy-p.js} +14 -12
  32. package/lib/svg-BKxumy-p.js.map +1 -0
  33. package/lib/{teams-S99tonRG.js → teams-Cwz9lce0.js} +6 -4
  34. package/lib/teams-Cwz9lce0.js.map +1 -0
  35. package/lib/{telegram-CbEO_2PN.js → telegram-gYFqyMXb.js} +5 -3
  36. package/lib/telegram-gYFqyMXb.js.map +1 -0
  37. package/lib/{text-B5U8ucRr.js → text-l1XNXBOC.js} +1 -1
  38. package/lib/{text-B5U8ucRr.js.map → text-l1XNXBOC.js.map} +1 -1
  39. package/lib/types/index.d.ts.map +1 -1
  40. package/lib/{whatsapp-DJ2D1jGG.js → whatsapp-CjSGoOKx.js} +5 -3
  41. package/lib/whatsapp-CjSGoOKx.js.map +1 -0
  42. package/lib/{xlsx-D47x-gZ5.js → xlsx-Bb5TWyXQ.js} +1 -1
  43. package/lib/{xlsx-D47x-gZ5.js.map → xlsx-Bb5TWyXQ.js.map} +1 -1
  44. package/package.json +3 -3
  45. package/src/builder.ts +6 -6
  46. package/src/download.ts +6 -0
  47. package/src/nodes.ts +5 -0
  48. package/src/renderers/confluence.ts +4 -3
  49. package/src/renderers/discord.ts +4 -3
  50. package/src/renderers/docx.ts +44 -32
  51. package/src/renderers/email.ts +15 -12
  52. package/src/renderers/google-chat.ts +4 -3
  53. package/src/renderers/html.ts +20 -12
  54. package/src/renderers/markdown.ts +4 -3
  55. package/src/renderers/notion.ts +4 -3
  56. package/src/renderers/pptx.ts +11 -10
  57. package/src/renderers/slack.ts +4 -3
  58. package/src/renderers/svg.ts +12 -11
  59. package/src/renderers/teams.ts +4 -3
  60. package/src/renderers/telegram.ts +3 -2
  61. package/src/renderers/whatsapp.ts +3 -2
  62. package/src/sanitize.ts +88 -0
  63. package/lib/confluence-Va8e7RxQ.js.map +0 -1
  64. package/lib/discord-DAoUZqvE.js.map +0 -1
  65. package/lib/docx-CorFwEH9.js.map +0 -1
  66. package/lib/email-Bn_Brjdp.js.map +0 -1
  67. package/lib/google-chat-B6I017I1.js.map +0 -1
  68. package/lib/html-De_iS_f0.js.map +0 -1
  69. package/lib/markdown-BYC_3C9i.js.map +0 -1
  70. package/lib/notion-DHaQHO6P.js.map +0 -1
  71. package/lib/pptx-DKQU6bjq.js.map +0 -1
  72. package/lib/slack-CJRJgkag.js.map +0 -1
  73. package/lib/svg-BM8biZmL.js.map +0 -1
  74. package/lib/teams-S99tonRG.js.map +0 -1
  75. package/lib/telegram-CbEO_2PN.js.map +0 -1
  76. package/lib/whatsapp-DJ2D1jGG.js.map +0 -1
@@ -1,3 +1,9 @@
1
+ import {
2
+ sanitizeColor,
3
+ sanitizeHref,
4
+ sanitizeImageSrc,
5
+ sanitizeStyle,
6
+ } from '../sanitize'
1
7
  import type {
2
8
  DocChild,
3
9
  DocNode,
@@ -71,7 +77,7 @@ function renderNode(node: DocNode): string {
71
77
  flexDirection: dir === 'row' ? 'row' : undefined,
72
78
  gap: p.gap as number | undefined,
73
79
  padding: padStr(p.padding as PageMargin),
74
- background: p.background as string | undefined,
80
+ background: sanitizeColor(p.background as string | undefined),
75
81
  borderRadius: p.borderRadius as number | undefined,
76
82
  })}>${renderChildren(node.children)}</div>`
77
83
  }
@@ -85,13 +91,13 @@ function renderNode(node: DocNode): string {
85
91
  case 'heading': {
86
92
  const level = (p.level as number) ?? 1
87
93
  const tag = `h${Math.min(Math.max(level, 1), 6)}`
88
- return `<${tag}${styleStr({ color: p.color as string | undefined, textAlign: p.align as string | undefined })}>${renderChildren(node.children)}</${tag}>`
94
+ return `<${tag}${styleStr({ color: sanitizeColor(p.color as string | undefined), textAlign: p.align as string | undefined })}>${renderChildren(node.children)}</${tag}>`
89
95
  }
90
96
 
91
97
  case 'text': {
92
98
  return `<p${styleStr({
93
99
  fontSize: p.size as number | undefined,
94
- color: p.color as string | undefined,
100
+ color: sanitizeColor(p.color as string | undefined),
95
101
  fontWeight: p.bold ? 'bold' : undefined,
96
102
  fontStyle: p.italic ? 'italic' : undefined,
97
103
  textDecoration: p.underline
@@ -105,7 +111,7 @@ function renderNode(node: DocNode): string {
105
111
  }
106
112
 
107
113
  case 'link':
108
- return `<a href="${escapeHtml(p.href as string)}"${styleStr({ color: p.color as string | undefined })}>${renderChildren(node.children)}</a>`
114
+ return `<a href="${escapeHtml(sanitizeHref(p.href as string))}"${styleStr({ color: sanitizeColor(p.color as string | undefined) })}>${renderChildren(node.children)}</a>`
109
115
 
110
116
  case 'image': {
111
117
  const alignStyle =
@@ -114,7 +120,7 @@ function renderNode(node: DocNode): string {
114
120
  : p.align === 'right'
115
121
  ? 'display:block;margin-left:auto'
116
122
  : ''
117
- const img = `<img src="${escapeHtml(p.src as string)}"${p.width ? ` width="${p.width}"` : ''}${p.height ? ` height="${p.height}"` : ''}${p.alt ? ` alt="${escapeHtml(p.alt as string)}"` : ''}${alignStyle ? ` style="${alignStyle}"` : ''} />`
123
+ const img = `<img src="${escapeHtml(sanitizeImageSrc(p.src as string))}"${p.width ? ` width="${p.width}"` : ''}${p.height ? ` height="${p.height}"` : ''}${p.alt ? ` alt="${escapeHtml(p.alt as string)}"` : ''}${alignStyle ? ` style="${sanitizeStyle(alignStyle)}"` : ''} />`
118
124
  if (p.caption) {
119
125
  return `<figure${p.align === 'center' ? ' style="text-align:center"' : ''}>${img}<figcaption>${escapeHtml(p.caption as string)}</figcaption></figure>`
120
126
  }
@@ -142,8 +148,10 @@ function renderNode(node: DocNode): string {
142
148
  html += '<thead><tr>'
143
149
  for (const col of columns) {
144
150
  const cellBorder = bordered ? 'border:1px solid #ddd;' : ''
145
- const bgStyle = hs?.background ? `background:${hs.background};` : ''
146
- const colorStyle = hs?.color ? `color:${hs.color};` : ''
151
+ const bgStyle = hs?.background
152
+ ? `background:${sanitizeColor(hs.background)};`
153
+ : ''
154
+ const colorStyle = hs?.color ? `color:${sanitizeColor(hs.color)};` : ''
147
155
  const fontStyle = hs?.bold !== false ? 'font-weight:bold;' : ''
148
156
  const alignStyle = col.align ? `text-align:${col.align};` : ''
149
157
  const widthStyle = col.width
@@ -182,7 +190,7 @@ function renderNode(node: DocNode): string {
182
190
  return `<pre style="background:#f5f5f5;padding:12px;border-radius:4px;overflow-x:auto"><code>${escapeHtml(renderChildren(node.children))}</code></pre>`
183
191
 
184
192
  case 'divider': {
185
- const color = (p.color as string) ?? '#ddd'
193
+ const color = sanitizeColor((p.color as string) ?? '#ddd')
186
194
  const thickness = (p.thickness as number) ?? 1
187
195
  return `<hr style="border:none;border-top:${thickness}px solid ${color};margin:16px 0" />`
188
196
  }
@@ -194,16 +202,16 @@ function renderNode(node: DocNode): string {
194
202
  return `<div style="height:${p.height}px"></div>`
195
203
 
196
204
  case 'button': {
197
- const bg = (p.background as string) ?? '#4f46e5'
198
- const color = (p.color as string) ?? '#fff'
205
+ const bg = sanitizeColor((p.background as string) ?? '#4f46e5')
206
+ const color = sanitizeColor((p.color as string) ?? '#fff')
199
207
  const radius = (p.borderRadius as number) ?? 4
200
208
  const pad = padStr((p.padding ?? [12, 24]) as [number, number])
201
209
  const align = (p.align as string) ?? 'left'
202
- return `<div style="text-align:${align}"><a href="${escapeHtml(p.href as string)}" style="display:inline-block;background:${bg};color:${color};padding:${pad};border-radius:${radius}px;text-decoration:none;font-weight:bold">${renderChildren(node.children)}</a></div>`
210
+ return `<div style="text-align:${align}"><a href="${escapeHtml(sanitizeHref(p.href as string))}" style="display:inline-block;background:${bg};color:${color};padding:${pad};border-radius:${radius}px;text-decoration:none;font-weight:bold">${renderChildren(node.children)}</a></div>`
203
211
  }
204
212
 
205
213
  case 'quote': {
206
- const borderColor = (p.borderColor as string) ?? '#ddd'
214
+ const borderColor = sanitizeColor((p.borderColor as string) ?? '#ddd')
207
215
  return `<blockquote style="margin:0;padding:12px 20px;border-left:4px solid ${borderColor};color:#555">${renderChildren(node.children)}</blockquote>`
208
216
  }
209
217
 
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref, sanitizeImageSrc } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -55,11 +56,11 @@ function renderNode(node: DocNode): string {
55
56
  }
56
57
 
57
58
  case 'link':
58
- return `[${renderInline(node.children)}](${p.href})`
59
+ return `[${renderInline(node.children)}](${sanitizeHref(p.href as string)})`
59
60
 
60
61
  case 'image': {
61
62
  const alt = (p.alt as string) ?? ''
62
- let md = `![${alt}](${p.src})`
63
+ let md = `![${alt}](${sanitizeImageSrc(p.src as string)})`
63
64
  if (p.caption) md += `\n*${p.caption}*`
64
65
  return `${md}\n\n`
65
66
  }
@@ -127,7 +128,7 @@ function renderNode(node: DocNode): string {
127
128
  return '\n'
128
129
 
129
130
  case 'button':
130
- return `[${renderInline(node.children)}](${p.href})\n\n`
131
+ return `[${renderInline(node.children)}](${sanitizeHref(p.href as string)})\n\n`
131
132
 
132
133
  case 'quote':
133
134
  return `> ${renderInline(node.children)}\n\n`
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref, sanitizeImageSrc } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -105,7 +106,7 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
105
106
  }
106
107
 
107
108
  case 'link': {
108
- const href = p.href as string
109
+ const href = sanitizeHref(p.href as string)
109
110
  const text = getTextContent(node.children)
110
111
  blocks.push({
111
112
  object: 'block',
@@ -120,7 +121,7 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
120
121
  }
121
122
 
122
123
  case 'image': {
123
- const src = p.src as string
124
+ const src = sanitizeImageSrc(p.src as string)
124
125
  if (src.startsWith('http')) {
125
126
  blocks.push({
126
127
  object: 'block',
@@ -224,7 +225,7 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
224
225
  break
225
226
 
226
227
  case 'button': {
227
- const href = p.href as string
228
+ const href = sanitizeHref(p.href as string)
228
229
  const text = getTextContent(node.children)
229
230
  blocks.push({
230
231
  object: 'block',
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref, sanitizeImageSrc, sanitizeXmlColor } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -90,7 +91,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
90
91
  h: 0.6,
91
92
  fontSize,
92
93
  bold: true,
93
- color: ((p.color as string) ?? '#000000').replace('#', ''),
94
+ color: sanitizeXmlColor((p.color as string) ?? '#000000'),
94
95
  align: (p.align as string) ?? 'left',
95
96
  })
96
97
  ctx.y += 0.7
@@ -109,7 +110,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
109
110
  italic: p.italic ?? false,
110
111
  underline: p.underline ? { style: 'sng' } : undefined,
111
112
  strike: p.strikethrough ? 'sngStrike' : undefined,
112
- color: ((p.color as string) ?? '#333333').replace('#', ''),
113
+ color: sanitizeXmlColor((p.color as string) ?? '#333333'),
113
114
  align: (p.align as string) ?? 'left',
114
115
  })
115
116
  ctx.y += 0.5
@@ -117,7 +118,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
117
118
  }
118
119
 
119
120
  case 'image': {
120
- const src = p.src as string
121
+ const src = sanitizeImageSrc(p.src as string)
121
122
  const w = Math.min(((p.width as number) ?? 400) / 96, CONTENT_WIDTH)
122
123
  const h = ((p.height as number) ?? 300) / 96
123
124
 
@@ -148,8 +149,8 @@ function processNode(node: DocNode, ctx: SlideContext): void {
148
149
  text: col.header,
149
150
  options: {
150
151
  bold: true,
151
- fill: { color: (hs?.background ?? '#f5f5f5').replace('#', '') },
152
- color: (hs?.color ?? '#000000').replace('#', ''),
152
+ fill: { color: sanitizeXmlColor(hs?.background ?? '#f5f5f5') },
153
+ color: sanitizeXmlColor(hs?.color ?? '#000000'),
153
154
  align: col.align ?? 'left',
154
155
  fontSize: 12,
155
156
  },
@@ -243,7 +244,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
243
244
  fontSize: 13,
244
245
  color: '4F46E5',
245
246
  underline: { style: 'sng' },
246
- hyperlink: { url: p.href as string },
247
+ hyperlink: { url: sanitizeHref(p.href as string) },
247
248
  })
248
249
  ctx.y += 0.5
249
250
  break
@@ -257,12 +258,12 @@ function processNode(node: DocNode, ctx: SlideContext): void {
257
258
  h: 0.5,
258
259
  fontSize: 14,
259
260
  bold: true,
260
- color: ((p.color as string) ?? '#ffffff').replace('#', ''),
261
+ color: sanitizeXmlColor((p.color as string) ?? '#ffffff'),
261
262
  fill: {
262
- color: ((p.background as string) ?? '#4f46e5').replace('#', ''),
263
+ color: sanitizeXmlColor((p.background as string) ?? '#4f46e5'),
263
264
  },
264
265
  align: 'center',
265
- hyperlink: { url: p.href as string },
266
+ hyperlink: { url: sanitizeHref(p.href as string) },
266
267
  })
267
268
  ctx.y += 0.6
268
269
  break
@@ -280,7 +281,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
280
281
  y: ctx.y,
281
282
  w: CONTENT_WIDTH,
282
283
  h: 0.02,
283
- fill: { color: ((p.color as string) ?? '#DDDDDD').replace('#', '') },
284
+ fill: { color: sanitizeXmlColor((p.color as string) ?? '#DDDDDD') },
284
285
  })
285
286
  ctx.y += 0.2
286
287
  break
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref, sanitizeImageSrc } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -73,7 +74,7 @@ function nodeToBlocks(node: DocNode): SlackBlock[] {
73
74
  }
74
75
 
75
76
  case 'link': {
76
- const href = p.href as string
77
+ const href = sanitizeHref(p.href as string)
77
78
  const text = getTextContent(node.children)
78
79
  blocks.push({
79
80
  type: 'section',
@@ -83,7 +84,7 @@ function nodeToBlocks(node: DocNode): SlackBlock[] {
83
84
  }
84
85
 
85
86
  case 'image': {
86
- const src = p.src as string
87
+ const src = sanitizeImageSrc(p.src as string)
87
88
  // Slack only supports public URLs for images
88
89
  if (src.startsWith('http')) {
89
90
  blocks.push({
@@ -155,7 +156,7 @@ function nodeToBlocks(node: DocNode): SlackBlock[] {
155
156
  break
156
157
 
157
158
  case 'button': {
158
- const href = p.href as string
159
+ const href = sanitizeHref(p.href as string)
159
160
  const text = getTextContent(node.children)
160
161
  blocks.push({
161
162
  type: 'actions',
@@ -1,3 +1,4 @@
1
+ import { sanitizeColor, sanitizeHref, sanitizeImageSrc } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -67,7 +68,7 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
67
68
  6: 14,
68
69
  }
69
70
  const size = sizes[level] ?? 24
70
- const color = (p.color as string) ?? '#000000'
71
+ const color = sanitizeColor((p.color as string) ?? '#000000')
71
72
  const text = escapeXml(getTextContent(node.children))
72
73
  ctx.y += size + 8
73
74
  svg += `<text x="${ctx.padding}" y="${ctx.y}" font-size="${size}" font-weight="bold" fill="${color}" font-family="system-ui, -apple-system, sans-serif">${text}</text>`
@@ -77,7 +78,7 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
77
78
 
78
79
  case 'text': {
79
80
  const size = (p.size as number) ?? 14
80
- const color = (p.color as string) ?? '#333333'
81
+ const color = sanitizeColor((p.color as string) ?? '#333333')
81
82
  const weight = p.bold ? 'bold' : 'normal'
82
83
  const style = p.italic ? 'italic' : 'normal'
83
84
  const text = escapeXml(getTextContent(node.children))
@@ -88,9 +89,9 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
88
89
  }
89
90
 
90
91
  case 'link': {
91
- const href = p.href as string
92
+ const href = sanitizeHref(p.href as string)
92
93
  const text = escapeXml(getTextContent(node.children))
93
- const color = (p.color as string) ?? '#4f46e5'
94
+ const color = sanitizeColor((p.color as string) ?? '#4f46e5')
94
95
  ctx.y += 18
95
96
  svg += `<a href="${escapeXml(href)}"><text x="${ctx.padding}" y="${ctx.y}" font-size="14" fill="${color}" text-decoration="underline" font-family="system-ui, -apple-system, sans-serif">${text}</text></a>`
96
97
  ctx.y += 10
@@ -100,7 +101,7 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
100
101
  case 'image': {
101
102
  const width = (p.width as number) ?? Math.min(contentWidth, 400)
102
103
  const height = (p.height as number) ?? 200
103
- const src = p.src as string
104
+ const src = sanitizeImageSrc(p.src as string)
104
105
 
105
106
  if (src.startsWith('data:') || src.startsWith('http')) {
106
107
  svg += `<image x="${ctx.padding}" y="${ctx.y}" width="${width}" height="${height}" href="${escapeXml(src)}" />`
@@ -131,8 +132,8 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
131
132
 
132
133
  const colWidth = contentWidth / columns.length
133
134
  const rowHeight = 28
134
- const headerBg = hs?.background ?? '#f5f5f5'
135
- const headerColor = hs?.color ?? '#000000'
135
+ const headerBg = sanitizeColor(hs?.background ?? '#f5f5f5')
136
+ const headerColor = sanitizeColor(hs?.color ?? '#000000')
136
137
 
137
138
  // Header
138
139
  svg += `<rect x="${ctx.padding}" y="${ctx.y}" width="${contentWidth}" height="${rowHeight}" fill="${headerBg}" />`
@@ -190,7 +191,7 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
190
191
  }
191
192
 
192
193
  case 'divider': {
193
- const color = (p.color as string) ?? '#ddd'
194
+ const color = sanitizeColor((p.color as string) ?? '#ddd')
194
195
  const thickness = (p.thickness as number) ?? 1
195
196
  ctx.y += 12
196
197
  svg += `<line x1="${ctx.padding}" y1="${ctx.y}" x2="${ctx.padding + contentWidth}" y2="${ctx.y}" stroke="${color}" stroke-width="${thickness}" />`
@@ -209,8 +210,8 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
209
210
  break
210
211
 
211
212
  case 'button': {
212
- const bg = (p.background as string) ?? '#4f46e5'
213
- const color = (p.color as string) ?? '#ffffff'
213
+ const bg = sanitizeColor((p.background as string) ?? '#4f46e5')
214
+ const color = sanitizeColor((p.color as string) ?? '#ffffff')
214
215
  const text = escapeXml(getTextContent(node.children))
215
216
  const btnWidth = Math.min(text.length * 10 + 48, contentWidth)
216
217
  const btnHeight = 40
@@ -222,7 +223,7 @@ function renderNode(node: DocNode, ctx: RenderContext): string {
222
223
  }
223
224
 
224
225
  case 'quote': {
225
- const borderColor = (p.borderColor as string) ?? '#ddd'
226
+ const borderColor = sanitizeColor((p.borderColor as string) ?? '#ddd')
226
227
  const text = escapeXml(getTextContent(node.children))
227
228
  ctx.y += 4
228
229
  svg += `<rect x="${ctx.padding}" y="${ctx.y}" width="4" height="20" fill="${borderColor}" />`
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref, sanitizeImageSrc } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -83,7 +84,7 @@ function nodeToElements(node: DocNode): AdaptiveElement[] {
83
84
  }
84
85
 
85
86
  case 'link': {
86
- const href = p.href as string
87
+ const href = sanitizeHref(p.href as string)
87
88
  const text = getTextContent(node.children)
88
89
  elements.push({
89
90
  type: 'TextBlock',
@@ -94,7 +95,7 @@ function nodeToElements(node: DocNode): AdaptiveElement[] {
94
95
  }
95
96
 
96
97
  case 'image': {
97
- const src = p.src as string
98
+ const src = sanitizeImageSrc(p.src as string)
98
99
  if (src.startsWith('http')) {
99
100
  elements.push({
100
101
  type: 'Image',
@@ -191,7 +192,7 @@ function nodeToElements(node: DocNode): AdaptiveElement[] {
191
192
  {
192
193
  type: 'Action.OpenUrl',
193
194
  title: getTextContent(node.children),
194
- url: p.href as string,
195
+ url: sanitizeHref(p.href as string),
195
196
  style: 'positive',
196
197
  },
197
198
  ],
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -60,7 +61,7 @@ function renderNode(node: DocNode): string {
60
61
  }
61
62
 
62
63
  case 'link': {
63
- const href = p.href as string
64
+ const href = sanitizeHref(p.href as string)
64
65
  const text = esc(getTextContent(node.children))
65
66
  return `<a href="${esc(href)}">${text}</a>\n\n`
66
67
  }
@@ -115,7 +116,7 @@ function renderNode(node: DocNode): string {
115
116
  return '\n'
116
117
 
117
118
  case 'button': {
118
- const href = p.href as string
119
+ const href = sanitizeHref(p.href as string)
119
120
  const text = esc(getTextContent(node.children))
120
121
  return `<a href="${esc(href)}">${text}</a>\n\n`
121
122
  }
@@ -1,3 +1,4 @@
1
+ import { sanitizeHref } from '../sanitize'
1
2
  import type {
2
3
  DocChild,
3
4
  DocNode,
@@ -50,7 +51,7 @@ function renderNode(node: DocNode): string {
50
51
  }
51
52
 
52
53
  case 'link': {
53
- const href = p.href as string
54
+ const href = sanitizeHref(p.href as string)
54
55
  const text = getTextContent(node.children)
55
56
  return `${text}: ${href}\n\n`
56
57
  }
@@ -99,7 +100,7 @@ function renderNode(node: DocNode): string {
99
100
  return '\n'
100
101
 
101
102
  case 'button': {
102
- const href = p.href as string
103
+ const href = sanitizeHref(p.href as string)
103
104
  const text = getTextContent(node.children)
104
105
  return `*${text}*: ${href}\n\n`
105
106
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared sanitization utilities for document renderers.
3
+ * Prevents XSS via CSS injection, XML injection, and javascript: protocol attacks.
4
+ */
5
+
6
+ /**
7
+ * Sanitize a CSS value — strips characters that could break out of a CSS property.
8
+ * Blocks: semicolons, braces, angle brackets, quotes, backslashes, expressions.
9
+ */
10
+ export function sanitizeCss(value: string | undefined): string {
11
+ if (value == null) return ''
12
+ // Remove anything that could break out of a CSS value
13
+ return value
14
+ .replace(/[;{}()<>\\'"]/g, '')
15
+ .replace(/expression\s*\(/gi, '')
16
+ .replace(/url\s*\(/gi, '')
17
+ .replace(/javascript\s*:/gi, '')
18
+ }
19
+
20
+ /**
21
+ * Sanitize a color value — only allows hex colors, named colors, rgb/rgba, hsl/hsla.
22
+ * Returns the value if valid, empty string if not.
23
+ */
24
+ export function sanitizeColor(value: string | undefined): string {
25
+ if (value == null) return ''
26
+ const trimmed = value.trim()
27
+ // Hex: #fff, #ffffff, #ffffffff
28
+ if (/^#[0-9a-fA-F]{3,8}$/.test(trimmed)) return trimmed
29
+ // Named colors (common subset)
30
+ if (/^[a-zA-Z]{1,20}$/.test(trimmed)) return trimmed
31
+ // rgb/rgba/hsl/hsla
32
+ if (/^(rgb|hsl)a?\(\s*[\d.,\s%]+\)$/.test(trimmed)) return trimmed
33
+ // transparent, inherit, currentColor
34
+ if (/^(transparent|inherit|currentColor|initial|unset)$/i.test(trimmed))
35
+ return trimmed
36
+ return ''
37
+ }
38
+
39
+ /**
40
+ * Sanitize a color for XML attributes (DOCX/PPTX) — only hex without #.
41
+ * Returns 6-char hex string or default.
42
+ */
43
+ export function sanitizeXmlColor(
44
+ value: string | undefined,
45
+ fallback = '000000',
46
+ ): string {
47
+ if (value == null) return fallback
48
+ const hex = value.replace('#', '')
49
+ if (/^[0-9a-fA-F]{3,8}$/.test(hex)) return hex
50
+ return fallback
51
+ }
52
+
53
+ /**
54
+ * Sanitize a URL — blocks javascript:, data: (except images), and vbscript: protocols.
55
+ * Returns the URL if safe, empty string if not.
56
+ */
57
+ export function sanitizeHref(url: string | undefined): string {
58
+ if (url == null) return ''
59
+ const trimmed = url.trim()
60
+ // Block dangerous protocols
61
+ const lower = trimmed.toLowerCase().replace(/\s/g, '')
62
+ if (lower.startsWith('javascript:')) return ''
63
+ if (lower.startsWith('vbscript:')) return ''
64
+ if (lower.startsWith('data:') && !lower.startsWith('data:image/')) return ''
65
+ return trimmed
66
+ }
67
+
68
+ /**
69
+ * Sanitize an image src — allows http(s), data:image, and relative paths.
70
+ * Blocks javascript:, vbscript:, and non-image data: URIs.
71
+ */
72
+ export function sanitizeImageSrc(src: string | undefined): string {
73
+ if (src == null) return ''
74
+ const trimmed = src.trim()
75
+ const lower = trimmed.toLowerCase().replace(/\s/g, '')
76
+ if (lower.startsWith('javascript:')) return ''
77
+ if (lower.startsWith('vbscript:')) return ''
78
+ if (lower.startsWith('data:') && !lower.startsWith('data:image/')) return ''
79
+ return trimmed
80
+ }
81
+
82
+ /**
83
+ * Sanitize a style attribute value — validates it's safe CSS.
84
+ */
85
+ export function sanitizeStyle(value: string | undefined): string {
86
+ if (value == null) return ''
87
+ return sanitizeCss(value)
88
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"confluence-Va8e7RxQ.js","names":[],"sources":["../src/renderers/confluence.ts"],"sourcesContent":["import 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 = 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 = 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 = 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 +0,0 @@
1
- {"version":3,"file":"discord-DAoUZqvE.js","names":[],"sources":["../src/renderers/discord.ts"],"sourcesContent":["import 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 = 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 = 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 = 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 +0,0 @@
1
- {"version":3,"file":"docx-CorFwEH9.js","names":[],"sources":["../src/renderers/docx.ts"],"sourcesContent":["import 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: (p.color as string)?.replace('#', '') ?? '000000',\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 bold: p.bold as boolean | undefined,\n italics: p.italic as boolean | undefined,\n underline: p.underline ? {} : undefined,\n strike: p.strikethrough as boolean | undefined,\n size: p.size ? (p.size as number) * 2 : undefined,\n color: (p.color as string)?.replace('#', '') ?? '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: p.href as string,\n children: [\n new docx.TextRun({\n text: getTextContent(n.children),\n color: (p.color as string)?.replace('#', '') ?? '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: hs?.color?.replace('#', '') ?? '000000',\n }),\n ],\n alignment: alignmentMap(col.align) as any,\n }),\n ],\n shading: hs?.background\n ? {\n fill: hs.background.replace('#', ''),\n type: docx.ShadingType.SOLID,\n }\n : undefined,\n 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 shading:\n p.striped && rowIdx % 2 === 1\n ? { fill: 'F9F9F9', type: docx.ShadingType.SOLID }\n : undefined,\n 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: 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: (p.borderColor as string)?.replace('#', '') ?? '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 numbering: ordered ? { reference: listRef, level } : undefined,\n bullet: ordered ? undefined : { 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:\n (n.props.color as string)?.replace('#', '') ?? '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 headers: headerContent\n ? { default: new docx.Header({ children: headerContent as any }) }\n : undefined,\n footers: footerContent\n ? { default: new docx.Footer({ children: footerContent as any }) }\n : undefined,\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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"}