From e72bcd9a99b32e30a033eab3befada9440a7a53e Mon Sep 17 00:00:00 2001 From: maelstrom Date: Mon, 15 Jun 2026 10:53:27 +0200 Subject: [PATCH] feat: renderer custom emojis on web --- src/components/LabelingServiceCard/index.tsx | 13 ++++- src/components/Post/Embed/index.tsx | 1 + src/components/ProfileCard.tsx | 1 + src/components/ProfileHoverCard/index.web.tsx | 1 + src/components/RichText.tsx | 56 ++++++++++++++++++- src/components/dms/MessageItem.tsx | 1 + .../Messages/components/MessageInputEmbed.tsx | 1 + .../components/ThreadItemAnchor.tsx | 1 + .../PostThread/components/ThreadItemPost.tsx | 1 + .../components/ThreadItemTreePost.tsx | 1 + .../Profile/Header/ProfileHeaderLabeler.tsx | 1 + .../Profile/Header/ProfileHeaderStandard.tsx | 1 + src/screens/VideoFeed/index.tsx | 49 ++++++++++++++-- src/view/com/post/Post.tsx | 1 + src/view/com/posts/PostFeedItem.tsx | 1 + 15 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index 10d8c9733..7e083b78a 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -51,11 +51,19 @@ export function Title({value}: {value: string}) { ) } -export function Description({value, handle}: {value?: string; handle: string}) { +export function Description({ + value, + handle, + did, +}: { + value?: string + handle: string + did: string +}) { const {_} = useLingui() return value ? ( - + ) : ( @@ -139,6 +147,7 @@ export function Default({ {labeler.likeCount ? : null} diff --git a/src/components/Post/Embed/index.tsx b/src/components/Post/Embed/index.tsx index 4919e5005..324a60ff8 100644 --- a/src/components/Post/Embed/index.tsx +++ b/src/components/Post/Embed/index.tsx @@ -429,6 +429,7 @@ export function QuoteEmbed({ style={a.text_md} numberOfLines={20} disableLinks + authorDid={quote.author.did} /> ) : null} {quote.embed && ( diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index d470222ca..fa724356e 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -429,6 +429,7 @@ export function Description({ style={style} numberOfLines={numberOfLines} disableLinks + authorDid={profile.did} /> ) diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index e49e7da17..c3678664d 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -748,6 +748,7 @@ function Inner({ numberOfLines={8} value={descriptionRT} onLinkPress={hide} + authorDid={profile.did} /> ) : undefined} diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 0dd348a71..c9531826e 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -1,8 +1,18 @@ import {useMemo} from 'react' import {type StyleProp, type TextStyle} from 'react-native' -import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' +import {Image as ExpoImage} from 'expo-image' +import { + AppBskyRichtextFacet, + type BlobRef, + type ComAtprotoRepoStrongRef, + RichText as RichTextAPI, +} from '@atproto/api' +import {useLingui} from '@lingui/react/macro' import {toShortUrl} from '#/lib/strings/url-helpers' +import {useImageCdnHost} from '#/state/preferences' +import {applyImageTransforms} from '#/state/preferences/image-cdn-host' +import { useThumbnailFormat } from '#/state/preferences/thumbnail-format' import {atoms as a, flatten, type TextStyleProp} from '#/alf' import {isOnlyEmoji} from '#/alf/typography' import {InlineLinkText, type LinkProps} from '#/components/Link' @@ -23,6 +33,7 @@ export type RichTextProps = TextStyleProp & disableLinks?: boolean enableTags?: boolean authorHandle?: string + authorDid?: string onLinkPress?: LinkProps['onPress'] interactiveStyle?: StyleProp emojiMultiplier?: number @@ -39,6 +50,18 @@ export type RichTextProps = TextStyleProp & disableMentionFacetValidation?: true } +type EmojiFeature = { + name: string + alt: string + emoji: ComAtprotoRepoStrongRef.Main + image: BlobRef +} + +// This is a bit silly, but it works +function makeCdnUri(did: string, cid: string) { + return `https://cdn.bsky.app/img/avatar_thumbnail/plain/${did}/${cid}@png` +} + export function RichText({ testID, value, @@ -55,7 +78,11 @@ export function RichText({ onTextLayout, shouldProxyLinks, disableMentionFacetValidation, + authorDid, }: RichTextProps) { + const {t: l} = useLingui() + const imageCdnHost = useImageCdnHost() + const thumbnailFormat = useThumbnailFormat() const richText = useMemo(() => { if (value instanceof RichTextAPI) { return value @@ -113,6 +140,10 @@ export function RichText({ const link = segment.link const mention = segment.mention const tag = segment.tag + const userEmoji = segment.facet?.features.find( + feature => + feature.$type === 'dev.maelstrom.bsky.richtext.facet#userEmoji', + ) as EmojiFeature | undefined if ( mention && @@ -155,6 +186,29 @@ export function RichText({ , ) } + } else if (userEmoji && authorDid) { + els.push( + // TODO(maelstrom): This uses inline-block, which is not compatible with mobile + , + ) } else if ( !disableLinks && enableTags && diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index 9d1b449f5..5fb20207c 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -484,6 +484,7 @@ let MessageItem = ({ enableTags emojiMultiplier={3} shouldProxyLinks={true} + authorDid={profile?.did} /> )} diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 4b69e1f67..74b38007b 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -251,6 +251,7 @@ function MessageInputPostEmbed({ value={rt} style={[a.text_sm, t.atoms.text_contrast_high]} authorHandle={post.author.handle} + authorDid={post.author.did} numberOfLines={3} /> )} diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index d4e35dc9a..f31b6dd3b 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -469,6 +469,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ value={richText} style={[a.flex_1, isCompactPosts ? a.text_md : a.text_lg]} authorHandle={post.author.handle} + authorDid={post.author.did} shouldProxyLinks={true} /> ) : undefined} diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx index 1ed8707e8..65fb80264 100644 --- a/src/screens/PostThread/components/ThreadItemPost.tsx +++ b/src/screens/PostThread/components/ThreadItemPost.tsx @@ -346,6 +346,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ style={[a.flex_1, a.text_md]} numberOfLines={limitLines ? MAX_POST_LINES : undefined} authorHandle={post.author.handle} + authorDid={post.author.did} shouldProxyLinks={true} /> {limitLines && ( diff --git a/src/screens/PostThread/components/ThreadItemTreePost.tsx b/src/screens/PostThread/components/ThreadItemTreePost.tsx index 1faf35a12..0a2e2b656 100644 --- a/src/screens/PostThread/components/ThreadItemTreePost.tsx +++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx @@ -355,6 +355,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({ style={[a.flex_1, a.text_md]} numberOfLines={limitLines ? MAX_POST_LINES : undefined} authorHandle={post.author.handle} + authorDid={post.author.did} shouldProxyLinks={true} /> {limitLines && ( diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index cb491575b..de7fe5664 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -139,6 +139,7 @@ let ProfileHeaderLabeler = ({ value={descriptionRT} enableTags authorHandle={profile.handle} + authorDid={profile.did} /> ) : undefined} diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index c67db6aeb..e5cd905a6 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -238,6 +238,7 @@ let ProfileHeaderStandard = ({ value={descriptionRT} enableTags authorHandle={profile.handle} + authorDid={profile.did} /> ) : undefined} diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 549a481e2..9a5d30325 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -980,15 +980,53 @@ function Overlay({ ) : ( Follow )} - - - ) - )} + + {profile.viewer?.following ? ( + Following + ) : ( + Follow + )} + + + )} + /> + ) : ( + + ))} {record?.text?.trim() && ( )} {record && ( @@ -1058,9 +1096,11 @@ function Overlay({ function ExpandableRichTextView({ value, authorHandle, + authorDid, }: { value: RichTextAPI authorHandle?: string + authorDid?: string }) { const {height: screenHeight} = useSafeAreaFrame() const [expanded, setExpanded] = useState(false) @@ -1096,6 +1136,7 @@ function ExpandableRichTextView({ value={value} style={[a.text_sm, a.flex_1, a.leading_relaxed]} authorHandle={authorHandle} + authorDid={authorDid} enableTags numberOfLines={ expanded || screenReaderEnabled ? undefined : constrained ? 2 : 2 diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 3233f8d6d..248313987 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -239,6 +239,7 @@ function PostInner({ numberOfLines={limitLines ? MAX_POST_LINES : undefined} style={[a.flex_1, a.text_md]} authorHandle={post.author.handle} + authorDid={post.author.did} shouldProxyLinks={true} /> {limitLines && ( diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index 42460ebfb..084641e82 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -532,6 +532,7 @@ let PostContent = ({ numberOfLines={limitLines ? MAX_POST_LINES : undefined} style={[a.flex_1, a.text_md]} authorHandle={postAuthor.handle} + authorDid={post.author.did} shouldProxyLinks={true} /> {limitLines && ( -- 2.51.2