Files
bsky-social-app/src/components/RichText.tsx
T
Eric Bailey df5a8f1542 Swap RichText (#2934)
* Switch to new RT

* Allow early exit from links

* Build in tracking to text atoms

* Clean up FeedSourceCard

* Clean up leading after new default

* Add deprecated notice
2024-02-20 11:03:04 -06:00

139 lines
3.4 KiB
TypeScript

import React from 'react'
import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
import {atoms as a, TextStyleProp, flatten} from '#/alf'
import {InlineLink} from '#/components/Link'
import {Text, TextProps} from '#/components/Typography'
import {toShortUrl} from 'lib/strings/url-helpers'
import {getAgent} from '#/state/session'
const WORD_WRAP = {wordWrap: 1}
export function RichText({
testID,
value,
style,
numberOfLines,
disableLinks,
resolveFacets = false,
selectable,
}: TextStyleProp &
Pick<TextProps, 'selectable'> & {
value: RichTextAPI | string
testID?: string
numberOfLines?: number
disableLinks?: boolean
resolveFacets?: boolean
}) {
const detected = React.useRef(false)
const [richText, setRichText] = React.useState<RichTextAPI>(() =>
value instanceof RichTextAPI ? value : new RichTextAPI({text: value}),
)
const styles = [a.leading_snug, flatten(style)]
React.useEffect(() => {
if (!resolveFacets) return
async function detectFacets() {
const rt = new RichTextAPI({text: richText.text})
await rt.detectFacets(getAgent())
setRichText(rt)
}
if (!detected.current) {
detected.current = true
detectFacets()
}
}, [richText, setRichText, resolveFacets])
const {text, facets} = richText
if (!facets?.length) {
if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) {
return (
<Text
selectable={selectable}
testID={testID}
style={[
{
fontSize: 26,
lineHeight: 30,
},
]}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{text}
</Text>
)
}
return (
<Text
selectable={selectable}
testID={testID}
style={styles}
numberOfLines={numberOfLines}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{text}
</Text>
)
}
const els = []
let key = 0
// N.B. must access segments via `richText.segments`, not via destructuring
for (const segment of richText.segments()) {
const link = segment.link
const mention = segment.mention
if (
mention &&
AppBskyRichtextFacet.validateMention(mention).success &&
!disableLinks
) {
els.push(
<InlineLink
selectable={selectable}
key={key}
to={`/profile/${mention.did}`}
style={[...styles, {pointerEvents: 'auto'}]}
// @ts-ignore TODO
dataSet={WORD_WRAP}>
{segment.text}
</InlineLink>,
)
} else if (link && AppBskyRichtextFacet.validateLink(link).success) {
if (disableLinks) {
els.push(toShortUrl(segment.text))
} else {
els.push(
<InlineLink
selectable={selectable}
key={key}
to={link.uri}
style={[...styles, {pointerEvents: 'auto'}]}
// @ts-ignore TODO
dataSet={WORD_WRAP}
warnOnMismatchingLabel>
{toShortUrl(segment.text)}
</InlineLink>,
)
}
} else {
els.push(segment.text)
}
key++
}
return (
<Text
selectable={selectable}
testID={testID}
style={styles}
numberOfLines={numberOfLines}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{els}
</Text>
)
}