import { Link as PdfLink, Text, View } from '@react-pdf/renderer' import type { ListItem, Content as MdastContent, Root as MdastRoot, PhrasingContent } from 'mdast' import type { ReactElement, ReactNode } from 'react' import remarkGfm from 'remark-gfm' import remarkParse from 'remark-parse' import { unified } from 'unified' import { pdfStyles } from './theme' const HEADING_STYLES = [ pdfStyles.heading1, pdfStyles.heading2, pdfStyles.heading3, pdfStyles.heading4, pdfStyles.heading5, pdfStyles.heading6, ] export function parseMarkdownToMdast(markdown: string): MdastRoot { return unified().use(remarkParse).use(remarkGfm).parse(markdown) as MdastRoot } function mapInline(node: PhrasingContent, key: string): ReactNode { switch (node.type) { case 'text': return node.value case 'strong': return ( {node.children.map((child, index) => mapInline(child, `${key}-${index}`))} ) case 'emphasis': return ( {node.children.map((child, index) => mapInline(child, `${key}-${index}`))} ) case 'delete': return ( {node.children.map((child, index) => mapInline(child, `${key}-${index}`))} ) case 'inlineCode': return ( {node.value} ) case 'link': return ( {node.children.map((child, index) => mapInline(child, `${key}-${index}`))} ) case 'break': return '\n' default: return null } } function mapListItem( item: ListItem, ordered: boolean, start: number, index: number, key: string ): ReactElement { const marker = ordered ? `${start + index}.` : '•' return ( {marker} {item.children.map((child, childIndex) => mapBlock(child, `${key}-${childIndex}`, { tight: true }) )} ) } function mapBlock( node: MdastContent, key: string, { tight = false }: { tight?: boolean } = {} ): ReactElement | null { switch (node.type) { case 'heading': return ( {node.children.map((child, index) => mapInline(child, `${key}-${index}`))} ) case 'paragraph': return ( {node.children.map((child, index) => mapInline(child, `${key}-${index}`))} ) case 'list': { const ordered = node.ordered ?? false const start = node.start ?? 1 return ( {node.children.map((item, index) => mapListItem(item, ordered, start, index, `${key}-${index}`) )} ) } case 'blockquote': return ( {node.children.map((child, index) => mapBlock(child, `${key}-${index}`))} ) case 'thematicBreak': return case 'code': return ( {node.value} ) case 'table': { const [headerRow, ...bodyRows] = node.children return ( {headerRow.children.map((cell, cellIndex) => ( {cell.children.map((child, childIndex) => mapInline(child, `${key}-header-${cellIndex}-${childIndex}`) )} ))} {bodyRows.map((row, rowIndex) => ( {row.children.map((cell, cellIndex) => ( {cell.children.map((child, childIndex) => mapInline(child, `${key}-row-${rowIndex}-${cellIndex}-${childIndex}`) )} ))} ))} ) } default: return null } } export function markdownToPdfBlocks(markdown: string): ReactElement[] { const root = parseMarkdownToMdast(markdown) return root.children .map((child, index) => mapBlock(child, `md-${index}`)) .filter((element): element is ReactElement => element !== null) } export function NotebookPdfMarkdown({ markdown }: { markdown: string }) { return {markdownToPdfBlocks(markdown)} }