@pyreon/document 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/confluence-Bd3ua1Ut.js.map +1 -1
  3. package/lib/csv-COrS4qdy.js.map +1 -1
  4. package/lib/discord-BLUnkEh9.js.map +1 -1
  5. package/lib/{dist-BsqdI2nY.js → dist-CYL41kqQ.js} +2 -2
  6. package/lib/dist-CYL41kqQ.js.map +1 -0
  7. package/lib/{docx-BEBOihjl.js → docx-uNAel545.js} +7 -2
  8. package/lib/docx-uNAel545.js.map +1 -0
  9. package/lib/email-D0bbfWq4.js.map +1 -1
  10. package/lib/{exceljs-BoIDUUaw.js → exceljs-BYETsesT.js} +314 -314
  11. package/lib/exceljs-BYETsesT.js.map +1 -0
  12. package/lib/google-chat-CkKCBUWC.js.map +1 -1
  13. package/lib/html-B5biprN2.js.map +1 -1
  14. package/lib/index.js +17 -8
  15. package/lib/index.js.map +1 -1
  16. package/lib/markdown-CdtlFGC0.js.map +1 -1
  17. package/lib/notion-iG2C5bEY.js.map +1 -1
  18. package/lib/{pdf-DIUQUEdj.js → pdf-IuBgTb3T.js} +9 -3
  19. package/lib/pdf-IuBgTb3T.js.map +1 -0
  20. package/lib/{pdfmake-DnmLxK4Q.js → pdfmake-CKMX5URW.js} +2 -4
  21. package/lib/pdfmake-CKMX5URW.js.map +1 -0
  22. package/lib/{pptx-Dd33oL3_.js → pptx-DXiMiYFM.js} +7 -2
  23. package/lib/pptx-DXiMiYFM.js.map +1 -0
  24. package/lib/{pptxgen.es-COcgXsyx.js → pptxgen.es-FsqHs8mD.js} +3 -6
  25. package/lib/pptxgen.es-FsqHs8mD.js.map +1 -0
  26. package/lib/sanitize-O_3j1mNJ.js.map +1 -1
  27. package/lib/slack-BI3EQwYm.js.map +1 -1
  28. package/lib/svg-BKxumy-p.js.map +1 -1
  29. package/lib/teams-Cwz9lce0.js.map +1 -1
  30. package/lib/telegram-gYFqyMXb.js.map +1 -1
  31. package/lib/text-l1XNXBOC.js.map +1 -1
  32. package/lib/types/index.d.ts +43 -39
  33. package/lib/types/index.d.ts.map +1 -1
  34. package/lib/{vfs_fonts-Df1kkZ4Y.js → vfs_fonts-Cap07Jg3.js} +2 -2
  35. package/lib/vfs_fonts-Cap07Jg3.js.map +1 -0
  36. package/lib/whatsapp-CjSGoOKx.js.map +1 -1
  37. package/lib/{xlsx-Bb5TWyXQ.js → xlsx-Cvu4LBNy.js} +8 -2
  38. package/lib/xlsx-Cvu4LBNy.js.map +1 -0
  39. package/package.json +19 -7
  40. package/src/builder.ts +53 -44
  41. package/src/download.ts +32 -36
  42. package/src/env.d.ts +3 -17
  43. package/src/index.ts +6 -8
  44. package/src/nodes.ts +45 -45
  45. package/src/render.ts +45 -118
  46. package/src/renderers/confluence.ts +64 -80
  47. package/src/renderers/csv.ts +11 -18
  48. package/src/renderers/discord.ts +38 -50
  49. package/src/renderers/docx.ts +78 -120
  50. package/src/renderers/email.ts +73 -92
  51. package/src/renderers/google-chat.ts +35 -47
  52. package/src/renderers/html.ts +78 -101
  53. package/src/renderers/markdown.ts +43 -53
  54. package/src/renderers/notion.ts +63 -85
  55. package/src/renderers/pdf.ts +92 -115
  56. package/src/renderers/pptx.ts +60 -66
  57. package/src/renderers/slack.ts +49 -61
  58. package/src/renderers/svg.ts +49 -63
  59. package/src/renderers/teams.ts +68 -80
  60. package/src/renderers/telegram.ts +40 -54
  61. package/src/renderers/text.ts +44 -66
  62. package/src/renderers/whatsapp.ts +34 -48
  63. package/src/renderers/xlsx.ts +47 -61
  64. package/src/sanitize.ts +21 -25
  65. package/src/tests/document.test.ts +1337 -1385
  66. package/src/tests/stress.test.ts +111 -111
  67. package/src/types.ts +66 -65
  68. package/lib/dist-BsqdI2nY.js.map +0 -1
  69. package/lib/docx-BEBOihjl.js.map +0 -1
  70. package/lib/exceljs-BoIDUUaw.js.map +0 -1
  71. package/lib/pdf-DIUQUEdj.js.map +0 -1
  72. package/lib/pdfmake-DnmLxK4Q.js.map +0 -1
  73. package/lib/pptx-Dd33oL3_.js.map +0 -1
  74. package/lib/pptxgen.es-COcgXsyx.js.map +0 -1
  75. package/lib/vfs_fonts-Df1kkZ4Y.js.map +0 -1
  76. package/lib/xlsx-Bb5TWyXQ.js.map +0 -1
