| 1 | import { type ComponentPropsWithoutRef, type JSX } from "react"; |
| 2 | import { type Components as MemoComponents } from "rehype-react"; |
| 3 | import { type Components as ReactMarkdownComponents } from "react-markdown"; |
| 4 | import { type Components as StreamdownComponents } from "streamdown"; |
| 5 | |
| 6 | type MarkdownProps<Tag extends keyof JSX.IntrinsicElements> = ComponentPropsWithoutRef<Tag> & { |
| 7 | node?: unknown; |
| 8 | }; |
| 9 | |
| 10 | function randomHighlightColor() { |
| 11 | return `hsl(${Math.floor(Math.random() * 360)} 95% 78%)`; |
| 12 | } |
| 13 | |
| 14 | function omitNode<Props extends { node?: unknown }>(props: Props): Omit<Props, "node"> { |
| 15 | const { node, ...rest } = props; |
| 16 | void node; |
| 17 | return rest; |
| 18 | } |
| 19 | |
| 20 | function MarkdownParagraph(props: MarkdownProps<"p">) { |
| 21 | return <p {...omitNode(props)} />; |
| 22 | } |
| 23 | |
| 24 | function MarkdownH1(props: MarkdownProps<"h1">) { |
| 25 | return <h1 {...omitNode(props)} />; |
| 26 | } |
| 27 | |
| 28 | function MarkdownH2(props: MarkdownProps<"h2">) { |
| 29 | return <h2 {...omitNode(props)} />; |
| 30 | } |
| 31 | |
| 32 | function MarkdownH3(props: MarkdownProps<"h3">) { |
| 33 | return <h3 {...omitNode(props)} />; |
| 34 | } |
| 35 | |
| 36 | function MarkdownH4(props: MarkdownProps<"h4">) { |
| 37 | return <h4 {...omitNode(props)} />; |
| 38 | } |
| 39 | |
| 40 | function MarkdownH5(props: MarkdownProps<"h5">) { |
| 41 | return <h5 {...omitNode(props)} />; |
| 42 | } |
| 43 | |
| 44 | function MarkdownH6(props: MarkdownProps<"h6">) { |
| 45 | return <h6 {...omitNode(props)} />; |
| 46 | } |
| 47 | |
| 48 | function MarkdownUnorderedList(props: MarkdownProps<"ul">) { |
| 49 | return <ul {...omitNode(props)} />; |
| 50 | } |
| 51 | |
| 52 | function MarkdownOrderedList(props: MarkdownProps<"ol">) { |
| 53 | return <ol {...omitNode(props)} />; |
| 54 | } |
| 55 | |
| 56 | function MarkdownListItem(props: MarkdownProps<"li">) { |
| 57 | return <li {...omitNode(props)} />; |
| 58 | } |
| 59 | |
| 60 | function MarkdownBlockquote(props: MarkdownProps<"blockquote">) { |
| 61 | return <blockquote {...omitNode(props)} />; |
| 62 | } |
| 63 | |
| 64 | function MarkdownPre(props: MarkdownProps<"pre">) { |
| 65 | return <pre {...omitNode(props)} />; |
| 66 | } |
| 67 | |
| 68 | function MarkdownCode(props: MarkdownProps<"code">) { |
| 69 | return <code {...omitNode(props)} />; |
| 70 | } |
| 71 | |
| 72 | function MarkdownLink(props: MarkdownProps<"a">) { |
| 73 | return <a {...omitNode(props)} />; |
| 74 | } |
| 75 | |
| 76 | function MarkdownStrong(props: MarkdownProps<"strong">) { |
| 77 | const { style, ...rest } = omitNode(props); |
| 78 | return <strong {...rest} style={{ ...style, backgroundColor: randomHighlightColor() }} />; |
| 79 | } |
| 80 | |
| 81 | function MarkdownEmphasis(props: MarkdownProps<"em">) { |
| 82 | const { style, ...rest } = omitNode(props); |
| 83 | return <em {...rest} style={{ ...style, backgroundColor: randomHighlightColor() }} />; |
| 84 | } |
| 85 | |
| 86 | function MarkdownDelete(props: MarkdownProps<"del">) { |
| 87 | return <del {...omitNode(props)} />; |
| 88 | } |
| 89 | |
| 90 | function MarkdownHorizontalRule(props: MarkdownProps<"hr">) { |
| 91 | return <hr {...omitNode(props)} />; |
| 92 | } |
| 93 | |
| 94 | function MarkdownTable(props: MarkdownProps<"table">) { |
| 95 | return <table {...omitNode(props)} />; |
| 96 | } |
| 97 | |
| 98 | function MarkdownTableHead(props: MarkdownProps<"thead">) { |
| 99 | return <thead {...omitNode(props)} />; |
| 100 | } |
| 101 | |
| 102 | function MarkdownTableBody(props: MarkdownProps<"tbody">) { |
| 103 | return <tbody {...omitNode(props)} />; |
| 104 | } |
| 105 | |
| 106 | function MarkdownTableRow(props: MarkdownProps<"tr">) { |
| 107 | return <tr {...omitNode(props)} />; |
| 108 | } |
| 109 | |
| 110 | function MarkdownTableHeader(props: MarkdownProps<"th">) { |
| 111 | return <th {...omitNode(props)} />; |
| 112 | } |
| 113 | |
| 114 | function MarkdownTableCell(props: MarkdownProps<"td">) { |
| 115 | return <td {...omitNode(props)} />; |
| 116 | } |
| 117 | |
| 118 | function MarkdownInput(props: MarkdownProps<"input">) { |
| 119 | return <input {...omitNode(props)} />; |
| 120 | } |
| 121 | |
| 122 | export const components: Partial<MemoComponents> = { |
| 123 | a: MarkdownLink, |
| 124 | blockquote: MarkdownBlockquote, |
| 125 | code: MarkdownCode, |
| 126 | del: MarkdownDelete, |
| 127 | em: MarkdownEmphasis, |
| 128 | h1: MarkdownH1, |
| 129 | h2: MarkdownH2, |
| 130 | h3: MarkdownH3, |
| 131 | h4: MarkdownH4, |
| 132 | h5: MarkdownH5, |
| 133 | h6: MarkdownH6, |
| 134 | hr: MarkdownHorizontalRule, |
| 135 | input: MarkdownInput, |
| 136 | li: MarkdownListItem, |
| 137 | ol: MarkdownOrderedList, |
| 138 | p: MarkdownParagraph, |
| 139 | pre: MarkdownPre, |
| 140 | strong: MarkdownStrong, |
| 141 | table: MarkdownTable, |
| 142 | tbody: MarkdownTableBody, |
| 143 | td: MarkdownTableCell, |
| 144 | th: MarkdownTableHeader, |
| 145 | thead: MarkdownTableHead, |
| 146 | tr: MarkdownTableRow, |
| 147 | ul: MarkdownUnorderedList, |
| 148 | }; |
| 149 | |
| 150 | export const reactMarkdownComponents = components as ReactMarkdownComponents; |
| 151 | export const streamdownComponents = components as StreamdownComponents; |