@@ -4,8 +4,20 @@ import { KeyboardAvoidingLegendList } from "@legendapp/list/keyboard";
44import { type LegendListRef } from "@legendapp/list/react-native" ;
55import { SymbolView } from "expo-symbols" ;
66import { memo , useCallback , useEffect , useMemo , useRef , useState } from "react" ;
7- import Markdown from "react-native-markdown-display" ;
8- import { Image , Pressable , ScrollView , View } from "react-native" ;
7+ import {
8+ Markdown ,
9+ type CustomRenderers ,
10+ type NodeStyleOverrides ,
11+ type PartialMarkdownTheme ,
12+ } from "react-native-nitro-markdown" ;
13+ import {
14+ Image ,
15+ Pressable ,
16+ ScrollView ,
17+ Text as NativeText ,
18+ type ColorValue ,
19+ View ,
20+ } from "react-native" ;
921import { TouchableOpacity } from "react-native-gesture-handler" ;
1022import ImageViewing from "react-native-image-viewing" ;
1123import { useSafeAreaInsets } from "react-native-safe-area-context" ;
@@ -73,9 +85,19 @@ function buildActivityRows(
7385
7486const MAX_VISIBLE_WORK_LOG_ENTRIES = 6 ;
7587
88+ function toMarkdownThemeColor ( value : ColorValue ) : string {
89+ return value as string ;
90+ }
91+
7692interface MarkdownStyleSets {
77- readonly user : Record < string , any > ;
78- readonly assistant : Record < string , any > ;
93+ readonly user : MarkdownStyleSet ;
94+ readonly assistant : MarkdownStyleSet ;
95+ }
96+
97+ interface MarkdownStyleSet {
98+ readonly theme : PartialMarkdownTheme ;
99+ readonly styles : NodeStyleOverrides ;
100+ readonly renderers : CustomRenderers ;
79101}
80102
81103function useMarkdownStyles ( ) : MarkdownStyleSets {
@@ -93,137 +115,193 @@ function useMarkdownStyles(): MarkdownStyleSets {
93115 const userFenceText = useThemeColor ( "--color-md-user-fence-text" ) ;
94116
95117 return useMemo ( ( ) => {
96- const base = {
97- body : {
98- color : bodyColor ,
99- fontSize : 15 ,
100- lineHeight : 22 ,
101- fontFamily : "DMSans_400Regular" ,
118+ const markdownBodyColor = toMarkdownThemeColor ( bodyColor ) ;
119+ const markdownStrongColor = toMarkdownThemeColor ( strongColor ) ;
120+ const markdownLinkColor = toMarkdownThemeColor ( linkColor ) ;
121+ const markdownBlockquoteBg = toMarkdownThemeColor ( blockquoteBg ) ;
122+ const markdownBlockquoteBorder = toMarkdownThemeColor ( blockquoteBorder ) ;
123+ const markdownCodeBg = toMarkdownThemeColor ( codeBg ) ;
124+ const markdownCodeText = toMarkdownThemeColor ( codeText ) ;
125+ const markdownHrColor = toMarkdownThemeColor ( hrColor ) ;
126+ const markdownUserCodeBg = toMarkdownThemeColor ( userCodeBg ) ;
127+ const markdownUserCodeText = toMarkdownThemeColor ( userCodeText ) ;
128+ const markdownUserFenceBg = toMarkdownThemeColor ( userFenceBg ) ;
129+ const markdownUserFenceText = toMarkdownThemeColor ( userFenceText ) ;
130+
131+ const baseTheme : PartialMarkdownTheme = {
132+ colors : {
133+ text : markdownBodyColor ,
134+ heading : markdownStrongColor ,
135+ link : markdownLinkColor ,
136+ blockquote : markdownBlockquoteBorder ,
137+ border : markdownHrColor ,
138+ surfaceLight : markdownBlockquoteBg ,
139+ accent : markdownLinkColor ,
140+ tableBorder : markdownHrColor ,
141+ tableHeader : markdownBlockquoteBg ,
142+ tableHeaderText : markdownStrongColor ,
143+ tableRowOdd : "transparent" ,
144+ tableRowEven : "transparent" ,
145+ } ,
146+ spacing : {
147+ xs : 4 ,
148+ s : 4 ,
149+ m : 8 ,
150+ l : 8 ,
151+ xl : 16 ,
102152 } ,
153+ fontSizes : {
154+ s : 13 ,
155+ m : 15 ,
156+ h1 : 22 ,
157+ h2 : 19 ,
158+ h3 : 17 ,
159+ h4 : 15 ,
160+ h5 : 15 ,
161+ h6 : 15 ,
162+ } ,
163+ fontFamilies : {
164+ regular : "DMSans_400Regular" ,
165+ heading : "DMSans_700Bold" ,
166+ mono : "ui-monospace" ,
167+ } ,
168+ headingWeight : "700" ,
169+ borderRadius : {
170+ s : 4 ,
171+ m : 8 ,
172+ l : 12 ,
173+ } ,
174+ showCodeLanguage : false ,
175+ } ;
176+
177+ const baseStyles : NodeStyleOverrides = {
178+ document : { flexShrink : 1 } ,
103179 paragraph : { marginTop : 0 , marginBottom : 8 } ,
104- bullet_list : { marginTop : 4 , marginBottom : 4 } ,
105- ordered_list : { marginTop : 4 , marginBottom : 4 } ,
106- list_item : { marginTop : 0 , marginBottom : 4 , flexDirection : "row" as const } ,
107- strong : { fontWeight : "700" as const , color : strongColor , fontFamily : "DMSans_700Bold" } ,
108- em : { fontStyle : "italic" as const } ,
109- link : { color : linkColor , textDecorationLine : "underline" as const } ,
180+ list : { marginTop : 4 , marginBottom : 4 } ,
181+ list_item : { marginTop : 0 , marginBottom : 4 } ,
182+ task_list_item : { marginTop : 0 , marginBottom : 4 } ,
183+ bold : { fontWeight : "700" , color : markdownStrongColor , fontFamily : "DMSans_700Bold" } ,
184+ italic : { fontStyle : "italic" } ,
185+ link : { color : markdownLinkColor , textDecorationLine : "underline" as const } ,
110186 blockquote : {
111187 borderLeftWidth : 3 ,
112- borderLeftColor : blockquoteBorder ,
113- backgroundColor : blockquoteBg ,
188+ borderLeftColor : markdownBlockquoteBorder ,
189+ backgroundColor : markdownBlockquoteBg ,
114190 paddingLeft : 12 ,
115191 paddingVertical : 6 ,
116192 marginLeft : 0 ,
117193 marginVertical : 4 ,
118194 borderRadius : 4 ,
119195 } ,
120- heading1 : {
121- fontSize : 22 ,
122- lineHeight : 28 ,
123- fontWeight : "800" as const ,
124- fontFamily : "DMSans_700Bold" ,
125- color : strongColor ,
126- marginTop : 16 ,
127- marginBottom : 8 ,
128- } ,
129- heading2 : {
130- fontSize : 19 ,
131- lineHeight : 26 ,
132- fontWeight : "700" as const ,
196+ heading : {
133197 fontFamily : "DMSans_700Bold" ,
134- color : strongColor ,
135- marginTop : 14 ,
136- marginBottom : 6 ,
137- } ,
138- heading3 : {
139- fontSize : 17 ,
140- lineHeight : 24 ,
141- fontWeight : "700" as const ,
142- fontFamily : "DMSans_700Bold" ,
143- color : strongColor ,
198+ color : markdownStrongColor ,
144199 marginTop : 12 ,
145- marginBottom : 4 ,
146- } ,
147- heading4 : {
148- fontSize : 15 ,
149- lineHeight : 22 ,
150- fontWeight : "700" as const ,
151- fontFamily : "DMSans_700Bold" ,
152- color : strongColor ,
153- marginTop : 10 ,
154- marginBottom : 4 ,
200+ marginBottom : 6 ,
155201 } ,
156- hr : {
157- backgroundColor : hrColor ,
202+ horizontal_rule : {
203+ backgroundColor : markdownHrColor ,
158204 height : 1 ,
159205 marginVertical : 12 ,
160206 } ,
161207 } ;
162208
163- const user = {
164- ...base ,
165- paragraph : { marginTop : 0 , marginBottom : 0 } ,
166- code_inline : {
167- backgroundColor : userCodeBg ,
168- color : userCodeText ,
169- borderRadius : 5 ,
170- paddingHorizontal : 5 ,
171- paddingVertical : 1 ,
172- fontFamily : "ui-monospace" ,
173- fontSize : 13 ,
174- } ,
175- code_block : {
176- backgroundColor : userFenceBg ,
177- color : userFenceText ,
178- borderRadius : 12 ,
179- padding : 12 ,
180- fontFamily : "ui-monospace" ,
181- fontSize : 13 ,
182- lineHeight : 19 ,
183- } ,
184- fence : {
185- backgroundColor : userFenceBg ,
186- color : userFenceText ,
187- borderRadius : 12 ,
188- padding : 12 ,
189- fontFamily : "ui-monospace" ,
190- fontSize : 13 ,
191- lineHeight : 19 ,
209+ const createCodeRenderers = (
210+ inlineBackgroundColor : string ,
211+ inlineTextColor : string ,
212+ blockBackgroundColor : string ,
213+ blockTextColor : string ,
214+ ) : CustomRenderers => ( {
215+ code_inline : ( { content } ) => (
216+ < NativeText
217+ style = { {
218+ backgroundColor : inlineBackgroundColor ,
219+ color : inlineTextColor ,
220+ borderRadius : 5 ,
221+ paddingHorizontal : 5 ,
222+ paddingVertical : 1 ,
223+ fontFamily : "ui-monospace" ,
224+ fontSize : 13 ,
225+ } }
226+ >
227+ { content }
228+ </ NativeText >
229+ ) ,
230+ code_block : ( { content } ) => (
231+ < View
232+ style = { {
233+ backgroundColor : blockBackgroundColor ,
234+ borderRadius : 12 ,
235+ padding : 12 ,
236+ marginVertical : 8 ,
237+ } }
238+ >
239+ < ScrollView horizontal showsHorizontalScrollIndicator = { false } bounces = { false } >
240+ < NativeText
241+ selectable
242+ style = { {
243+ color : blockTextColor ,
244+ fontFamily : "ui-monospace" ,
245+ fontSize : 13 ,
246+ lineHeight : 19 ,
247+ } }
248+ >
249+ { content }
250+ </ NativeText >
251+ </ ScrollView >
252+ </ View >
253+ ) ,
254+ } ) ;
255+
256+ const userTheme : PartialMarkdownTheme = {
257+ ...baseTheme ,
258+ colors : {
259+ ...baseTheme . colors ,
260+ code : markdownUserCodeText ,
261+ codeBackground : markdownUserCodeBg ,
262+ border : markdownUserFenceBg ,
192263 } ,
193264 } ;
265+ const userStyles : NodeStyleOverrides = {
266+ ...baseStyles ,
267+ paragraph : { marginTop : 0 , marginBottom : 0 } ,
268+ } ;
194269
195- const assistant = {
196- ...base ,
197- code_inline : {
198- backgroundColor : codeBg ,
199- color : codeText ,
200- borderRadius : 5 ,
201- paddingHorizontal : 5 ,
202- paddingVertical : 1 ,
203- fontFamily : "ui-monospace" ,
204- fontSize : 13 ,
270+ const assistantTheme : PartialMarkdownTheme = {
271+ ...baseTheme ,
272+ colors : {
273+ ...baseTheme . colors ,
274+ code : markdownCodeText ,
275+ codeBackground : markdownCodeBg ,
276+ border : markdownCodeBg ,
205277 } ,
206- code_block : {
207- backgroundColor : codeBg ,
208- color : codeText ,
209- borderRadius : 12 ,
210- padding : 12 ,
211- fontFamily : "ui-monospace" ,
212- fontSize : 13 ,
213- lineHeight : 19 ,
278+ } ;
279+ const assistantStyles : NodeStyleOverrides = {
280+ ...baseStyles ,
281+ } ;
282+
283+ return {
284+ user : {
285+ theme : userTheme ,
286+ styles : userStyles ,
287+ renderers : createCodeRenderers (
288+ markdownUserCodeBg ,
289+ markdownUserCodeText ,
290+ markdownUserFenceBg ,
291+ markdownUserFenceText ,
292+ ) ,
214293 } ,
215- fence : {
216- backgroundColor : codeBg ,
217- color : codeText ,
218- borderRadius : 12 ,
219- padding : 12 ,
220- fontFamily : "ui-monospace" ,
221- fontSize : 13 ,
222- lineHeight : 19 ,
294+ assistant : {
295+ theme : assistantTheme ,
296+ styles : assistantStyles ,
297+ renderers : createCodeRenderers (
298+ markdownCodeBg ,
299+ markdownCodeText ,
300+ markdownCodeBg ,
301+ markdownCodeText ,
302+ ) ,
223303 } ,
224304 } ;
225-
226- return { user, assistant } ;
227305 } , [
228306 blockquoteBg ,
229307 blockquoteBorder ,
@@ -267,7 +345,14 @@ function renderFeedEntry(
267345 < View className = "mb-3.5 items-end gap-1.5" >
268346 < View className = "max-w-[85%] gap-2 rounded-[22px] rounded-br-[10px] border border-blue-300/50 bg-blue-50/80 px-4 py-4 dark:border-blue-400/20 dark:bg-blue-500/12" >
269347 { message . text . trim ( ) . length > 0 ? (
270- < Markdown style = { styles } > { message . text } </ Markdown >
348+ < Markdown
349+ options = { { gfm : true } }
350+ renderers = { styles . renderers }
351+ styles = { styles . styles }
352+ theme = { styles . theme }
353+ >
354+ { message . text }
355+ </ Markdown >
271356 ) : null }
272357 { attachments . map ( ( attachment ) => {
273358 const uri = messageImageUrl ( props . httpBaseUrl , attachment . id ) ;
@@ -308,7 +393,16 @@ function renderFeedEntry(
308393
309394 return (
310395 < View className = "mb-3.5 gap-1.5 px-1" >
311- { message . text . trim ( ) . length > 0 ? < Markdown style = { styles } > { message . text } </ Markdown > : null }
396+ { message . text . trim ( ) . length > 0 ? (
397+ < Markdown
398+ options = { { gfm : true } }
399+ renderers = { styles . renderers }
400+ styles = { styles . styles }
401+ theme = { styles . theme }
402+ >
403+ { message . text }
404+ </ Markdown >
405+ ) : null }
312406 { attachments . map ( ( attachment ) => {
313407 const uri = messageImageUrl ( props . httpBaseUrl , attachment . id ) ;
314408 if ( ! uri ) {
0 commit comments