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