@@ -1,11 +1,5 @@
1
- import { sanitizeHref, sanitizeImageSrc, sanitizeXmlColor } from '../sanitize'
2
- import type {
3
- DocChild,
4
- DocNode,
5
- DocumentRenderer,
6
- RenderOptions,
7
- TableColumn,
8
- } from '../types'
1
+ import { sanitizeHref, sanitizeImageSrc, sanitizeXmlColor } from "../sanitize"
2
+ import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from "../types"
9
3
 
10
4
  /**
11
5
  * PPTX renderer — lazy-loads pptxgenjs on first use.
@@ -27,15 +21,13 @@ import type {
27
21
  */
28
22
 
29
23
  function resolveColumn(col: string | TableColumn): TableColumn {
30
- return typeof col === 'string' ? { header: col } : col
24
+ return typeof col === "string" ? { header: col } : col
31
25
  }
32
26
 
33
27
  function getTextContent(children: DocChild[]): string {
34
28
  return children
35
- .map((c) =>
36
- typeof c === 'string' ? c : getTextContent((c as DocNode).children),
37
- )
38
- .join('')
29
+ .map((c) => (typeof c === "string" ? c : getTextContent((c as DocNode).children)))
30
+ .join("")
39
31
  }
40
32
 
41
33
  /** Vertical position tracker for placing elements on a slide. */
@@ -81,7 +73,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
81
73
  const p = node.props
82
74
 
