mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {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?: 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 flattenedStyle = flatten(style)
52 const plainStyles = [a.leading_snug, flattenedStyle]
53 const interactiveStyles = [
54 a.leading_snug,
55 flatten(interactiveStyle),
56 flattenedStyle,
57 ]
58
59 const {text, facets} = richText
60
61 if (!facets?.length) {
62 if (isOnlyEmoji(text)) {
63 const fontSize =
64 (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier
65 return (
66 <Text
67 emoji
68 selectable={selectable}
69 testID={testID}
70 style={[plainStyles, {fontSize}]}
71 onLayout={onLayout}
72 onTextLayout={onTextLayout}
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 onLayout={onLayout}
87 onTextLayout={onTextLayout}
88 // @ts-ignore web only -prf
89 dataSet={WORD_WRAP}>
90 {text}
91 </Text>
92 )
93 }
94
95 const els = []
96 let key = 0
97 // N.B. must access segments via `richText.segments`, not via destructuring
98 for (const segment of richText.segments()) {
99 const link = segment.link
100 const mention = segment.mention
101 const tag = segment.tag
102 if (
103 mention &&
104 AppBskyRichtextFacet.validateMention(mention).success &&
105 !disableLinks
106 ) {
107 els.push(
108 <ProfileHoverCard key={key} did={mention.did}>
109 <InlineLinkText
110 selectable={selectable}
111 to={`/profile/${mention.did}`}
112 style={interactiveStyles}
113 // @ts-ignore TODO
114 dataSet={WORD_WRAP}
115 shouldProxy={shouldProxyLinks}
116 onPress={onLinkPress}>
117 {segment.text}
118 </InlineLinkText>
119 </ProfileHoverCard>,
120 )
121 } else if (link && AppBskyRichtextFacet.validateLink(link).success) {
122 if (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}