@pyreon/document 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -3
- 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
package/src/renderers/html.ts
CHANGED
|
@@ -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
|
|
146
|
-
|
|
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 = ``
|
|
63
|
+
let md = `})`
|
|
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`
|
package/src/renderers/notion.ts
CHANGED
|
@@ -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',
|
package/src/renderers/pptx.ts
CHANGED
|
@@ -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')
|
|
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')
|
|
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')
|
|
152
|
-
color: (hs?.color ?? '#000000')
|
|
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')
|
|
261
|
+
color: sanitizeXmlColor((p.color as string) ?? '#ffffff'),
|
|
261
262
|
fill: {
|
|
262
|
-
color: ((p.background as string) ?? '#4f46e5')
|
|
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')
|
|
284
|
+
fill: { color: sanitizeXmlColor((p.color as string) ?? '#DDDDDD') },
|
|
284
285
|
})
|
|
285
286
|
ctx.y += 0.2
|
|
286
287
|
break
|
package/src/renderers/slack.ts
CHANGED
|
@@ -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',
|
package/src/renderers/svg.ts
CHANGED
|
@@ -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}" />`
|
package/src/renderers/teams.ts
CHANGED
|
@@ -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
|
}
|
package/src/sanitize.ts
ADDED
|
@@ -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":";;;;;AAaA,SAAS,cAAc,KAAwC;AAC7D,QAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ,KAAK,GAAG;;AAGrD,SAAS,eAAe,UAA8B;AACpD,QAAO,SACJ,KAAK,MACJ,OAAO,MAAM,WAAW,IAAI,eAAgB,EAAc,SAAS,CACpE,CACA,KAAK,GAAG;;;AAUb,SAAS,YAAY,MAAsD;AACzE,KAAI,KAAK,SAAS,WAEhB;OADe,KAAK,MAAM,SAAoB,OAChC,EAAG,QAAO,EAAE,OAAO,eAAe,KAAK,SAAS,EAAE;;AAElE,KAAI,KAAK,SAAS,SAAS;EACzB,MAAM,MAAM,KAAK,MAAM;AACvB,MAAI,IAAI,WAAW,OAAO,CAAE,QAAO,EAAE,UAAU,KAAK;;AAEtD,MAAK,MAAM,SAAS,KAAK,SACvB,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,SAAS,YAAY,MAAM;AACjC,MAAI,OAAO,SAAS,OAAO,SAAU,QAAO;;AAGhD,QAAO,EAAE;;AAGX,SAAS,eACP,MACA,MAC6C;CAC7C,MAAM,IAAI,KAAK;CACf,IAAI,UAAU;CACd,MAAM,SAAyB,EAAE;AAEjC,SAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;AACH,QAAK,MAAM,SAAS,KAAK,SACvB,KAAI,OAAO,UAAU,UAAU;IAC7B,MAAM,SAAS,eAAe,OAAO,KAAK;AAC1C,eAAW,OAAO;AAClB,WAAO,KAAK,GAAG,OAAO,OAAO;;AAGjC;EAEF,KAAK,WAAW;GACd,MAAM,OAAO,eAAe,KAAK,SAAS;AAG1C,QAFe,EAAE,SAAoB,OAEvB,KAAK,SAAS,KAAK,MAC/B;AAEF,cAAW,KAAK,KAAK;AACrB;;EAGF,KAAK,QAAQ;GACX,IAAI,OAAO,eAAe,KAAK,SAAS;AACxC,OAAI,EAAE,KAAM,QAAO,KAAK,KAAK;AAC7B,OAAI,EAAE,OAAQ,QAAO,IAAI,KAAK;AAC9B,OAAI,EAAE,cAAe,QAAO,KAAK,KAAK;AACtC,cAAW,GAAG,KAAK;AACnB;;EAGF,KAAK,QAAQ;GACX,MAAM,OAAO,EAAE;GACf,MAAM,OAAO,eAAe,KAAK,SAAS;AAC1C,cAAW,IAAI,KAAK,IAAI,KAAK;AAC7B;;EAGF,KAAK,QAEH;EAEF,KAAK,SAAS;GACZ,MAAM,WAAY,EAAE,WAAW,EAAE,EAA+B,IAC9D,cACD;GACD,MAAM,OAAQ,EAAE,QAAQ,EAAE;AAG1B,OAAI,QAAQ,UAAU,KAAK,KAAK,UAAU,GACxC,MAAK,MAAM,OAAO,SAAS;IACzB,MAAM,SAAS,QAAQ,QAAQ,IAAI;IACnC,MAAM,SAAS,KAAK,KAAK,QAAQ,OAAO,IAAI,WAAW,GAAG,CAAC,CAAC,KAAK,KAAK;AACtE,WAAO,KAAK;KACV,MAAM,IAAI;KACV,OAAO,UAAU;KACjB,QAAQ;KACT,CAAC;;QAEC;IAEL,MAAM,SAAS,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM;IACvD,MAAM,YAAY,QAAQ,UAAU,MAAM,CAAC,KAAK,MAAM;IACtD,MAAM,OAAO,KACV,KAAK,QAAQ,IAAI,KAAK,MAAM,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CACzD,KAAK,KAAK;AACb,eAAW,WAAW,OAAO,IAAI,UAAU,IAAI,KAAK;;AAEtD;;EAGF,KAAK,QAAQ;GACX,MAAM,UAAU,EAAE;GAClB,MAAM,QAAQ,KAAK,SAChB,QAAQ,MAAoB,OAAO,MAAM,SAAS,CAClD,KAAK,MAAM,MAAM;AAEhB,WAAO,GADQ,UAAU,GAAG,IAAI,EAAE,KAAK,IACtB,GAAG,eAAe,KAAK,SAAS;KACjD,CACD,KAAK,KAAK;AACb,cAAW,GAAG,MAAM;AACpB;;EAGF,KAAK,QAAQ;GACX,MAAM,OAAQ,EAAE,YAAuB;GACvC,MAAM,OAAO,eAAe,KAAK,SAAS;AAC1C,cAAW,SAAS,KAAK,IAAI,KAAK;AAClC;;EAGF,KAAK;EACL,KAAK;AACH,cAAW;AACX;EAEF,KAAK,UAAU;GACb,MAAM,OAAO,EAAE;GACf,MAAM,OAAO,eAAe,KAAK,SAAS;AAC1C,cAAW,MAAM,KAAK,MAAM,KAAK;AACjC;;EAGF,KAAK,SAAS;GACZ,MAAM,OAAO,eAAe,KAAK,SAAS;AAC1C,cAAW,KAAK,KAAK;AACrB;;;AAIJ,QAAO;EAAE;EAAS;EAAQ;;AAG5B,MAAa,kBAAoC,EAC/C,MAAM,OAAO,MAAe,UAA2C;CACrE,MAAM,OAAO,YAAY,KAAK;CAC9B,MAAM,EAAE,SAAS,WAAW,eAAe,MAAM,KAAK;CAEtD,MAAM,QAAiC;EACrC,OAAO,KAAK,SAAU,KAAK,MAAM,SAAoB;EACrD,aAAa,QAAQ,MAAM,IAAI;EAC/B,OAAO;EACR;AAED,KAAI,OAAO,SAAS,EAAG,OAAM,SAAS;AACtC,KAAI,KAAK,SAAU,OAAM,QAAQ,EAAE,KAAK,KAAK,UAAU;AAEvD,QAAO,KAAK,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE;GAEtD"}
|
package/lib/docx-CorFwEH9.js.map
DELETED
|
@@ -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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"}
|