83
75
  switch (node.type) {
84
- case 'heading': {
76
+ case "heading": {
85
77
  const level = (p.level as number) ?? 1
86
78
  const fontSize = HEADING_SIZES[level] ?? 20
87
79
  ctx.slide.addText(getTextContent(node.children), {
@@ -91,14 +83,14 @@ function processNode(node: DocNode, ctx: SlideContext): void {
91
83
  h: 0.6,
92
84
  fontSize,
93
85
  bold: true,
94
- color: sanitizeXmlColor((p.color as string) ?? '#000000'),
95
- align: (p.align as string) ?? 'left',
86
+ color: sanitizeXmlColor((p.color as string) ?? "#000000"),
87
+ align: (p.align as string) ?? "left",
96
88
  })
97
89
  ctx.y += 0.7
98
90
  break
99
91
  }
100
92
 
101
- case 'text': {
93
+ case "text": {
102
94
  const text = getTextContent(node.children)
103
95
  ctx.slide.addText(text, {
104
96
  x: CONTENT_MARGIN,
@@ -108,21 +100,21 @@ function processNode(node: DocNode, ctx: SlideContext): void {
108
100
  fontSize: (p.size as number) ?? 14,
109
101
  bold: p.bold ?? false,
110
102
  italic: p.italic ?? false,
111
- underline: p.underline ? { style: 'sng' } : undefined,
112
- strike: p.strikethrough ? 'sngStrike' : undefined,
113
- color: sanitizeXmlColor((p.color as string) ?? '#333333'),
114
- align: (p.align as string) ?? 'left',
103
+ underline: p.underline ? { style: "sng" } : undefined,
104
+ strike: p.strikethrough ? "sngStrike" : undefined,
105
+ color: sanitizeXmlColor((p.color as string) ?? "#333333"),
106
+ align: (p.align as string) ?? "left",
115
107
  })
116
108
  ctx.y += 0.5
117
109
  break
118
110
  }
119
111
 
120
- case 'image': {
112
+ case "image": {
121
113
  const src = sanitizeImageSrc(p.src as string)
122
114
  const w = Math.min(((p.width as number) ?? 400) / 96, CONTENT_WIDTH)
123
115
  const h = ((p.height as number) ?? 300) / 96
124
116
 
125
- if (src.startsWith('data:')) {
117
+ if (src.startsWith("data:")) {
126
118
  ctx.slide.addImage({
127
119
  data: src,
128
120
  x: CONTENT_MARGIN,
@@ -136,34 +128,29 @@ function processNode(node: DocNode, ctx: SlideContext): void {
136
128
  break
137
129
  }
138
130
 
139
- case 'table': {
140
- const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(
141
- resolveColumn,
142
- )
131
+ case "table": {
132
+ const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
143
133
  const rows = (p.rows ?? []) as (string | number)[][]
144
- const hs = p.headerStyle as
145
- | { background?: string; color?: string }
146
- | undefined
134
+ const hs = p.headerStyle as { background?: string; color?: string } | undefined
147
135
 
148
136
  const headerRow = columns.map((col) => ({
149
137
  text: col.header,
150
138
  options: {
151
139
  bold: true,
152
- fill: { color: sanitizeXmlColor(hs?.background ?? '#f5f5f5') },
153
- color: sanitizeXmlColor(hs?.color ?? '#000000'),
154
- align: col.align ?? 'left',
140
+ fill: { color: sanitizeXmlColor(hs?.background ?? "#f5f5f5") },
141
+ color: sanitizeXmlColor(hs?.color ?? "#000000"),
142
+ align: col.align ?? "left",
155
143
  fontSize: 12,
156
144
  },
157
145
  }))
158
146
 
159
147
  const dataRows = rows.map((row, rowIdx) =>
160
148
  columns.map((col, colIdx) => ({
161
- text: String(row[colIdx] ?? ''),
149
+ text: String(row[colIdx] ?? ""),
162
150
  options: {
163
- align: col.align ?? 'left',
151
+ align: col.align ?? "left",
164
152
  fontSize: 11,
165
- fill:
166
- p.striped && rowIdx % 2 === 1 ? { color: 'F9F9F9' } : undefined,
153
+ fill: p.striped && rowIdx % 2 === 1 ? { color: "F9F9F9" } : undefined,
167
154
  },
168
155
  })),
169
156
  )
@@ -176,16 +163,16 @@ function processNode(node: DocNode, ctx: SlideContext): void {
176
163
  x: CONTENT_MARGIN,
177
164
  y: ctx.y,
178
165
  w: CONTENT_WIDTH,
179
- border: { pt: 0.5, color: 'DDDDDD' },
166
+ border: { pt: 0.5, color: "DDDDDD" },
180
167
  rowH: rowHeight,
181
168
  })
182
169
  ctx.y += tableHeight + 0.2
183
170
  break
184
171
  }
185
172
 
186
- case 'list': {
173
+ case "list": {
187
174
  const items = node.children
188
- .filter((c): c is DocNode => typeof c !== 'string')
175
+ .filter((c): c is DocNode => typeof c !== "string")
189
176
  .map((item) => getTextContent(item.children))
190
177
 
191
178
  const isOrdered = p.ordered as boolean
@@ -204,7 +191,7 @@ function processNode(node: DocNode, ctx: SlideContext): void {
204
191
  break
205
192
  }
206
193
 
207
- case 'code': {
194
+ case "code": {
208
195
  const text = getTextContent(node.children)
209
196
  ctx.slide.addText(text, {
210
197
  x: CONTENT_MARGIN,
@@ -212,15 +199,15 @@ function processNode(node: DocNode, ctx: SlideContext): void {
212
199
  w: CONTENT_WIDTH,
213
200
  h: 0.5,
214
201
  fontSize: 10,
215
- fontFace: 'Courier New',
216
- fill: { color: 'F5F5F5' },
217
- color: '333333',
202
+ fontFace: "Courier New",
203
+ fill: { color: "F5F5F5" },
204
+ color: "333333",
218
205
  })
219
206
  ctx.y += 0.6
220
207
  break
221
208
  }
222
209
 
223
- case 'quote': {
210
+ case "quote": {
224
211
  const text = getTextContent(node.children)
225
212
  ctx.slide.addText(text, {
226
213
  x: CONTENT_MARGIN + 0.3,
@@ -229,28 +216,28 @@ function processNode(node: DocNode, ctx: SlideContext): void {
229
216
  h: 0.5,
230
217
  fontSize: 13,
231
218
  italic: true,
232
- color: '555555',
219
+ color: "555555",
233
220
  })
234
221
  ctx.y += 0.6
235
222
  break
236
223
  }
237
224
 
238
- case 'link': {
225
+ case "link": {
239
226
  ctx.slide.addText(getTextContent(node.children), {
240
227
  x: CONTENT_MARGIN,
241
228
  y: ctx.y,
242
229
  w: CONTENT_WIDTH,
243
230
  h: 0.4,
244
231
  fontSize: 13,
245
- color: '4F46E5',
246
- underline: { style: 'sng' },
232
+ color: "4F46E5",
233
+ underline: { style: "sng" },
247
234
  hyperlink: { url: sanitizeHref(p.href as string) },
248
235
  })
249
236
  ctx.y += 0.5
250
237
  break
251
238
  }
252
239
 
253
- case 'button': {
240
+ case "button": {
254
241
  ctx.slide.addText(getTextContent(node.children), {
255
242
  x: CONTENT_MARGIN,
256
243
  y: ctx.y,
@@ -258,41 +245,41 @@ function processNode(node: DocNode, ctx: SlideContext): void {
258
245
  h: 0.5,
259
246
  fontSize: 14,
260
247
  bold: true,
261
- color: sanitizeXmlColor((p.color as string) ?? '#ffffff'),
248
+ color: sanitizeXmlColor((p.color as string) ?? "#ffffff"),
262
249
  fill: {
263
- color: sanitizeXmlColor((p.background as string) ?? '#4f46e5'),
250
+ color: sanitizeXmlColor((p.background as string) ?? "#4f46e5"),
264
251
  },
265
- align: 'center',
252
+ align: "center",
266
253
  hyperlink: { url: sanitizeHref(p.href as string) },
267
254
  })
268
255
  ctx.y += 0.6
269
256
  break
270
257
  }
271
258
 
272
- case 'spacer': {
259
+ case "spacer": {
273
260
  ctx.y += ((p.height as number) ?? 12) / 72
274
261
  break
275
262
  }
276
263
 
277
- case 'divider': {
264
+ case "divider": {
278
265
  // Render as a thin line using a text element with top border
279
- ctx.slide.addText('', {
266
+ ctx.slide.addText("", {
280
267
  x: CONTENT_MARGIN,
281
268
  y: ctx.y,
282
269
  w: CONTENT_WIDTH,
283
270
  h: 0.02,
284
- fill: { color: sanitizeXmlColor((p.color as string) ?? '#DDDDDD') },
271
+ fill: { color: sanitizeXmlColor((p.color as string) ?? "#DDDDDD") },
285
272
  })
286
273
  ctx.y += 0.2
287
274
  break
288
275
  }
289
276
 
290
277
  // Container types — recurse into children
291
- case 'section':
292
- case 'row':
293
- case 'column':
278
+ case "section":
279
+ case "row":
280
+ case "column":
294
281
  for (const child of node.children) {
295
- if (typeof child !== 'string') {
282
+ if (typeof child !== "string") {
296
283
  processNode(child, ctx)
297
284
  }
298
285
  }
@@ -308,7 +295,7 @@ function processSlide(pageNode: DocNode, pptx: PptxGen): void {
308
295
  const ctx: SlideContext = { slide, y: CONTENT_MARGIN }
309
296
 
310
297
  for (const child of pageNode.children) {
311
- if (typeof child !== 'string') {
298
+ if (typeof child !== "string") {
312
299
  processNode(child, ctx)
313
300
  }
314
301
  }
@@ -316,7 +303,14 @@ function processSlide(pageNode: DocNode, pptx: PptxGen): void {
316
303
 
317
304
  export const pptxRenderer: DocumentRenderer = {
318
305
  async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {
319
- const PptxGenJS = await import('pptxgenjs')
306
+ let PptxGenJS: any
307
+ try {
308
+ PptxGenJS = await import("pptxgenjs")
309
+ } catch {
310
+ throw new Error(
311
+ '[@pyreon/document] PPTX renderer requires "pptxgenjs" package. Install it: bun add pptxgenjs',
312
+ )
313
+ }
320
314
  const PptxGenClass = PptxGenJS.default ?? PptxGenJS
321
315
 
322
316
  const pptx = new PptxGenClass() as PptxGen
@@ -329,7 +323,7 @@ export const pptxRenderer: DocumentRenderer = {
329
323
  // Collect pages — each becomes a slide
330
324
  const pages: DocNode[] = []
331
325
  for (const child of node.children) {
332
- if (typeof child !== 'string' && child.type === 'page') {
326
+ if (typeof child !== "string" && child.type === "page") {
333
327
  pages.push(child)
334
328
  }
335
329
  }
@@ -337,7 +331,7 @@ export const pptxRenderer: DocumentRenderer = {
337
331
  // If no explicit pages, treat entire document content as one slide
338
332
  if (pages.length === 0) {
339
333
  const syntheticPage: DocNode = {
340
- type: 'page',
334
+ type: "page",
341
335
  props: {},
342
336
  children: node.children,
343
337
  }
@@ -348,7 +342,7 @@ export const pptxRenderer: DocumentRenderer = {
348
342
  processSlide(page, pptx)
349
343
  }
350
344
 
351
- const output = await pptx.write('arraybuffer')
345
+ const output = await pptx.write("arraybuffer")
352
346
  return new Uint8Array(output as ArrayBuffer)
353
347
  },
354
348
  }
@@ -1,11 +1,5 @@
1
- import { sanitizeHref, sanitizeImageSrc } from '../sanitize'
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
  * Slack Block Kit renderer — outputs JSON that can be posted via Slack's API.
@@ -13,15 +7,13 @@ import type {
13
7
  */
14
8
 
15
9
  function resolveColumn(col: string | TableColumn): TableColumn {
16
- return typeof col === 'string' ? { header: col } : 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
- typeof c === 'string' ? c : getTextContent((c as DocNode).children),
23
- )
24
- .join('')
15
+ .map((c) => (typeof c === "string" ? c : getTextContent((c as DocNode).children)))
16
+ .join("")
25
17
  }
26
18
 
27
19
  interface SlackBlock {
@@ -29,12 +21,12 @@ interface SlackBlock {
29
21
  [key: string]: unknown
30
22
  }
31
23
 
32
- function mrkdwn(text: string): { type: 'mrkdwn'; text: string } {
33
- return { type: 'mrkdwn', text }
24
+ function mrkdwn(text: string): { type: "mrkdwn"; text: string } {
25
+ return { type: "mrkdwn", text }
34
26
  }
35
27
 
36
- function plainText(text: string): { type: 'plain_text'; text: string } {
37
- return { type: 'plain_text', text }
28
+ function plainText(text: string): { type: "plain_text"; text: string } {
29
+ return { type: "plain_text", text }
38
30
  }
39
31
 
40
32
  function nodeToBlocks(node: DocNode): SlackBlock[] {
@@ -42,140 +34,136 @@ function nodeToBlocks(node: DocNode): SlackBlock[] {
42
34
  const blocks: SlackBlock[] = []
43
35
 
44
36
  switch (node.type) {
45
- case 'document':
46
- case 'page':
47
- case 'section':
48
- case 'row':
49
- case 'column':
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 !== 'string') {
43
+ if (typeof child !== "string") {
52
44
  blocks.push(...nodeToBlocks(child))
53
45
  }
54
46
  }
55
47
  break
56
48
 
57
- case 'heading':
49
+ case "heading":
58
50
  blocks.push({
59
- type: 'header',
51
+ type: "header",
60
52
  text: plainText(getTextContent(node.children)),
61
53
  })
62
54
  break
63
55
 
64
- case 'text': {
56
+ case "text": {
65
57
  let text = getTextContent(node.children)
66
58
  if (p.bold) text = `*${text}*`
67
59
  if (p.italic) text = `_${text}_`
68
60
  if (p.strikethrough) text = `~${text}~`
69
61
  blocks.push({
70
- type: 'section',
62
+ type: "section",
71
63
  text: mrkdwn(text),
72
64
  })
73
65
  break
74
66
  }
75
67
 
76
- case 'link': {
68
+ case "link": {
77
69
  const href = sanitizeHref(p.href as string)
78
70
  const text = getTextContent(node.children)
79
71
  blocks.push({
80
- type: 'section',
72
+ type: "section",
81
73
  text: mrkdwn(`<${href}|${text}>`),
82
74
  })
83
75
  break
84
76
  }
85
77
 
86
- case 'image': {
78
+ case "image": {
87
79
  const src = sanitizeImageSrc(p.src as string)
88
80
  // Slack only supports public URLs for images
89
- if (src.startsWith('http')) {
81
+ if (src.startsWith("http")) {
90
82
  blocks.push({
91
- type: 'image',
83
+ type: "image",
92
84
  image_url: src,
93
- alt_text: (p.alt as string) ?? 'Image',
85
+ alt_text: (p.alt as string) ?? "Image",
94
86
  ...(p.caption ? { title: plainText(p.caption as string) } : {}),
95
87
  })
96
88
  }
97
89
  break
98
90
  }
99
91
 
100
- case 'table': {
101
- const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(
102
- resolveColumn,
103
- )
92
+ case "table": {
93
+ const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)
104
94
  const rows = (p.rows ?? []) as (string | number)[][]
105
95
 
106
96
  // Slack doesn't have native tables — render as formatted text
107
- const header = columns.map((c) => `*${c.header}*`).join(' | ')
108
- const separator = columns.map(() => '---').join(' | ')
109
- const body = rows
110
- .map((row) => row.map((cell) => String(cell ?? '')).join(' | '))
111
- .join('\n')
97
+ const header = columns.map((c) => `*${c.header}*`).join(" | ")
98
+ const separator = columns.map(() => "---").join(" | ")
99
+ const body = rows.map((row) => row.map((cell) => String(cell ?? "")).join(" | ")).join("\n")
112
100
 
113
101
  let text = `${header}\n${separator}\n${body}`
114
102
  if (p.caption) text = `_${p.caption}_\n${text}`
115
103
 
116
104
  blocks.push({
117
- type: 'section',
105
+ type: "section",
118
106
  text: mrkdwn(`\`\`\`\n${text}\n\`\`\``),
119
107
  })
120
108
  break
121
109
  }
122
110
 
123
- case 'list': {
111
+ case "list": {
124
112
  const ordered = p.ordered as boolean | undefined
125
113
  const items = node.children
126
- .filter((c): c is DocNode => typeof c !== 'string')
114
+ .filter((c): c is DocNode => typeof c !== "string")
127
115
  .map((item, i) => {
128
- const prefix = ordered ? `${i + 1}.` : ''
116
+ const prefix = ordered ? `${i + 1}.` : ""
129
117
  return `${prefix} ${getTextContent(item.children)}`
130
118
  })
131
- .join('\n')
119
+ .join("\n")
132
120
  blocks.push({
133
- type: 'section',
121
+ type: "section",
134
122
  text: mrkdwn(items),
135
123
  })
136
124
  break
137
125
  }
138
126
 
139
- case 'code': {
127
+ case "code": {
140
128
  const text = getTextContent(node.children)
141
- const lang = (p.language as string) ?? ''
129
+ const lang = (p.language as string) ?? ""
142
130
  blocks.push({
143
- type: 'section',
131
+ type: "section",
144
132
  text: mrkdwn(`\`\`\`${lang}\n${text}\n\`\`\``),
145
133
  })
146
134
  break
147
135
  }
148
136
 
149
- case 'divider':
150
- case 'page-break':
151
- blocks.push({ type: 'divider' })
137
+ case "divider":
138
+ case "page-break":
139
+ blocks.push({ type: "divider" })
152
140
  break
153
141
 
154
- case 'spacer':
142
+ case "spacer":
155
143
  // No equivalent in Slack — skip
156
144
  break
157
145
 
158
- case 'button': {
146
+ case "button": {
159
147
  const href = sanitizeHref(p.href as string)
160
148
  const text = getTextContent(node.children)
161
149
  blocks.push({
162
- type: 'actions',
150
+ type: "actions",
163
151
  elements: [
164
152
  {
165
- type: 'button',
153
+ type: "button",
166
154
  text: plainText(text),
167
155
  url: href,
168
- style: 'primary',
156
+ style: "primary",
169
157
  },
170
158
  ],
171
159
  })
172
160
  break
173
161
  }
174
162
 
175
- case 'quote': {
163
+ case "quote": {
176
164
  const text = getTextContent(node.children)
177
165
  blocks.push({
178
- type: 'section',
166
+ type: "section",
179
167
  text: mrkdwn(`> ${text}`),
180
168
  })
181
169
  break