@@ -3,47 +3,13 @@ import "./tailwind.css";
3
3
4
4
import clsx from "clsx" ;
5
5
import type React from "react" ;
6
- import { memo , useCallback , useEffect , useRef , useState } from "react" ;
6
+ import { memo , useCallback , useEffect , useRef } from "react" ;
7
7
import Markdown from "react-markdown" ;
8
- import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" ;
9
- import {
10
- oneLight ,
11
- vscDarkPlus ,
12
- } from "react-syntax-highlighter/dist/esm/styles/prism" ;
13
8
import remarkGfm from "remark-gfm" ;
14
9
import remarkMath from "remark-math" ;
15
10
import { twMerge } from "tailwind-merge" ;
16
- import type { MessageParam } from "./utils" ;
17
-
18
- const useTheme = ( ) => {
19
- const [ isDark , setDark ] = useState ( false ) ;
20
-
21
- useEffect ( ( ) => {
22
- const checkDarkMode = ( ) => {
23
- setDark ( document . documentElement . classList . contains ( "dark" ) ) ;
24
- } ;
25
- checkDarkMode ( ) ;
26
-
27
- const observer = new MutationObserver ( checkDarkMode ) ;
28
- observer . observe ( document . documentElement , {
29
- attributes : true ,
30
- attributeFilter : [ "class" ] ,
31
- } ) ;
32
-
33
- const mediaQuery = window . matchMedia ( "(prefers-color-scheme: dark)" ) ;
34
- const handleSystemThemeChange = ( event : MediaQueryListEvent ) => {
35
- setDark ( event . matches ) ;
36
- } ;
37
- mediaQuery . addEventListener ( "change" , handleSystemThemeChange ) ;
38
-
39
- return ( ) => {
40
- observer . disconnect ( ) ;
41
- mediaQuery . removeEventListener ( "change" , handleSystemThemeChange ) ;
42
- } ;
43
- } , [ ] ) ;
44
-
45
- return { isDark } ;
46
- } ;
11
+ import type { MessageParam } from "../utils" ;
12
+ import { BlockQuote , CodeBlock , Heading , Link } from "./markdown" ;
47
13
48
14
const bubbleVariants = cva (
49
15
"flex flex-col gap-1 justify-center rounded-lg dark:text-gray-200 text-gray-800 max-w-full overflow-x-auto" ,
@@ -111,7 +77,6 @@ export function Bubble({
111
77
isPending = false ,
112
78
...props
113
79
} : BubbleProps ) {
114
- const { isDark } = useTheme ( ) ;
115
80
116
81
const defaultPending = (
117
82
< div className = "flex items-center space-x-1 py-1" >
@@ -149,130 +114,15 @@ export function Bubble({
149
114
< Markdown
150
115
remarkPlugins = { [ remarkGfm , remarkMath ] }
151
116
components = { {
152
- h1 ( props ) {
153
- const { children, className, node : _node , ...rest } = props ;
154
- return (
155
- < h1
156
- { ...rest }
157
- className = { clsx ( "my-3 text-2xl font-bold" , className ) }
158
- >
159
- { children }
160
- </ h1 >
161
- ) ;
162
- } ,
163
- h2 ( props ) {
164
- const { children, className, node : _node , ...rest } = props ;
165
- return (
166
- < h2
167
- { ...rest }
168
- className = { clsx ( "my-2 text-xl font-bold" , className ) }
169
- >
170
- { children }
171
- </ h2 >
172
- ) ;
173
- } ,
174
- h3 ( props ) {
175
- const { children, className, node : _node , ...rest } = props ;
176
- return (
177
- < h3
178
- { ...rest }
179
- className = { clsx ( "my-1 text-lg font-bold" , className ) }
180
- >
181
- { children }
182
- </ h3 >
183
- ) ;
184
- } ,
185
- code ( props ) {
186
- const { children, className, ref : _ref , ...rest } = props ;
187
- const match = / l a n g u a g e - ( \w + ) / . exec ( className || "" ) ;
188
-
189
- const [ copied , setCopied ] = useState ( false ) ;
190
- const handleCopy = ( ) => {
191
- navigator . clipboard . writeText ( String ( children ) . replace ( / \n $ / , '' ) ) ;
192
- setCopied ( true ) ;
193
- setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
194
- } ;
195
-
196
- return match ? (
197
- < div
198
- className = { clsx (
199
- "w-full overflow-x-auto rounded-lg" ,
200
- "bg-gray-50 dark:bg-gray-800" ,
201
- ) }
202
- >
203
- < div className = "inline-flex w-full justify-between bg-gray-100 p-2" >
204
- < div className = "px-2 py-1 text-xs text-gray-900 dark:text-gray-400" >
205
- { match [ 1 ] }
206
- </ div >
207
- < div
208
- className = "px-2 py-1 text-xs text-gray-900 dark:text-gray-400 cursor-pointer"
209
- onClick = { handleCopy }
210
- >
211
- { copied ? "Copied" : "Copy" }
212
- </ div >
213
- </ div >
214
- < SyntaxHighlighter
215
- { ...rest }
216
- PreTag = "div"
217
- language = { match [ 1 ] }
218
- style = { isDark ? vscDarkPlus : oneLight }
219
- customStyle = { {
220
- background : "transparent" ,
221
- margin : 0 ,
222
- padding : "1rem" ,
223
- borderRadius : "0.5rem" ,
224
- overflowX : "auto" ,
225
- } }
226
- codeTagProps = { {
227
- style : {
228
- fontFamily : "monospace" ,
229
- fontSize : "0.875rem" ,
230
- } ,
231
- } }
232
- >
233
- { String ( children ) . replace ( / \n $ / , "" ) }
234
- </ SyntaxHighlighter >
235
- </ div >
236
- ) : (
237
- < code
238
- { ...rest }
239
- className = { clsx (
240
- "rounded-md px-1 py-0.5 text-[85%]" ,
241
- "bg-gray-100 dark:bg-gray-800" ,
242
- ) }
243
- >
244
- { children }
245
- </ code >
246
- ) ;
247
- } ,
248
- blockquote ( props ) {
249
- const { children, className, ...rest } = props ;
250
- return (
251
- < blockquote
252
- { ...rest }
253
- className = { clsx (
254
- "border-l-4 border-gray-300 pl-4 italic" ,
255
- className ,
256
- ) }
257
- >
258
- { children }
259
- </ blockquote >
260
- ) ;
261
- } ,
262
- a ( props ) {
263
- const { children, className, ref : _ref , ...rest } = props ;
264
- return (
265
- < a
266
- { ...rest }
267
- className = { clsx (
268
- "text-blue-600 dark:text-blue-400 hover:underline underline-offset-1" ,
269
- className ,
270
- ) }
271
- >
272
- { children }
273
- </ a >
274
- ) ;
275
- } ,
117
+ a : Link ,
118
+ code : CodeBlock ,
119
+ blockquote : BlockQuote ,
120
+ h1 : ( props ) => < Heading { ...props } level = { 1 } /> ,
121
+ h2 : ( props ) => < Heading { ...props } level = { 2 } /> ,
122
+ h3 : ( props ) => < Heading { ...props } level = { 3 } /> ,
123
+ h4 : ( props ) => < Heading { ...props } level = { 4 } /> ,
124
+ h5 : ( props ) => < Heading { ...props } level = { 5 } /> ,
125
+ h6 : ( props ) => < Heading { ...props } level = { 6 } /> ,
276
126
} }
277
127
>
278
128
{ text }
0 commit comments