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)}
}