@pyreon/document 0.11.5 → 0.11.7
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/README.md +7 -4
- package/lib/confluence-Bd3ua1Ut.js.map +1 -1
- package/lib/csv-COrS4qdy.js.map +1 -1
- package/lib/discord-BLUnkEh9.js.map +1 -1
- package/lib/docx-uNAel545.js.map +1 -1
- package/lib/email-D0bbfWq4.js.map +1 -1
- package/lib/google-chat-CkKCBUWC.js.map +1 -1
- package/lib/html-B5biprN2.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/markdown-CdtlFGC0.js.map +1 -1
- package/lib/notion-iG2C5bEY.js.map +1 -1
- package/lib/pdf-IuBgTb3T.js.map +1 -1
- package/lib/pptx-DXiMiYFM.js.map +1 -1
- package/lib/sanitize-O_3j1mNJ.js.map +1 -1
- package/lib/slack-BI3EQwYm.js.map +1 -1
- package/lib/svg-BKxumy-p.js.map +1 -1
- package/lib/teams-Cwz9lce0.js.map +1 -1
- package/lib/telegram-gYFqyMXb.js.map +1 -1
- package/lib/text-l1XNXBOC.js.map +1 -1
- package/lib/types/index.d.ts +27 -27
- package/lib/whatsapp-CjSGoOKx.js.map +1 -1
- package/lib/xlsx-Cvu4LBNy.js.map +1 -1
- package/package.json +21 -21
- package/src/builder.ts +36 -36
- package/src/download.ts +32 -32
- package/src/index.ts +5 -10
- package/src/nodes.ts +45 -45
- package/src/render.ts +43 -43
- package/src/renderers/confluence.ts +63 -63
- package/src/renderers/csv.ts +10 -10
- package/src/renderers/discord.ts +37 -37
- package/src/renderers/docx.ts +57 -57
- package/src/renderers/email.ts +72 -72
- package/src/renderers/google-chat.ts +34 -34
- package/src/renderers/html.ts +76 -76
- package/src/renderers/markdown.ts +42 -42
- package/src/renderers/notion.ts +60 -60
- package/src/renderers/pdf.ts +78 -78
- package/src/renderers/pptx.ts +51 -51
- package/src/renderers/slack.ts +48 -48
- package/src/renderers/svg.ts +47 -47
- package/src/renderers/teams.ts +67 -67
- package/src/renderers/telegram.ts +39 -39
- package/src/renderers/text.ts +43 -43
- package/src/renderers/whatsapp.ts +33 -33
- package/src/renderers/xlsx.ts +35 -35
- package/src/sanitize.ts +20 -20
- package/src/tests/document.test.ts +1302 -1302
- package/src/tests/stress.test.ts +110 -110
- package/src/types.ts +61 -61
package/src/renderers/notion.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sanitizeHref, sanitizeImageSrc } from
|
|
2
|
-
import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from
|
|
1
|
+
import { sanitizeHref, sanitizeImageSrc } from '../sanitize'
|
|
2
|
+
import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Notion renderer — outputs Notion Block JSON for the Notion API.
|
|
@@ -7,17 +7,17 @@ import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn }
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
function resolveColumn(col: string | TableColumn): TableColumn {
|
|
10
|
-
return typeof col ===
|
|
10
|
+
return typeof col === 'string' ? { header: col } : col
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function getTextContent(children: DocChild[]): string {
|
|
14
14
|
return children
|
|
15
|
-
.map((c) => (typeof c ===
|
|
16
|
-
.join(
|
|
15
|
+
.map((c) => (typeof c === 'string' ? c : getTextContent((c as DocNode).children)))
|
|
16
|
+
.join('')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface RichText {
|
|
20
|
-
type:
|
|
20
|
+
type: 'text'
|
|
21
21
|
text: { content: string; link?: { url: string } }
|
|
22
22
|
annotations?: {
|
|
23
23
|
bold?: boolean
|
|
@@ -28,10 +28,10 @@ interface RichText {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function textToRichText(text: string, annotations?: RichText[
|
|
31
|
+
function textToRichText(text: string, annotations?: RichText['annotations']): RichText[] {
|
|
32
32
|
return [
|
|
33
33
|
{
|
|
34
|
-
type:
|
|
34
|
+
type: 'text',
|
|
35
35
|
text: { content: text },
|
|
36
36
|
...(annotations ? { annotations } : {}),
|
|
37
37
|
},
|
|
@@ -39,7 +39,7 @@ function textToRichText(text: string, annotations?: RichText["annotations"]): Ri
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
interface NotionBlock {
|
|
42
|
-
object:
|
|
42
|
+
object: 'block'
|
|
43
43
|
type: string
|
|
44
44
|
[key: string]: unknown
|
|
45
45
|
}
|
|
@@ -49,40 +49,40 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
49
49
|
const blocks: NotionBlock[] = []
|
|
50
50
|
|
|
51
51
|
switch (node.type) {
|
|
52
|
-
case
|
|
53
|
-
case
|
|
54
|
-
case
|
|
55
|
-
case
|
|
56
|
-
case
|
|
52
|
+
case 'document':
|
|
53
|
+
case 'page':
|
|
54
|
+
case 'section':
|
|
55
|
+
case 'row':
|
|
56
|
+
case 'column':
|
|
57
57
|
for (const child of node.children) {
|
|
58
|
-
if (typeof child !==
|
|
58
|
+
if (typeof child !== 'string') {
|
|
59
59
|
blocks.push(...nodeToBlocks(child))
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
break
|
|
63
63
|
|
|
64
|
-
case
|
|
64
|
+
case 'heading': {
|
|
65
65
|
const level = (p.level as number) ?? 1
|
|
66
66
|
const text = getTextContent(node.children)
|
|
67
|
-
const type = level <= 1 ?
|
|
67
|
+
const type = level <= 1 ? 'heading_1' : level === 2 ? 'heading_2' : 'heading_3'
|
|
68
68
|
blocks.push({
|
|
69
|
-
object:
|
|
69
|
+
object: 'block',
|
|
70
70
|
type,
|
|
71
71
|
[type]: { rich_text: textToRichText(text) },
|
|
72
72
|
})
|
|
73
73
|
break
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
case
|
|
76
|
+
case 'text': {
|
|
77
77
|
const text = getTextContent(node.children)
|
|
78
|
-
const annotations: RichText[
|
|
78
|
+
const annotations: RichText['annotations'] = {}
|
|
79
79
|
if (p.bold) annotations.bold = true
|
|
80
80
|
if (p.italic) annotations.italic = true
|
|
81
81
|
if (p.strikethrough) annotations.strikethrough = true
|
|
82
82
|
if (p.underline) annotations.underline = true
|
|
83
83
|
blocks.push({
|
|
84
|
-
object:
|
|
85
|
-
type:
|
|
84
|
+
object: 'block',
|
|
85
|
+
type: 'paragraph',
|
|
86
86
|
paragraph: {
|
|
87
87
|
rich_text: textToRichText(
|
|
88
88
|
text,
|
|
@@ -93,27 +93,27 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
93
93
|
break
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
case
|
|
96
|
+
case 'link': {
|
|
97
97
|
const href = sanitizeHref(p.href as string)
|
|
98
98
|
const text = getTextContent(node.children)
|
|
99
99
|
blocks.push({
|
|
100
|
-
object:
|
|
101
|
-
type:
|
|
100
|
+
object: 'block',
|
|
101
|
+
type: 'paragraph',
|
|
102
102
|
paragraph: {
|
|
103
|
-
rich_text: [{ type:
|
|
103
|
+
rich_text: [{ type: 'text', text: { content: text, link: { url: href } } }],
|
|
104
104
|
},
|
|
105
105
|
})
|
|
106
106
|
break
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
case
|
|
109
|
+
case 'image': {
|
|
110
110
|
const src = sanitizeImageSrc(p.src as string)
|
|
111
|
-
if (src.startsWith(
|
|
111
|
+
if (src.startsWith('http')) {
|
|
112
112
|
blocks.push({
|
|
113
|
-
object:
|
|
114
|
-
type:
|
|
113
|
+
object: 'block',
|
|
114
|
+
type: 'image',
|
|
115
115
|
image: {
|
|
116
|
-
type:
|
|
116
|
+
type: 'external',
|
|
117
117
|
external: { url: src },
|
|
118
118
|
...(p.caption ? { caption: textToRichText(p.caption as string) } : {}),
|
|
119
119
|
},
|
|
@@ -122,7 +122,7 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
122
122
|
break
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
case
|
|
125
|
+
case 'table': {
|
|
126
126
|
const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
|
|
127
127
|
const rows = (p.rows ?? []) as (string | number)[][]
|
|
128
128
|
|
|
@@ -130,8 +130,8 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
130
130
|
|
|
131
131
|
// Header row
|
|
132
132
|
tableRows.push({
|
|
133
|
-
object:
|
|
134
|
-
type:
|
|
133
|
+
object: 'block',
|
|
134
|
+
type: 'table_row',
|
|
135
135
|
table_row: {
|
|
136
136
|
cells: columns.map((col) => textToRichText(col.header, { bold: true })),
|
|
137
137
|
},
|
|
@@ -140,17 +140,17 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
140
140
|
// Data rows
|
|
141
141
|
for (const row of rows) {
|
|
142
142
|
tableRows.push({
|
|
143
|
-
object:
|
|
144
|
-
type:
|
|
143
|
+
object: 'block',
|
|
144
|
+
type: 'table_row',
|
|
145
145
|
table_row: {
|
|
146
|
-
cells: columns.map((_, i) => textToRichText(String(row[i] ??
|
|
146
|
+
cells: columns.map((_, i) => textToRichText(String(row[i] ?? ''))),
|
|
147
147
|
},
|
|
148
148
|
})
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
blocks.push({
|
|
152
|
-
object:
|
|
153
|
-
type:
|
|
152
|
+
object: 'block',
|
|
153
|
+
type: 'table',
|
|
154
154
|
table: {
|
|
155
155
|
table_width: columns.length,
|
|
156
156
|
has_column_header: true,
|
|
@@ -160,14 +160,14 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
160
160
|
break
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
case
|
|
163
|
+
case 'list': {
|
|
164
164
|
const ordered = p.ordered as boolean | undefined
|
|
165
|
-
const items = node.children.filter((c): c is DocNode => typeof c !==
|
|
165
|
+
const items = node.children.filter((c): c is DocNode => typeof c !== 'string')
|
|
166
166
|
for (const item of items) {
|
|
167
167
|
const text = getTextContent(item.children)
|
|
168
|
-
const type = ordered ?
|
|
168
|
+
const type = ordered ? 'numbered_list_item' : 'bulleted_list_item'
|
|
169
169
|
blocks.push({
|
|
170
|
-
object:
|
|
170
|
+
object: 'block',
|
|
171
171
|
type,
|
|
172
172
|
[type]: { rich_text: textToRichText(text) },
|
|
173
173
|
})
|
|
@@ -175,12 +175,12 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
175
175
|
break
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
case
|
|
178
|
+
case 'code': {
|
|
179
179
|
const text = getTextContent(node.children)
|
|
180
|
-
const lang = (p.language as string) ??
|
|
180
|
+
const lang = (p.language as string) ?? 'plain text'
|
|
181
181
|
blocks.push({
|
|
182
|
-
object:
|
|
183
|
-
type:
|
|
182
|
+
object: 'block',
|
|
183
|
+
type: 'code',
|
|
184
184
|
code: {
|
|
185
185
|
rich_text: textToRichText(text),
|
|
186
186
|
language: lang,
|
|
@@ -189,29 +189,29 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
189
189
|
break
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
case
|
|
193
|
-
case
|
|
194
|
-
blocks.push({ object:
|
|
192
|
+
case 'divider':
|
|
193
|
+
case 'page-break':
|
|
194
|
+
blocks.push({ object: 'block', type: 'divider', divider: {} })
|
|
195
195
|
break
|
|
196
196
|
|
|
197
|
-
case
|
|
197
|
+
case 'spacer':
|
|
198
198
|
blocks.push({
|
|
199
|
-
object:
|
|
200
|
-
type:
|
|
199
|
+
object: 'block',
|
|
200
|
+
type: 'paragraph',
|
|
201
201
|
paragraph: { rich_text: [] },
|
|
202
202
|
})
|
|
203
203
|
break
|
|
204
204
|
|
|
205
|
-
case
|
|
205
|
+
case 'button': {
|
|
206
206
|
const href = sanitizeHref(p.href as string)
|
|
207
207
|
const text = getTextContent(node.children)
|
|
208
208
|
blocks.push({
|
|
209
|
-
object:
|
|
210
|
-
type:
|
|
209
|
+
object: 'block',
|
|
210
|
+
type: 'paragraph',
|
|
211
211
|
paragraph: {
|
|
212
212
|
rich_text: [
|
|
213
213
|
{
|
|
214
|
-
type:
|
|
214
|
+
type: 'text',
|
|
215
215
|
text: { content: text, link: { url: href } },
|
|
216
216
|
annotations: { bold: true },
|
|
217
217
|
},
|
|
@@ -221,11 +221,11 @@ function nodeToBlocks(node: DocNode): NotionBlock[] {
|
|
|
221
221
|
break
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
case
|
|
224
|
+
case 'quote': {
|
|
225
225
|
const text = getTextContent(node.children)
|
|
226
226
|
blocks.push({
|
|
227
|
-
object:
|
|
228
|
-
type:
|
|
227
|
+
object: 'block',
|
|
228
|
+
type: 'quote',
|
|
229
229
|
quote: { rich_text: textToRichText(text) },
|
|
230
230
|
})
|
|
231
231
|
break
|
package/src/renderers/pdf.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from
|
|
1
|
+
import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from '../types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* PDF renderer — lazy-loads pdfmake on first use.
|
|
@@ -17,13 +17,13 @@ import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn }
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
function resolveColumn(col: string | TableColumn): TableColumn {
|
|
20
|
-
return typeof col ===
|
|
20
|
+
return typeof col === 'string' ? { header: col } : col
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function getTextContent(children: DocChild[]): string {
|
|
24
24
|
return children
|
|
25
|
-
.map((c) => (typeof c ===
|
|
26
|
-
.join(
|
|
25
|
+
.map((c) => (typeof c === 'string' ? c : getTextContent((c as DocNode).children)))
|
|
26
|
+
.join('')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
type PdfContent = Record<string, unknown> | string | PdfContent[]
|
|
@@ -50,39 +50,39 @@ const PAGE_SIZES: Record<string, { width: number; height: number }> = {
|
|
|
50
50
|
function resolveImageSrc(
|
|
51
51
|
src: string,
|
|
52
52
|
): { image: string } | { text: string; italics: true; color: string } {
|
|
53
|
-
if (src.startsWith(
|
|
53
|
+
if (src.startsWith('data:')) {
|
|
54
54
|
return { image: src }
|
|
55
55
|
}
|
|
56
|
-
if (src.startsWith(
|
|
57
|
-
return { text: `[Image: ${src}]`, italics: true, color:
|
|
56
|
+
if (src.startsWith('http://') || src.startsWith('https://')) {
|
|
57
|
+
return { text: `[Image: ${src}]`, italics: true, color: '#999999' }
|
|
58
58
|
}
|
|
59
59
|
// Local path — cannot resolve in browser
|
|
60
|
-
return { text: `[Image: ${src}]`, italics: true, color:
|
|
60
|
+
return { text: `[Image: ${src}]`, italics: true, color: '#999999' }
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {
|
|
64
64
|
const p = node.props
|
|
65
65
|
|
|
66
66
|
switch (node.type) {
|
|
67
|
-
case
|
|
68
|
-
case
|
|
67
|
+
case 'document':
|
|
68
|
+
case 'page':
|
|
69
69
|
return node.children
|
|
70
|
-
.map((c) => (typeof c ===
|
|
70
|
+
.map((c) => (typeof c === 'string' ? c : nodeToContent(c)))
|
|
71
71
|
.filter((c): c is PdfContent => c != null)
|
|
72
72
|
|
|
73
|
-
case
|
|
73
|
+
case 'section': {
|
|
74
74
|
const content = node.children
|
|
75
|
-
.map((c) => (typeof c ===
|
|
75
|
+
.map((c) => (typeof c === 'string' ? c : nodeToContent(c)))
|
|
76
76
|
.filter((c): c is PdfContent => c != null)
|
|
77
77
|
.flat()
|
|
78
78
|
|
|
79
|
-
if (p.direction ===
|
|
79
|
+
if (p.direction === 'row') {
|
|
80
80
|
return {
|
|
81
81
|
columns: node.children
|
|
82
|
-
.filter((c): c is DocNode => typeof c !==
|
|
82
|
+
.filter((c): c is DocNode => typeof c !== 'string')
|
|
83
83
|
.map((child) => ({
|
|
84
84
|
stack: [nodeToContent(child)].flat().filter(Boolean),
|
|
85
|
-
width: child.props.width ===
|
|
85
|
+
width: child.props.width === '*' || !child.props.width ? '*' : child.props.width,
|
|
86
86
|
})),
|
|
87
87
|
columnGap: (p.gap as number) ?? 0,
|
|
88
88
|
}
|
|
@@ -91,25 +91,25 @@ function nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {
|
|
|
91
91
|
return content
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
case
|
|
94
|
+
case 'row': {
|
|
95
95
|
return {
|
|
96
96
|
columns: node.children
|
|
97
|
-
.filter((c): c is DocNode => typeof c !==
|
|
97
|
+
.filter((c): c is DocNode => typeof c !== 'string')
|
|
98
98
|
.map((child) => ({
|
|
99
99
|
stack: [nodeToContent(child)].flat().filter(Boolean),
|
|
100
|
-
width: child.props.width ??
|
|
100
|
+
width: child.props.width ?? '*',
|
|
101
101
|
})),
|
|
102
102
|
columnGap: (p.gap as number) ?? 0,
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
case
|
|
106
|
+
case 'column':
|
|
107
107
|
return node.children
|
|
108
|
-
.map((c) => (typeof c ===
|
|
108
|
+
.map((c) => (typeof c === 'string' ? c : nodeToContent(c)))
|
|
109
109
|
.filter((c): c is PdfContent => c != null)
|
|
110
110
|
.flat()
|
|
111
111
|
|
|
112
|
-
case
|
|
112
|
+
case 'heading': {
|
|
113
113
|
const level = (p.level as number) ?? 1
|
|
114
114
|
const sizes: Record<number, number> = {
|
|
115
115
|
1: 24,
|
|
@@ -123,45 +123,45 @@ function nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {
|
|
|
123
123
|
text: getTextContent(node.children),
|
|
124
124
|
fontSize: sizes[level] ?? 18,
|
|
125
125
|
bold: true,
|
|
126
|
-
color: (p.color as string) ??
|
|
127
|
-
alignment: (p.align as string) ??
|
|
126
|
+
color: (p.color as string) ?? '#000000',
|
|
127
|
+
alignment: (p.align as string) ?? 'left',
|
|
128
128
|
margin: [0, level === 1 ? 0 : 8, 0, 8],
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
case
|
|
132
|
+
case 'text':
|
|
133
133
|
return {
|
|
134
134
|
text: getTextContent(node.children),
|
|
135
135
|
fontSize: (p.size as number) ?? 12,
|
|
136
|
-
color: (p.color as string) ??
|
|
136
|
+
color: (p.color as string) ?? '#333333',
|
|
137
137
|
bold: p.bold ?? false,
|
|
138
138
|
italics: p.italic ?? false,
|
|
139
|
-
decoration: p.underline ?
|
|
140
|
-
alignment: (p.align as string) ??
|
|
139
|
+
decoration: p.underline ? 'underline' : p.strikethrough ? 'lineThrough' : undefined,
|
|
140
|
+
alignment: (p.align as string) ?? 'left',
|
|
141
141
|
lineHeight: (p.lineHeight as number) ?? 1.4,
|
|
142
142
|
margin: [0, 0, 0, 8],
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
case
|
|
145
|
+
case 'link':
|
|
146
146
|
return {
|
|
147
147
|
text: getTextContent(node.children),
|
|
148
148
|
link: p.href as string,
|
|
149
|
-
color: (p.color as string) ??
|
|
150
|
-
decoration:
|
|
149
|
+
color: (p.color as string) ?? '#4f46e5',
|
|
150
|
+
decoration: 'underline',
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
case
|
|
153
|
+
case 'image': {
|
|
154
154
|
const src = p.src as string
|
|
155
155
|
const resolved = resolveImageSrc(src)
|
|
156
156
|
|
|
157
|
-
if (
|
|
157
|
+
if ('image' in resolved) {
|
|
158
158
|
const result: Record<string, unknown> = {
|
|
159
159
|
image: resolved.image,
|
|
160
160
|
fit: [p.width ?? 500, p.height ?? 400],
|
|
161
161
|
margin: [0, 0, 0, 8],
|
|
162
162
|
}
|
|
163
|
-
if (p.align ===
|
|
164
|
-
if (p.align ===
|
|
163
|
+
if (p.align === 'center') result.alignment = 'center'
|
|
164
|
+
if (p.align === 'right') result.alignment = 'right'
|
|
165
165
|
return result
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -169,7 +169,7 @@ function nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {
|
|
|
169
169
|
return { ...resolved, margin: [0, 0, 0, 8] }
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
case
|
|
172
|
+
case 'table': {
|
|
173
173
|
const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
|
|
174
174
|
const rows = (p.rows ?? []) as (string | number)[][]
|
|
175
175
|
const hs = p.headerStyle as { background?: string; color?: string } | undefined
|
|
@@ -177,22 +177,22 @@ function nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {
|
|
|
177
177
|
const headerRow = columns.map((col) => ({
|
|
178
178
|
text: col.header,
|
|
179
179
|
bold: true,
|
|
180
|
-
fillColor: hs?.background ??
|
|
181
|
-
color: hs?.color ??
|
|
182
|
-
alignment: col.align ??
|
|
180
|
+
fillColor: hs?.background ?? '#f5f5f5',
|
|
181
|
+
color: hs?.color ?? '#000000',
|
|
182
|
+
alignment: col.align ?? 'left',
|
|
183
183
|
}))
|
|
184
184
|
|
|
185
185
|
const dataRows = rows.map((row, rowIdx) =>
|
|
186
186
|
columns.map((col, colIdx) => ({
|
|
187
|
-
text: String(row[colIdx] ??
|
|
188
|
-
alignment: col.align ??
|
|
189
|
-
fillColor: p.striped && rowIdx % 2 === 1 ?
|
|
187
|
+
text: String(row[colIdx] ?? ''),
|
|
188
|
+
alignment: col.align ?? 'left',
|
|
189
|
+
fillColor: p.striped && rowIdx % 2 === 1 ? '#f9f9f9' : undefined,
|
|
190
190
|
})),
|
|
191
191
|
)
|
|
192
192
|
|
|
193
193
|
const widths = columns.map((col) => {
|
|
194
|
-
if (!col.width) return
|
|
195
|
-
if (typeof col.width ===
|
|
194
|
+
if (!col.width) return '*'
|
|
195
|
+
if (typeof col.width === 'string' && col.width.endsWith('%')) {
|
|
196
196
|
return col.width
|
|
197
197
|
}
|
|
198
198
|
return col.width
|
|
@@ -204,81 +204,81 @@ function nodeToContent(node: DocNode): PdfContent | PdfContent[] | null {
|
|
|
204
204
|
widths,
|
|
205
205
|
body: [headerRow, ...dataRows],
|
|
206
206
|
},
|
|
207
|
-
layout: p.bordered ? undefined :
|
|
207
|
+
layout: p.bordered ? undefined : 'lightHorizontalLines',
|
|
208
208
|
unbreakable: p.keepTogether ?? false,
|
|
209
209
|
margin: [0, 0, 0, 12],
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
case
|
|
213
|
+
case 'list': {
|
|
214
214
|
const items = node.children
|
|
215
|
-
.filter((c): c is DocNode => typeof c !==
|
|
215
|
+
.filter((c): c is DocNode => typeof c !== 'string')
|
|
216
216
|
.map((item) => getTextContent(item.children))
|
|
217
217
|
|
|
218
218
|
return p.ordered ? { ol: items, margin: [0, 0, 0, 8] } : { ul: items, margin: [0, 0, 0, 8] }
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
case
|
|
221
|
+
case 'list-item':
|
|
222
222
|
return getTextContent(node.children)
|
|
223
223
|
|
|
224
|
-
case
|
|
224
|
+
case 'code':
|
|
225
225
|
return {
|
|
226
226
|
text: getTextContent(node.children),
|
|
227
|
-
font:
|
|
227
|
+
font: 'Courier',
|
|
228
228
|
fontSize: 10,
|
|
229
|
-
background:
|
|
229
|
+
background: '#f5f5f5',
|
|
230
230
|
margin: [0, 0, 0, 8],
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
case
|
|
234
|
-
return { text:
|
|
233
|
+
case 'page-break':
|
|
234
|
+
return { text: '', pageBreak: 'after' }
|
|
235
235
|
|
|
236
|
-
case
|
|
236
|
+
case 'divider':
|
|
237
237
|
return {
|
|
238
238
|
canvas: [
|
|
239
239
|
{
|
|
240
|
-
type:
|
|
240
|
+
type: 'line',
|
|
241
241
|
x1: 0,
|
|
242
242
|
y1: 0,
|
|
243
243
|
x2: 515,
|
|
244
244
|
y2: 0,
|
|
245
245
|
lineWidth: (p.thickness as number) ?? 1,
|
|
246
|
-
lineColor: (p.color as string) ??
|
|
246
|
+
lineColor: (p.color as string) ?? '#dddddd',
|
|
247
247
|
},
|
|
248
248
|
],
|
|
249
249
|
margin: [0, 8, 0, 8],
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
case
|
|
253
|
-
return { text:
|
|
252
|
+
case 'spacer':
|
|
253
|
+
return { text: '', margin: [0, (p.height as number) ?? 12, 0, 0] }
|
|
254
254
|
|
|
255
|
-
case
|
|
255
|
+
case 'button':
|
|
256
256
|
return {
|
|
257
257
|
text: getTextContent(node.children),
|
|
258
258
|
link: p.href as string,
|
|
259
259
|
bold: true,
|
|
260
|
-
color: (p.color as string) ??
|
|
261
|
-
background: (p.background as string) ??
|
|
260
|
+
color: (p.color as string) ?? '#ffffff',
|
|
261
|
+
background: (p.background as string) ?? '#4f46e5',
|
|
262
262
|
margin: [0, 8, 0, 8],
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
case
|
|
265
|
+
case 'quote':
|
|
266
266
|
return {
|
|
267
267
|
table: {
|
|
268
|
-
widths: [4,
|
|
268
|
+
widths: [4, '*'],
|
|
269
269
|
body: [
|
|
270
270
|
[
|
|
271
|
-
{ text:
|
|
271
|
+
{ text: '', fillColor: (p.borderColor as string) ?? '#dddddd' },
|
|
272
272
|
{
|
|
273
273
|
text: getTextContent(node.children),
|
|
274
274
|
italics: true,
|
|
275
|
-
color:
|
|
275
|
+
color: '#555555',
|
|
276
276
|
margin: [8, 4, 0, 4],
|
|
277
277
|
},
|
|
278
278
|
],
|
|
279
279
|
],
|
|
280
280
|
},
|
|
281
|
-
layout:
|
|
281
|
+
layout: 'noBorders',
|
|
282
282
|
margin: [0, 4, 0, 8],
|
|
283
283
|
}
|
|
284
284
|
|
|
@@ -291,7 +291,7 @@ function resolveMargin(
|
|
|
291
291
|
margin: number | [number, number] | [number, number, number, number] | undefined,
|
|
292
292
|
): [number, number, number, number] {
|
|
293
293
|
if (margin == null) return [40, 40, 40, 40]
|
|
294
|
-
if (typeof margin ===
|
|
294
|
+
if (typeof margin === 'number') return [margin, margin, margin, margin]
|
|
295
295
|
if (margin.length === 2) return [margin[1], margin[0], margin[1], margin[0]]
|
|
296
296
|
return margin
|
|
297
297
|
}
|
|
@@ -307,7 +307,7 @@ function renderHeaderFooter(node: DocNode | undefined): PdfContent | undefined {
|
|
|
307
307
|
const content = nodeToContent(node)
|
|
308
308
|
if (content == null) return undefined
|
|
309
309
|
if (Array.isArray(content)) return { stack: content, margin: [40, 10, 40, 0] }
|
|
310
|
-
if (typeof content ===
|
|
310
|
+
if (typeof content === 'object') return { ...content, margin: [40, 10, 40, 0] }
|
|
311
311
|
return { text: content, margin: [40, 10, 40, 0] }
|
|
312
312
|
}
|
|
313
313
|
|
|
@@ -317,8 +317,8 @@ export const pdfRenderer: DocumentRenderer = {
|
|
|
317
317
|
let pdfMakeModule: any
|
|
318
318
|
let pdfFontsModule: any
|
|
319
319
|
try {
|
|
320
|
-
pdfMakeModule = await import(
|
|
321
|
-
pdfFontsModule = await import(
|
|
320
|
+
pdfMakeModule = await import('pdfmake/build/pdfmake')
|
|
321
|
+
pdfFontsModule = await import('pdfmake/build/vfs_fonts')
|
|
322
322
|
} catch {
|
|
323
323
|
throw new Error(
|
|
324
324
|
'[@pyreon/document] PDF renderer requires "pdfmake" package. Install it: bun add pdfmake',
|
|
@@ -330,7 +330,7 @@ export const pdfRenderer: DocumentRenderer = {
|
|
|
330
330
|
// ESM interop may wrap it in an extra .default layer.
|
|
331
331
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- pdfmake types are incomplete
|
|
332
332
|
let pdfMake: any = pdfMakeModule.default ?? pdfMakeModule
|
|
333
|
-
if (pdfMake.default && typeof pdfMake.default.createPdf ===
|
|
333
|
+
if (pdfMake.default && typeof pdfMake.default.createPdf === 'function') {
|
|
334
334
|
pdfMake = pdfMake.default
|
|
335
335
|
}
|
|
336
336
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- pdfmake types are incomplete
|
|
@@ -343,10 +343,10 @@ export const pdfRenderer: DocumentRenderer = {
|
|
|
343
343
|
|
|
344
344
|
// Find page config
|
|
345
345
|
const pageNode = node.children.find(
|
|
346
|
-
(c): c is DocNode => typeof c !==
|
|
346
|
+
(c): c is DocNode => typeof c !== 'string' && c.type === 'page',
|
|
347
347
|
)
|
|
348
|
-
const pageSize = (pageNode?.props.size as string) ??
|
|
349
|
-
const pageOrientation = (pageNode?.props.orientation as string) ??
|
|
348
|
+
const pageSize = (pageNode?.props.size as string) ?? 'A4'
|
|
349
|
+
const pageOrientation = (pageNode?.props.orientation as string) ?? 'portrait'
|
|
350
350
|
const pageMargin = resolveMargin(
|
|
351
351
|
pageNode?.props.margin as
|
|
352
352
|
| number
|
|
@@ -366,10 +366,10 @@ export const pdfRenderer: DocumentRenderer = {
|
|
|
366
366
|
pageOrientation,
|
|
367
367
|
pageMargins: pageMargin,
|
|
368
368
|
info: {
|
|
369
|
-
title: (node.props.title as string) ??
|
|
370
|
-
author: (node.props.author as string) ??
|
|
371
|
-
subject: (node.props.subject as string) ??
|
|
372
|
-
keywords: (node.props.keywords as string[])?.join(
|
|
369
|
+
title: (node.props.title as string) ?? '',
|
|
370
|
+
author: (node.props.author as string) ?? '',
|
|
371
|
+
subject: (node.props.subject as string) ?? '',
|
|
372
|
+
keywords: (node.props.keywords as string[])?.join(', ') ?? '',
|
|
373
373
|
},
|
|
374
374
|
content,
|
|
375
375
|
defaultStyle: {
|