diff options
author | Jan-Olof Eriksson <jan-olof.eriksson@iki.fi> | 2024-02-29 11:55:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-29 11:55:03 +0200 |
commit | 963a44ab872a1044d6997a8fcf7b2fc754ac618a (patch) | |
tree | bbd64f464a8f14e55cbb06e28811cdc43f059d29 /src/components/RichText.tsx | |
parent | 1f9562847512bb41cd8bb381b735a388be4db59b (diff) | |
parent | a35976cdc9b6467ad8b6e0c4ff46ba684fee9064 (diff) | |
download | voidsky-963a44ab872a1044d6997a8fcf7b2fc754ac618a.tar.zst |
Merge branch 'bluesky-social:main' into main
Diffstat (limited to 'src/components/RichText.tsx')
-rw-r--r-- | src/components/RichText.tsx | 128 |
1 files changed, 106 insertions, 22 deletions
diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index c72fcabdd..3d5f08026 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -1,11 +1,15 @@ import React from 'react' import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api' +import {useLingui} from '@lingui/react' +import {msg} from '@lingui/macro' -import {atoms as a, TextStyleProp, flatten} from '#/alf' +import {atoms as a, TextStyleProp, flatten, useTheme, web, native} 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' +import {TagMenu, useTagMenuControl} from '#/components/TagMenu' +import {isNative} from '#/platform/detection' +import {useInteractionState} from '#/components/hooks/useInteractionState' const WORD_WRAP = {wordWrap: 1} @@ -15,37 +19,25 @@ export function RichText({ style, numberOfLines, disableLinks, - resolveFacets = false, selectable, + enableTags = false, + authorHandle, }: TextStyleProp & Pick<TextProps, 'selectable'> & { value: RichTextAPI | string testID?: string numberOfLines?: number disableLinks?: boolean - resolveFacets?: boolean + enableTags?: boolean + authorHandle?: string }) { - const detected = React.useRef(false) - const [richText, setRichText] = React.useState<RichTextAPI>(() => - value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), + const richText = React.useMemo( + () => + value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), + [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) { @@ -85,6 +77,7 @@ export function RichText({ for (const segment of richText.segments()) { const link = segment.link const mention = segment.mention + const tag = segment.tag if ( mention && AppBskyRichtextFacet.validateMention(mention).success && @@ -118,6 +111,21 @@ export function RichText({ </InlineLink>, ) } + } else if ( + !disableLinks && + enableTags && + tag && + AppBskyRichtextFacet.validateTag(tag).success + ) { + els.push( + <RichTextTag + key={key} + text={segment.text} + style={styles} + selectable={selectable} + authorHandle={authorHandle} + />, + ) } else { els.push(segment.text) } @@ -136,3 +144,79 @@ export function RichText({ </Text> ) } + +function RichTextTag({ + text: tag, + style, + selectable, + authorHandle, +}: { + text: string + selectable?: boolean + authorHandle?: string +} & TextStyleProp) { + const t = useTheme() + const {_} = useLingui() + const control = useTagMenuControl() + const { + state: hovered, + onIn: onHoverIn, + onOut: onHoverOut, + } = useInteractionState() + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() + const { + state: pressed, + onIn: onPressIn, + onOut: onPressOut, + } = useInteractionState() + + const open = React.useCallback(() => { + control.open() + }, [control]) + + /* + * N.B. On web, this is wrapped in another pressable comopnent with a11y + * labels, etc. That's why only some of these props are applied here. + */ + + return ( + <React.Fragment> + <TagMenu control={control} tag={tag} authorHandle={authorHandle}> + <Text + selectable={selectable} + {...native({ + accessibilityLabel: _(msg`Hashtag: ${tag}`), + accessibilityHint: _(msg`Click here to open tag menu for ${tag}`), + accessibilityRole: isNative ? 'button' : undefined, + onPress: open, + onPressIn: onPressIn, + onPressOut: onPressOut, + })} + {...web({ + onMouseEnter: onHoverIn, + onMouseLeave: onHoverOut, + })} + // @ts-ignore + onFocus={onFocus} + onBlur={onBlur} + style={[ + style, + { + pointerEvents: 'auto', + color: t.palette.primary_500, + }, + web({ + cursor: 'pointer', + }), + (hovered || focused || pressed) && { + ...web({outline: 0}), + textDecorationLine: 'underline', + textDecorationColor: t.palette.primary_500, + }, + ]}> + {tag} + </Text> + </TagMenu> + </React.Fragment> + ) +} |