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