mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at utm-source 250 lines 6.6 kB view raw
1import React from 'react' 2import {TextStyle} from 'react-native' 3import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useNavigation} from '@react-navigation/native' 7 8import {NavigationProp} from '#/lib/routes/types' 9import {toShortUrl} from '#/lib/strings/url-helpers' 10import {isNative} from '#/platform/detection' 11import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf' 12import {isOnlyEmoji} from '#/alf/typography' 13import {useInteractionState} from '#/components/hooks/useInteractionState' 14import {InlineLinkText, LinkProps} from '#/components/Link' 15import {ProfileHoverCard} from '#/components/ProfileHoverCard' 16import {TagMenu, useTagMenuControl} from '#/components/TagMenu' 17import {Text, TextProps} from '#/components/Typography' 18 19const WORD_WRAP = {wordWrap: 1} 20 21export type RichTextProps = TextStyleProp & 22 Pick<TextProps, 'selectable'> & { 23 value: RichTextAPI | string 24 testID?: string 25 numberOfLines?: number 26 disableLinks?: boolean 27 enableTags?: boolean 28 authorHandle?: string 29 onLinkPress?: LinkProps['onPress'] 30 interactiveStyle?: TextStyle 31 emojiMultiplier?: number 32 } 33 34export function RichText({ 35 testID, 36 value, 37 style, 38 numberOfLines, 39 disableLinks, 40 selectable, 41 enableTags = false, 42 authorHandle, 43 onLinkPress, 44 interactiveStyle, 45 emojiMultiplier = 1.85, 46}: RichTextProps) { 47 const richText = React.useMemo( 48 () => 49 value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), 50 [value], 51 ) 52 53 const flattenedStyle = flatten(style) 54 const plainStyles = [a.leading_snug, flattenedStyle] 55 const interactiveStyles = [ 56 a.leading_snug, 57 flatten(interactiveStyle), 58 flattenedStyle, 59 ] 60 61 const {text, facets} = richText 62 63 if (!facets?.length) { 64 if (isOnlyEmoji(text)) { 65 const fontSize = 66 (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier 67 return ( 68 <Text 69 emoji 70 selectable={selectable} 71 testID={testID} 72 style={[plainStyles, {fontSize}]} 73 // @ts-ignore web only -prf 74 dataSet={WORD_WRAP}> 75 {text} 76 </Text> 77 ) 78 } 79 return ( 80 <Text 81 emoji 82 selectable={selectable} 83 testID={testID} 84 style={plainStyles} 85 numberOfLines={numberOfLines} 86 // @ts-ignore web only -prf 87 dataSet={WORD_WRAP}> 88 {text} 89 </Text> 90 ) 91 } 92 93 const els = [] 94 let key = 0 95 // N.B. must access segments via `richText.segments`, not via destructuring 96 for (const segment of richText.segments()) { 97 const link = segment.link 98 const mention = segment.mention 99 const tag = segment.tag 100 if ( 101 mention && 102 AppBskyRichtextFacet.validateMention(mention).success && 103 !disableLinks 104 ) { 105 els.push( 106 <ProfileHoverCard key={key} inline did={mention.did}> 107 <InlineLinkText 108 selectable={selectable} 109 to={`/profile/${mention.did}`} 110 style={interactiveStyles} 111 // @ts-ignore TODO 112 dataSet={WORD_WRAP} 113 onPress={onLinkPress}> 114 {segment.text} 115 </InlineLinkText> 116 </ProfileHoverCard>, 117 ) 118 } else if (link && AppBskyRichtextFacet.validateLink(link).success) { 119 if (disableLinks) { 120 els.push(toShortUrl(segment.text)) 121 } else { 122 els.push( 123 <InlineLinkText 124 selectable={selectable} 125 key={key} 126 to={link.uri} 127 style={interactiveStyles} 128 // @ts-ignore TODO 129 dataSet={WORD_WRAP} 130 shareOnLongPress 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 text={segment.text} 147 tag={tag.tag} 148 style={interactiveStyles} 149 selectable={selectable} 150 authorHandle={authorHandle} 151 />, 152 ) 153 } else { 154 els.push(segment.text) 155 } 156 key++ 157 } 158 159 return ( 160 <Text 161 emoji 162 selectable={selectable} 163 testID={testID} 164 style={plainStyles} 165 numberOfLines={numberOfLines} 166 // @ts-ignore web only -prf 167 dataSet={WORD_WRAP}> 168 {els} 169 </Text> 170 ) 171} 172 173function RichTextTag({ 174 text, 175 tag, 176 style, 177 selectable, 178 authorHandle, 179}: { 180 text: string 181 tag: string 182 selectable?: boolean 183 authorHandle?: string 184} & TextStyleProp) { 185 const t = useTheme() 186 const {_} = useLingui() 187 const control = useTagMenuControl() 188 const { 189 state: hovered, 190 onIn: onHoverIn, 191 onOut: onHoverOut, 192 } = useInteractionState() 193 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 194 const navigation = useNavigation<NavigationProp>() 195 196 const navigateToPage = React.useCallback(() => { 197 navigation.push('Hashtag', { 198 tag: encodeURIComponent(tag), 199 }) 200 }, [navigation, tag]) 201 202 const openDialog = React.useCallback(() => { 203 control.open() 204 }, [control]) 205 206 /* 207 * N.B. On web, this is wrapped in another pressable comopnent with a11y 208 * labels, etc. That's why only some of these props are applied here. 209 */ 210 211 return ( 212 <React.Fragment> 213 <TagMenu control={control} tag={tag} authorHandle={authorHandle}> 214 <Text 215 emoji 216 selectable={selectable} 217 {...native({ 218 accessibilityLabel: _(msg`Hashtag: #${tag}`), 219 accessibilityHint: _(msg`Long press to open tag menu for #${tag}`), 220 accessibilityRole: isNative ? 'button' : undefined, 221 onPress: navigateToPage, 222 onLongPress: openDialog, 223 })} 224 {...web({ 225 onMouseEnter: onHoverIn, 226 onMouseLeave: onHoverOut, 227 })} 228 // @ts-ignore 229 onFocus={onFocus} 230 onBlur={onBlur} 231 style={[ 232 web({ 233 cursor: 'pointer', 234 }), 235 {color: t.palette.primary_500}, 236 (hovered || focused) && { 237 ...web({ 238 outline: 0, 239 textDecorationLine: 'underline', 240 textDecorationColor: t.palette.primary_500, 241 }), 242 }, 243 style, 244 ]}> 245 {text} 246 </Text> 247 </TagMenu> 248 </React.Fragment> 249 ) 250}