1- import React from 'react' ;
2- import { GestureResponderEvent , Linking , Text , View } from 'react-native' ;
1+ import React , { PropsWithChildren } from 'react' ;
2+ import { GestureResponderEvent , Linking , Text , TextProps , View , ViewProps } from 'react-native' ;
33
44// @ts -expect-error
55import Markdown from 'react-native-markdown-package' ;
@@ -12,7 +12,9 @@ import {
1212 ParseFunction ,
1313 parseInline ,
1414 ReactNodeOutput ,
15+ ReactOutput ,
1516 SingleASTNode ,
17+ State ,
1618} from 'simple-markdown' ;
1719
1820import { parseLinksFromText } from './parseLinks' ;
@@ -171,7 +173,7 @@ export const renderText = <
171173 ? onLinkParams ( url )
172174 : Linking . canOpenURL ( url ) . then ( ( canOpenUrl ) => canOpenUrl && Linking . openURL ( url ) ) ;
173175
174- const react : ReactNodeOutput = ( node , output , { ...state } ) => {
176+ const link : ReactNodeOutput = ( node , output , { ...state } ) => {
175177 const onPress = ( event : GestureResponderEvent ) => {
176178 if ( ! preventPress && onPressParam ) {
177179 onPressParam ( {
@@ -249,56 +251,18 @@ export const renderText = <
249251 ) ;
250252 } ;
251253
252- const listLevels = {
253- sub : 'sub' ,
254- top : 'top' ,
255- } ;
256-
257- /**
258- * For lists and sublists, the default behavior of the markdown library we use is
259- * to always renumber any list, so all ordered lists start from 1.
260- *
261- * This custom rule overrides this behavior both for top level lists and sublists,
262- * in order to start the numbering from the number of the first list item provided.
263- */
264- const customListAtLevel =
265- ( level : keyof typeof listLevels ) : ReactNodeOutput =>
266- ( node , output , { ...state } ) => {
267- const items = node . items . map ( ( item : Array < SingleASTNode > , index : number ) => {
268- const withinList = item . length > 1 && item [ 1 ] . type === 'list' ;
269- const content = output ( item , { ...state , withinList } ) ;
270-
271- const isTopLevelText =
272- [ 'text' , 'paragraph' , 'strong' ] . includes ( item [ 0 ] . type ) && withinList === false ;
273-
274- return (
275- < View key = { index } style = { styles . listRow } >
276- < Text style = { styles . listItemNumber } >
277- { node . ordered ? `${ node . start + index } . ` : `\u2022` }
278- </ Text >
279- < Text style = { [ styles . listItemText , isTopLevelText && { marginBottom : 0 } ] } >
280- { content }
281- </ Text >
282- </ View >
283- ) ;
284- } ) ;
285-
286- const isSublist = level === 'sub' ;
287- return (
288- < View key = { state . key } style = { [ isSublist ? styles . list : styles . sublist ] } >
289- { items }
290- </ View >
291- ) ;
292- } ;
254+ const list : ReactNodeOutput = ( node , output , state ) => (
255+ < ListOutput node = { node } output = { output } state = { state } styles = { styles } />
256+ ) ;
293257
294258 const customRules = {
295- link : { react } ,
296- list : { react : customListAtLevel ( 'top' ) } ,
259+ link : { link } ,
260+ list : { react : list } ,
297261 // Truncate long text content in the message overlay
298262 paragraph : messageTextNumberOfLines ? { react : paragraphText } : { } ,
299263 // we have no react rendering support for reflinks
300264 reflink : { match : ( ) => null } ,
301- sublist : { react : customListAtLevel ( 'sub' ) } ,
265+ sublist : { react : list } ,
302266 ...( mentionedUsers
303267 ? {
304268 mentions : {
@@ -327,3 +291,69 @@ export const renderText = <
327291 </ Markdown >
328292 ) ;
329293} ;
294+
295+ export interface ListOutputProps {
296+ node : SingleASTNode ;
297+ output : ReactOutput ;
298+ state : State ;
299+ styles ?: Partial < MarkdownStyle > ;
300+ }
301+
302+ /**
303+ * For lists and sublists, the default behavior of the markdown library we use is
304+ * to always renumber any list, so all ordered lists start from 1.
305+ *
306+ * This custom rule overrides this behavior both for top level lists and sublists,
307+ * in order to start the numbering from the number of the first list item provided.
308+ */
309+ export const ListOutput = ( { node, output, state, styles } : ListOutputProps ) => {
310+ let isSublist = state . withinList ;
311+ const parentTypes = [ 'text' , 'paragraph' , 'strong' ] ;
312+
313+ return (
314+ < View key = { state . key } style = { isSublist ? styles ?. sublist : styles ?. list } >
315+ { node . items . map ( ( item : SingleASTNode , index : number ) => {
316+ const indexAfterStart = node . start + index ;
317+
318+ if ( item === null ) {
319+ return (
320+ < ListRow key = { index } style = { styles ?. listRow } testID = 'list-item' >
321+ < Bullet index = { node . ordered && indexAfterStart } />
322+ </ ListRow >
323+ ) ;
324+ }
325+
326+ isSublist = item . length > 1 && item [ 1 ] . type === 'list' ;
327+ const isSublistWithinText = parentTypes . includes ( ( item [ 0 ] ?? { } ) . type ) && isSublist ;
328+ const style = isSublistWithinText ? { marginBottom : 0 } : { } ;
329+
330+ return (
331+ < ListRow key = { index } style = { styles ?. listRow } testID = 'list-item' >
332+ < Bullet index = { node . ordered && indexAfterStart } />
333+ < ListItem key = { 1 } style = { [ styles ?. listItemText , style ] } >
334+ { output ( item , state ) }
335+ </ ListItem >
336+ </ ListRow >
337+ ) ;
338+ } ) }
339+ </ View >
340+ ) ;
341+ } ;
342+
343+ interface BulletProps extends TextProps {
344+ index ?: number ;
345+ }
346+
347+ const Bullet = ( { index, style } : BulletProps ) => (
348+ < Text key = { 0 } style = { [ style , defaultMarkdownStyles . listItemNumber ] } >
349+ { index ? `${ index } . ` : '\u2022 ' }
350+ </ Text >
351+ ) ;
352+
353+ const ListRow = ( props : PropsWithChildren < ViewProps > ) => (
354+ < Text style = { [ props . style , defaultMarkdownStyles . listRow ] } > { props . children } </ Text >
355+ ) ;
356+
357+ const ListItem = ( { children, style } : PropsWithChildren < TextProps > ) => (
358+ < Text style = { style } > { children } </ Text >
359+ ) ;
0 commit comments