@pyreon/document 0.10.0 → 0.11.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-Bd3ua1Ut.js.map +1 -1
- package/lib/csv-COrS4qdy.js.map +1 -1
- package/lib/discord-BLUnkEh9.js.map +1 -1
- package/lib/{dist-BsqdI2nY.js → dist-CYL41kqQ.js} +2 -2
- package/lib/dist-CYL41kqQ.js.map +1 -0
- package/lib/{docx-BEBOihjl.js → docx-uNAel545.js} +7 -2
- package/lib/docx-uNAel545.js.map +1 -0
- package/lib/email-D0bbfWq4.js.map +1 -1
- package/lib/{exceljs-BoIDUUaw.js → exceljs-BYETsesT.js} +314 -314
- package/lib/exceljs-BYETsesT.js.map +1 -0
- package/lib/google-chat-CkKCBUWC.js.map +1 -1
- package/lib/html-B5biprN2.js.map +1 -1
- package/lib/index.js +17 -8
- 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-DIUQUEdj.js → pdf-IuBgTb3T.js} +9 -3
- package/lib/pdf-IuBgTb3T.js.map +1 -0
- package/lib/{pdfmake-DnmLxK4Q.js → pdfmake-CKMX5URW.js} +2 -4
- package/lib/pdfmake-CKMX5URW.js.map +1 -0
- package/lib/{pptx-Dd33oL3_.js → pptx-DXiMiYFM.js} +7 -2
- package/lib/pptx-DXiMiYFM.js.map +1 -0
- package/lib/{pptxgen.es-COcgXsyx.js → pptxgen.es-FsqHs8mD.js} +3 -6
- package/lib/pptxgen.es-FsqHs8mD.js.map +1 -0
- 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 +43 -39
- package/lib/types/index.d.ts.map +1 -1
- package/lib/{vfs_fonts-Df1kkZ4Y.js → vfs_fonts-Cap07Jg3.js} +2 -2
- package/lib/vfs_fonts-Cap07Jg3.js.map +1 -0
- package/lib/whatsapp-CjSGoOKx.js.map +1 -1
- package/lib/{xlsx-Bb5TWyXQ.js → xlsx-Cvu4LBNy.js} +8 -2
- package/lib/xlsx-Cvu4LBNy.js.map +1 -0
- package/package.json +19 -7
- package/src/builder.ts +53 -44
- package/src/download.ts +32 -36
- package/src/env.d.ts +3 -17
- package/src/index.ts +6 -8
- package/src/nodes.ts +45 -45
- package/src/render.ts +45 -118
- package/src/renderers/confluence.ts +64 -80
- package/src/renderers/csv.ts +11 -18
- package/src/renderers/discord.ts +38 -50
- package/src/renderers/docx.ts +78 -120
- package/src/renderers/email.ts +73 -92
- package/src/renderers/google-chat.ts +35 -47
- package/src/renderers/html.ts +78 -101
- package/src/renderers/markdown.ts +43 -53
- package/src/renderers/notion.ts +63 -85
- package/src/renderers/pdf.ts +92 -115
- package/src/renderers/pptx.ts +60 -66
- package/src/renderers/slack.ts +49 -61
- package/src/renderers/svg.ts +49 -63
- package/src/renderers/teams.ts +68 -80
- package/src/renderers/telegram.ts +40 -54
- package/src/renderers/text.ts +44 -66
- package/src/renderers/whatsapp.ts +34 -48
- package/src/renderers/xlsx.ts +47 -61
- package/src/sanitize.ts +21 -25
- package/src/tests/document.test.ts +1337 -1385
- package/src/tests/stress.test.ts +111 -111
- package/src/types.ts +66 -65
- package/lib/dist-BsqdI2nY.js.map +0 -1
- package/lib/docx-BEBOihjl.js.map +0 -1
- package/lib/exceljs-BoIDUUaw.js.map +0 -1
- package/lib/pdf-DIUQUEdj.js.map +0 -1
- package/lib/pdfmake-DnmLxK4Q.js.map +0 -1
- package/lib/pptx-Dd33oL3_.js.map +0 -1
- package/lib/pptxgen.es-COcgXsyx.js.map +0 -1
- package/lib/vfs_fonts-Df1kkZ4Y.js.map +0 -1
- package/lib/xlsx-Bb5TWyXQ.js.map +0 -1
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import { sanitizeHref, sanitizeImageSrc } from
|
|
2
|
-
import type {
|
|
3
|
-
DocChild,
|
|
4
|
-
DocNode,
|
|
5
|
-
DocumentRenderer,
|
|
6
|
-
RenderOptions,
|
|
7
|
-
TableColumn,
|
|
8
|
-
} from '../types'
|
|
1
|
+
import { sanitizeHref, sanitizeImageSrc } from "../sanitize"
|
|
2
|
+
import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from "../types"
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
5
|
* Atlassian Document Format (ADF) renderer — for Jira and Confluence.
|
|
@@ -14,15 +8,13 @@ import type {
|
|
|
14
8
|
*/
|
|
15
9
|
|
|
16
10
|
function resolveColumn(col: string | TableColumn): TableColumn {
|
|
17
|
-
return typeof col ===
|
|
11
|
+
return typeof col === "string" ? { header: col } : col
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
function getTextContent(children: DocChild[]): string {
|
|
21
15
|
return children
|
|
22
|
-
.map((c) =>
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
.join('')
|
|
16
|
+
.map((c) => (typeof c === "string" ? c : getTextContent((c as DocNode).children)))
|
|
17
|
+
.join("")
|
|
26
18
|
}
|
|
27
19
|
|
|
28
20
|
interface AdfNode {
|
|
@@ -33,8 +25,8 @@ interface AdfNode {
|
|
|
33
25
|
attrs?: Record<string, unknown>
|
|
34
26
|
}
|
|
35
27
|
|
|
36
|
-
function textNode(text: string, marks?: AdfNode[
|
|
37
|
-
return { type:
|
|
28
|
+
function textNode(text: string, marks?: AdfNode["marks"]): AdfNode {
|
|
29
|
+
return { type: "text", text, ...(marks && marks.length > 0 ? { marks } : {}) }
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
function nodeToAdf(node: DocNode): AdfNode[] {
|
|
@@ -42,66 +34,65 @@ function nodeToAdf(node: DocNode): AdfNode[] {
|
|
|
42
34
|
const result: AdfNode[] = []
|
|
43
35
|
|
|
44
36
|
switch (node.type) {
|
|
45
|
-
case
|
|
46
|
-
case
|
|
47
|
-
case
|
|
48
|
-
case
|
|
49
|
-
case
|
|
37
|
+
case "document":
|
|
38
|
+
case "page":
|
|
39
|
+
case "section":
|
|
40
|
+
case "row":
|
|
41
|
+
case "column":
|
|
50
42
|
for (const child of node.children) {
|
|
51
|
-
if (typeof child !==
|
|
43
|
+
if (typeof child !== "string") {
|
|
52
44
|
result.push(...nodeToAdf(child))
|
|
53
45
|
}
|
|
54
46
|
}
|
|
55
47
|
break
|
|
56
48
|
|
|
57
|
-
case
|
|
49
|
+
case "heading": {
|
|
58
50
|
const level = Math.min(Math.max((p.level as number) ?? 1, 1), 6)
|
|
59
51
|
const text = getTextContent(node.children)
|
|
60
52
|
result.push({
|
|
61
|
-
type:
|
|
53
|
+
type: "heading",
|
|
62
54
|
attrs: { level },
|
|
63
|
-
content: [textNode(text, [{ type:
|
|
55
|
+
content: [textNode(text, [{ type: "strong" }])],
|
|
64
56
|
})
|
|
65
57
|
break
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
case
|
|
60
|
+
case "text": {
|
|
69
61
|
const text = getTextContent(node.children)
|
|
70
|
-
const marks: AdfNode[
|
|
71
|
-
if (p.bold) marks.push({ type:
|
|
72
|
-
if (p.italic) marks.push({ type:
|
|
73
|
-
if (p.underline) marks.push({ type:
|
|
74
|
-
if (p.strikethrough) marks.push({ type:
|
|
75
|
-
if (p.color)
|
|
76
|
-
marks.push({ type: 'textColor', attrs: { color: p.color as string } })
|
|
62
|
+
const marks: AdfNode["marks"] = []
|
|
63
|
+
if (p.bold) marks.push({ type: "strong" })
|
|
64
|
+
if (p.italic) marks.push({ type: "em" })
|
|
65
|
+
if (p.underline) marks.push({ type: "underline" })
|
|
66
|
+
if (p.strikethrough) marks.push({ type: "strike" })
|
|
67
|
+
if (p.color) marks.push({ type: "textColor", attrs: { color: p.color as string } })
|
|
77
68
|
result.push({
|
|
78
|
-
type:
|
|
69
|
+
type: "paragraph",
|
|
79
70
|
content: [textNode(text, marks)],
|
|
80
71
|
})
|
|
81
72
|
break
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
case
|
|
75
|
+
case "link": {
|
|
85
76
|
const href = sanitizeHref(p.href as string)
|
|
86
77
|
const text = getTextContent(node.children)
|
|
87
78
|
result.push({
|
|
88
|
-
type:
|
|
89
|
-
content: [textNode(text, [{ type:
|
|
79
|
+
type: "paragraph",
|
|
80
|
+
content: [textNode(text, [{ type: "link", attrs: { href } }])],
|
|
90
81
|
})
|
|
91
82
|
break
|
|
92
83
|
}
|
|
93
84
|
|
|
94
|
-
case
|
|
85
|
+
case "image": {
|
|
95
86
|
const src = sanitizeImageSrc(p.src as string)
|
|
96
|
-
if (src.startsWith(
|
|
87
|
+
if (src.startsWith("http")) {
|
|
97
88
|
result.push({
|
|
98
|
-
type:
|
|
99
|
-
attrs: { layout:
|
|
89
|
+
type: "mediaSingle",
|
|
90
|
+
attrs: { layout: "center" },
|
|
100
91
|
content: [
|
|
101
92
|
{
|
|
102
|
-
type:
|
|
93
|
+
type: "media",
|
|
103
94
|
attrs: {
|
|
104
|
-
type:
|
|
95
|
+
type: "external",
|
|
105
96
|
url: src,
|
|
106
97
|
width: (p.width as number) ?? undefined,
|
|
107
98
|
height: (p.height as number) ?? undefined,
|
|
@@ -113,56 +104,54 @@ function nodeToAdf(node: DocNode): AdfNode[] {
|
|
|
113
104
|
break
|
|
114
105
|
}
|
|
115
106
|
|
|
116
|
-
case
|
|
117
|
-
const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(
|
|
118
|
-
resolveColumn,
|
|
119
|
-
)
|
|
107
|
+
case "table": {
|
|
108
|
+
const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
|
|
120
109
|
const rows = (p.rows ?? []) as (string | number)[][]
|
|
121
110
|
|
|
122
111
|
const headerRow: AdfNode = {
|
|
123
|
-
type:
|
|
112
|
+
type: "tableRow",
|
|
124
113
|
content: columns.map((col) => ({
|
|
125
|
-
type:
|
|
114
|
+
type: "tableHeader",
|
|
126
115
|
content: [
|
|
127
116
|
{
|
|
128
|
-
type:
|
|
129
|
-
content: [textNode(col.header, [{ type:
|
|
117
|
+
type: "paragraph",
|
|
118
|
+
content: [textNode(col.header, [{ type: "strong" }])],
|
|
130
119
|
},
|
|
131
120
|
],
|
|
132
121
|
})),
|
|
133
122
|
}
|
|
134
123
|
|
|
135
124
|
const dataRows = rows.map((row) => ({
|
|
136
|
-
type:
|
|
125
|
+
type: "tableRow" as const,
|
|
137
126
|
content: columns.map((_, i) => ({
|
|
138
|
-
type:
|
|
127
|
+
type: "tableCell" as const,
|
|
139
128
|
content: [
|
|
140
129
|
{
|
|
141
|
-
type:
|
|
142
|
-
content: [textNode(String(row[i] ??
|
|
130
|
+
type: "paragraph" as const,
|
|
131
|
+
content: [textNode(String(row[i] ?? ""))],
|
|
143
132
|
},
|
|
144
133
|
],
|
|
145
134
|
})),
|
|
146
135
|
}))
|
|
147
136
|
|
|
148
137
|
result.push({
|
|
149
|
-
type:
|
|
150
|
-
attrs: { isNumberColumnEnabled: false, layout:
|
|
138
|
+
type: "table",
|
|
139
|
+
attrs: { isNumberColumnEnabled: false, layout: "default" },
|
|
151
140
|
content: [headerRow, ...dataRows],
|
|
152
141
|
})
|
|
153
142
|
break
|
|
154
143
|
}
|
|
155
144
|
|
|
156
|
-
case
|
|
145
|
+
case "list": {
|
|
157
146
|
const ordered = p.ordered as boolean | undefined
|
|
158
|
-
const type = ordered ?
|
|
147
|
+
const type = ordered ? "orderedList" : "bulletList"
|
|
159
148
|
const items = node.children
|
|
160
|
-
.filter((c): c is DocNode => typeof c !==
|
|
149
|
+
.filter((c): c is DocNode => typeof c !== "string")
|
|
161
150
|
.map((item) => ({
|
|
162
|
-
type:
|
|
151
|
+
type: "listItem" as const,
|
|
163
152
|
content: [
|
|
164
153
|
{
|
|
165
|
-
type:
|
|
154
|
+
type: "paragraph" as const,
|
|
166
155
|
content: [textNode(getTextContent(item.children))],
|
|
167
156
|
},
|
|
168
157
|
],
|
|
@@ -171,46 +160,41 @@ function nodeToAdf(node: DocNode): AdfNode[] {
|
|
|
171
160
|
break
|
|
172
161
|
}
|
|
173
162
|
|
|
174
|
-
case
|
|
163
|
+
case "code": {
|
|
175
164
|
const text = getTextContent(node.children)
|
|
176
165
|
const lang = (p.language as string) ?? null
|
|
177
166
|
result.push({
|
|
178
|
-
type:
|
|
167
|
+
type: "codeBlock",
|
|
179
168
|
attrs: { language: lang },
|
|
180
169
|
content: [textNode(text)],
|
|
181
170
|
})
|
|
182
171
|
break
|
|
183
172
|
}
|
|
184
173
|
|
|
185
|
-
case
|
|
186
|
-
case
|
|
187
|
-
result.push({ type:
|
|
174
|
+
case "divider":
|
|
175
|
+
case "page-break":
|
|
176
|
+
result.push({ type: "rule" })
|
|
188
177
|
break
|
|
189
178
|
|
|
190
|
-
case
|
|
191
|
-
result.push({ type:
|
|
179
|
+
case "spacer":
|
|
180
|
+
result.push({ type: "paragraph", content: [] })
|
|
192
181
|
break
|
|
193
182
|
|
|
194
|
-
case
|
|
183
|
+
case "button": {
|
|
195
184
|
const href = sanitizeHref(p.href as string)
|
|
196
185
|
const text = getTextContent(node.children)
|
|
197
186
|
result.push({
|
|
198
|
-
type:
|
|
199
|
-
content: [
|
|
200
|
-
textNode(text, [
|
|
201
|
-
{ type: 'link', attrs: { href } },
|
|
202
|
-
{ type: 'strong' },
|
|
203
|
-
]),
|
|
204
|
-
],
|
|
187
|
+
type: "paragraph",
|
|
188
|
+
content: [textNode(text, [{ type: "link", attrs: { href } }, { type: "strong" }])],
|
|
205
189
|
})
|
|
206
190
|
break
|
|
207
191
|
}
|
|
208
192
|
|
|
209
|
-
case
|
|
193
|
+
case "quote": {
|
|
210
194
|
const text = getTextContent(node.children)
|
|
211
195
|
result.push({
|
|
212
|
-
type:
|
|
213
|
-
content: [{ type:
|
|
196
|
+
type: "blockquote",
|
|
197
|
+
content: [{ type: "paragraph", content: [textNode(text)] }],
|
|
214
198
|
})
|
|
215
199
|
break
|
|
216
200
|
}
|
|
@@ -224,7 +208,7 @@ export const confluenceRenderer: DocumentRenderer = {
|
|
|
224
208
|
const content = nodeToAdf(node)
|
|
225
209
|
const adf = {
|
|
226
210
|
version: 1,
|
|
227
|
-
type:
|
|
211
|
+
type: "doc",
|
|
228
212
|
content,
|
|
229
213
|
}
|
|
230
214
|
return JSON.stringify(adf, null, 2)
|
package/src/renderers/csv.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DocNode,
|
|
3
|
-
DocumentRenderer,
|
|
4
|
-
RenderOptions,
|
|
5
|
-
TableColumn,
|
|
6
|
-
} from '../types'
|
|
1
|
+
import type { DocNode, DocumentRenderer, RenderOptions, TableColumn } from "../types"
|
|
7
2
|
|
|
8
3
|
function resolveColumn(col: string | TableColumn): TableColumn {
|
|
9
|
-
return typeof col ===
|
|
4
|
+
return typeof col === "string" ? { header: col } : col
|
|
10
5
|
}
|
|
11
6
|
|
|
12
7
|
function escapeCsv(value: string): string {
|
|
13
|
-
if (value.includes(
|
|
8
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
14
9
|
return `"${value.replace(/"/g, '""')}"`
|
|
15
10
|
}
|
|
16
11
|
return value
|
|
@@ -18,11 +13,11 @@ function escapeCsv(value: string): string {
|
|
|
18
13
|
|
|
19
14
|
function findTables(node: DocNode): DocNode[] {
|
|
20
15
|
const tables: DocNode[] = []
|
|
21
|
-
if (node.type ===
|
|
16
|
+
if (node.type === "table") {
|
|
22
17
|
tables.push(node)
|
|
23
18
|
}
|
|
24
19
|
for (const child of node.children) {
|
|
25
|
-
if (typeof child !==
|
|
20
|
+
if (typeof child !== "string") {
|
|
26
21
|
tables.push(...findTables(child))
|
|
27
22
|
}
|
|
28
23
|
}
|
|
@@ -30,9 +25,7 @@ function findTables(node: DocNode): DocNode[] {
|
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
function tableToCsv(node: DocNode): string {
|
|
33
|
-
const columns = ((node.props.columns ?? []) as (string | TableColumn)[]).map(
|
|
34
|
-
resolveColumn,
|
|
35
|
-
)
|
|
28
|
+
const columns = ((node.props.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
|
|
36
29
|
const rows = (node.props.rows ?? []) as (string | number)[][]
|
|
37
30
|
|
|
38
31
|
const lines: string[] = []
|
|
@@ -43,14 +36,14 @@ function tableToCsv(node: DocNode): string {
|
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
// Header
|
|
46
|
-
lines.push(columns.map((c) => escapeCsv(c.header)).join(
|
|
39
|
+
lines.push(columns.map((c) => escapeCsv(c.header)).join(","))
|
|
47
40
|
|
|
48
41
|
// Rows
|
|
49
42
|
for (const row of rows) {
|
|
50
|
-
lines.push(row.map((cell) => escapeCsv(String(cell ??
|
|
43
|
+
lines.push(row.map((cell) => escapeCsv(String(cell ?? ""))).join(","))
|
|
51
44
|
}
|
|
52
45
|
|
|
53
|
-
return lines.join(
|
|
46
|
+
return lines.join("\n")
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
export const csvRenderer: DocumentRenderer = {
|
|
@@ -58,10 +51,10 @@ export const csvRenderer: DocumentRenderer = {
|
|
|
58
51
|
const tables = findTables(node)
|
|
59
52
|
|
|
60
53
|
if (tables.length === 0) {
|
|
61
|
-
return
|
|
54
|
+
return "# No tables found in document\n"
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
// If multiple tables, separate with blank lines
|
|
65
|
-
return `${tables.map(tableToCsv).join(
|
|
58
|
+
return `${tables.map(tableToCsv).join("\n\n")}\n`
|
|
66
59
|
},
|
|
67
60
|
}
|
package/src/renderers/discord.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import { sanitizeHref, sanitizeImageSrc } from
|
|
2
|
-
import type {
|
|
3
|
-
DocChild,
|
|
4
|
-
DocNode,
|
|
5
|
-
DocumentRenderer,
|
|
6
|
-
RenderOptions,
|
|
7
|
-
TableColumn,
|
|
8
|
-
} from '../types'
|
|
1
|
+
import { sanitizeHref, sanitizeImageSrc } from "../sanitize"
|
|
2
|
+
import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from "../types"
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
5
|
* Discord renderer — outputs embed JSON for Discord webhooks/bots.
|
|
@@ -13,15 +7,13 @@ import type {
|
|
|
13
7
|
*/
|
|
14
8
|
|
|
15
9
|
function resolveColumn(col: string | TableColumn): TableColumn {
|
|
16
|
-
return typeof col ===
|
|
10
|
+
return typeof col === "string" ? { header: col } : col
|
|
17
11
|
}
|
|
18
12
|
|
|
19
13
|
function getTextContent(children: DocChild[]): string {
|
|
20
14
|
return children
|
|
21
|
-
.map((c) =>
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
.join('')
|
|
15
|
+
.map((c) => (typeof c === "string" ? c : getTextContent((c as DocNode).children)))
|
|
16
|
+
.join("")
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
interface DiscordField {
|
|
@@ -32,16 +24,16 @@ interface DiscordField {
|
|
|
32
24
|
|
|
33
25
|
/** Extract the first h1 title and first HTTP image from the tree. */
|
|
34
26
|
function extractMeta(node: DocNode): { title?: string; imageUrl?: string } {
|
|
35
|
-
if (node.type ===
|
|
27
|
+
if (node.type === "heading") {
|
|
36
28
|
const level = (node.props.level as number) ?? 1
|
|
37
29
|
if (level === 1) return { title: getTextContent(node.children) }
|
|
38
30
|
}
|
|
39
|
-
if (node.type ===
|
|
31
|
+
if (node.type === "image") {
|
|
40
32
|
const src = sanitizeImageSrc(node.props.src as string)
|
|
41
|
-
if (src.startsWith(
|
|
33
|
+
if (src.startsWith("http")) return { imageUrl: src }
|
|
42
34
|
}
|
|
43
35
|
for (const child of node.children) {
|
|
44
|
-
if (typeof child !==
|
|
36
|
+
if (typeof child !== "string") {
|
|
45
37
|
const result = extractMeta(child)
|
|
46
38
|
if (result.title || result.imageUrl) return result
|
|
47
39
|
}
|
|
@@ -54,17 +46,17 @@ function nodeToMarkdown(
|
|
|
54
46
|
meta: { title?: string },
|
|
55
47
|
): { content: string; fields: DiscordField[] } {
|
|
56
48
|
const p = node.props
|
|
57
|
-
let content =
|
|
49
|
+
let content = ""
|
|
58
50
|
const fields: DiscordField[] = []
|
|
59
51
|
|
|
60
52
|
switch (node.type) {
|
|
61
|
-
case
|
|
62
|
-
case
|
|
63
|
-
case
|
|
64
|
-
case
|
|
65
|
-
case
|
|
53
|
+
case "document":
|
|
54
|
+
case "page":
|
|
55
|
+
case "section":
|
|
56
|
+
case "row":
|
|
57
|
+
case "column":
|
|
66
58
|
for (const child of node.children) {
|
|
67
|
-
if (typeof child !==
|
|
59
|
+
if (typeof child !== "string") {
|
|
68
60
|
const result = nodeToMarkdown(child, meta)
|
|
69
61
|
content += result.content
|
|
70
62
|
fields.push(...result.fields)
|
|
@@ -72,7 +64,7 @@ function nodeToMarkdown(
|
|
|
72
64
|
}
|
|
73
65
|
break
|
|
74
66
|
|
|
75
|
-
case
|
|
67
|
+
case "heading": {
|
|
76
68
|
const text = getTextContent(node.children)
|
|
77
69
|
const level = (p.level as number) ?? 1
|
|
78
70
|
// Skip the first h1 — it's used as embed title
|
|
@@ -83,7 +75,7 @@ function nodeToMarkdown(
|
|
|
83
75
|
break
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
case
|
|
78
|
+
case "text": {
|
|
87
79
|
let text = getTextContent(node.children)
|
|
88
80
|
if (p.bold) text = `**${text}**`
|
|
89
81
|
if (p.italic) text = `*${text}*`
|
|
@@ -92,79 +84,75 @@ function nodeToMarkdown(
|
|
|
92
84
|
break
|
|
93
85
|
}
|
|
94
86
|
|
|
95
|
-
case
|
|
87
|
+
case "link": {
|
|
96
88
|
const href = sanitizeHref(p.href as string)
|
|
97
89
|
const text = getTextContent(node.children)
|
|
98
90
|
content += `[${text}](${href})\n\n`
|
|
99
91
|
break
|
|
100
92
|
}
|
|
101
93
|
|
|
102
|
-
case
|
|
94
|
+
case "image":
|
|
103
95
|
// Image handled via extractMeta — embedded as embed.image
|
|
104
96
|
break
|
|
105
97
|
|
|
106
|
-
case
|
|
107
|
-
const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(
|
|
108
|
-
resolveColumn,
|
|
109
|
-
)
|
|
98
|
+
case "table": {
|
|
99
|
+
const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
|
|
110
100
|
const rows = (p.rows ?? []) as (string | number)[][]
|
|
111
101
|
|
|
112
102
|
// Use Discord embed fields for small tables
|
|
113
103
|
if (columns.length <= 3 && rows.length <= 10) {
|
|
114
104
|
for (const col of columns) {
|
|
115
105
|
const colIdx = columns.indexOf(col)
|
|
116
|
-
const values = rows.map((row) => String(row[colIdx] ??
|
|
106
|
+
const values = rows.map((row) => String(row[colIdx] ?? "")).join("\n")
|
|
117
107
|
fields.push({
|
|
118
108
|
name: col.header,
|
|
119
|
-
value: values ||
|
|
109
|
+
value: values || "-",
|
|
120
110
|
inline: true,
|
|
121
111
|
})
|
|
122
112
|
}
|
|
123
113
|
} else {
|
|
124
114
|
// Fallback to code block for large tables
|
|
125
|
-
const header = columns.map((c) => c.header).join(
|
|
126
|
-
const separator = columns.map(() =>
|
|
127
|
-
const body = rows
|
|
128
|
-
.map((row) => row.map((c) => String(c ?? '')).join(' | '))
|
|
129
|
-
.join('\n')
|
|
115
|
+
const header = columns.map((c) => c.header).join(" | ")
|
|
116
|
+
const separator = columns.map(() => "---").join(" | ")
|
|
117
|
+
const body = rows.map((row) => row.map((c) => String(c ?? "")).join(" | ")).join("\n")
|
|
130
118
|
content += `\`\`\`\n${header}\n${separator}\n${body}\n\`\`\`\n\n`
|
|
131
119
|
}
|
|
132
120
|
break
|
|
133
121
|
}
|
|
134
122
|
|
|
135
|
-
case
|
|
123
|
+
case "list": {
|
|
136
124
|
const ordered = p.ordered as boolean | undefined
|
|
137
125
|
const items = node.children
|
|
138
|
-
.filter((c): c is DocNode => typeof c !==
|
|
126
|
+
.filter((c): c is DocNode => typeof c !== "string")
|
|
139
127
|
.map((item, i) => {
|
|
140
|
-
const prefix = ordered ? `${i + 1}.` :
|
|
128
|
+
const prefix = ordered ? `${i + 1}.` : "•"
|
|
141
129
|
return `${prefix} ${getTextContent(item.children)}`
|
|
142
130
|
})
|
|
143
|
-
.join(
|
|
131
|
+
.join("\n")
|
|
144
132
|
content += `${items}\n\n`
|
|
145
133
|
break
|
|
146
134
|
}
|
|
147
135
|
|
|
148
|
-
case
|
|
149
|
-
const lang = (p.language as string) ??
|
|
136
|
+
case "code": {
|
|
137
|
+
const lang = (p.language as string) ?? ""
|
|
150
138
|
const text = getTextContent(node.children)
|
|
151
139
|
content += `\`\`\`${lang}\n${text}\n\`\`\`\n\n`
|
|
152
140
|
break
|
|
153
141
|
}
|
|
154
142
|
|
|
155
|
-
case
|
|
156
|
-
case
|
|
157
|
-
content +=
|
|
143
|
+
case "divider":
|
|
144
|
+
case "page-break":
|
|
145
|
+
content += "───────────\n\n"
|
|
158
146
|
break
|
|
159
147
|
|
|
160
|
-
case
|
|
148
|
+
case "button": {
|
|
161
149
|
const href = sanitizeHref(p.href as string)
|
|
162
150
|
const text = getTextContent(node.children)
|
|
163
151
|
content += `[**${text}**](${href})\n\n`
|
|
164
152
|
break
|
|
165
153
|
}
|
|
166
154
|
|
|
167
|
-
case
|
|
155
|
+
case "quote": {
|
|
168
156
|
const text = getTextContent(node.children)
|
|
169
157
|
content += `> ${text}\n\n`
|
|
170
158
|
break
|