Bluesky app fork with some witchin' additions 馃挮
at main 4.5 kB view raw
1import React from 'react' 2import {type StyleProp, type TextStyle} from 'react-native' 3import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' 4 5import {toShortUrl} from '#/lib/strings/url-helpers' 6import {atoms as a, flatten, type TextStyleProp} from '#/alf' 7import {isOnlyEmoji} from '#/alf/typography' 8import {InlineLinkText, type LinkProps} from '#/components/Link' 9import {ProfileHoverCard} from '#/components/ProfileHoverCard' 10import {RichTextTag} from '#/components/RichTextTag' 11import {Text, type TextProps} from '#/components/Typography' 12 13const WORD_WRAP = {wordWrap: 1} 14 15export type RichTextProps = TextStyleProp & 16 Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & { 17 value: RichTextAPI | string 18 testID?: string 19 numberOfLines?: number 20 disableLinks?: boolean 21 enableTags?: boolean 22 authorHandle?: string 23 onLinkPress?: LinkProps['onPress'] 24 interactiveStyle?: StyleProp<TextStyle> 25 emojiMultiplier?: number 26 shouldProxyLinks?: boolean 27 } 28 29export function RichText({ 30 testID, 31 value, 32 style, 33 numberOfLines, 34 disableLinks, 35 selectable, 36 enableTags = false, 37 authorHandle, 38 onLinkPress, 39 interactiveStyle, 40 emojiMultiplier = 1.85, 41 onLayout, 42 onTextLayout, 43 shouldProxyLinks, 44}: RichTextProps) { 45 const richText = React.useMemo( 46 () => 47 value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), 48 [value], 49 ) 50 51 const plainStyles = [a.leading_snug, style] 52 const interactiveStyles = [plainStyles, interactiveStyle] 53 54 const {text, facets} = richText 55 56 if (!facets?.length) { 57 if (isOnlyEmoji(text)) { 58 const flattenedStyle = flatten(style) ?? {} 59 const fontSize = 60 (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier 61 return ( 62 <Text 63 emoji 64 selectable={selectable} 65 testID={testID} 66 style={[plainStyles, {fontSize}]} 67 onLayout={onLayout} 68 onTextLayout={onTextLayout} 69 // @ts-ignore web only -prf 70 dataSet={WORD_WRAP}> 71 {text} 72 </Text> 73 ) 74 } 75 return ( 76 <Text 77 emoji 78 selectable={selectable} 79 testID={testID} 80 style={plainStyles} 81 numberOfLines={numberOfLines} 82 onLayout={onLayout} 83 onTextLayout={onTextLayout} 84 // @ts-ignore web only -prf 85 dataSet={WORD_WRAP}> 86 {text} 87 </Text> 88 ) 89 } 90 91 const els = [] 92 let key = 0 93 // N.B. must access segments via `richText.segments`, not via destructuring 94 for (const segment of richText.segments()) { 95 const link = segment.link 96 const mention = segment.mention 97 const tag = segment.tag 98 if ( 99 mention && 100 AppBskyRichtextFacet.validateMention(mention).success && 101 !disableLinks 102 ) { 103 els.push( 104 <ProfileHoverCard key={key} did={mention.did}> 105 <InlineLinkText 106 selectable={selectable} 107 to={`/profile/${mention.did}`} 108 style={interactiveStyles} 109 // @ts-ignore TODO 110 dataSet={WORD_WRAP} 111 shouldProxy={shouldProxyLinks} 112 onPress={onLinkPress}> 113 {segment.text} 114 </InlineLinkText> 115 </ProfileHoverCard>, 116 ) 117 } else if (link && AppBskyRichtextFacet.validateLink(link).success) { 118 if (disableLinks) { 119 els.push(toShortUrl(segment.text)) 120 } else { 121 els.push( 122 <InlineLinkText 123 selectable={selectable} 124 key={key} 125 to={link.uri} 126 style={interactiveStyles} 127 // @ts-ignore TODO 128 dataSet={WORD_WRAP} 129 shareOnLongPress 130 shouldProxy={shouldProxyLinks} 131 onPress={onLinkPress} 132 emoji> 133 {toShortUrl(segment.text)} 134 </InlineLinkText>, 135 ) 136 } 137 } else if ( 138 !disableLinks && 139 enableTags && 140 tag && 141 AppBskyRichtextFacet.validateTag(tag).success 142 ) { 143 els.push( 144 <RichTextTag 145 key={key} 146 display={segment.text} 147 tag={tag.tag} 148 textStyle={interactiveStyles} 149 authorHandle={authorHandle} 150 />, 151 ) 152 } else { 153 els.push(segment.text) 154 } 155 key++ 156 } 157 158 return ( 159 <Text 160 emoji 161 selectable={selectable} 162 testID={testID} 163 style={plainStyles} 164 numberOfLines={numberOfLines} 165 onLayout={onLayout} 166 onTextLayout={onTextLayout} 167 // @ts-ignore web only -prf 168 dataSet={WORD_WRAP}> 169 {els} 170 </Text> 171 ) 172}