diff options
-rw-r--r-- | assets/icons/arrowCornerDownRight_stroke2_rounded_2_rounded.svg | 1 | ||||
-rw-r--r-- | src/components/Post/PostRepliedTo.tsx | 63 | ||||
-rw-r--r-- | src/components/icons/ArrowCornerDownRight.tsx | 7 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 45 | ||||
-rw-r--r-- | src/view/com/posts/PostFeedItem.tsx | 84 | ||||
-rw-r--r-- | src/view/com/util/UserInfoText.tsx | 68 |
6 files changed, 110 insertions, 158 deletions
diff --git a/assets/icons/arrowCornerDownRight_stroke2_rounded_2_rounded.svg b/assets/icons/arrowCornerDownRight_stroke2_rounded_2_rounded.svg new file mode 100644 index 000000000..fa21c9824 --- /dev/null +++ b/assets/icons/arrowCornerDownRight_stroke2_rounded_2_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M15.793 10.293a1 1 0 0 1 1.338-.068l.076.068 3.293 3.293a2 2 0 0 1 .138 2.677l-.138.151-3.293 3.293a1 1 0 1 1-1.414-1.414L18.086 16H8a5 5 0 0 1-5-5V5a1 1 0 0 1 2 0v6a3 3 0 0 0 3 3h10.086l-2.293-2.293-.068-.076a1 1 0 0 1 .068-1.338Z"/></svg> diff --git a/src/components/Post/PostRepliedTo.tsx b/src/components/Post/PostRepliedTo.tsx new file mode 100644 index 000000000..3085826c2 --- /dev/null +++ b/src/components/Post/PostRepliedTo.tsx @@ -0,0 +1,63 @@ +import {View} from 'react-native' +import {Trans} from '@lingui/macro' + +import {useSession} from '#/state/session' +import {UserInfoText} from '#/view/com/util/UserInfoText' +import {atoms as a, useTheme} from '#/alf' +import {ArrowCornerDownRight_Stroke2_Corner2_Rounded as ArrowCornerDownRightIcon} from '#/components/icons/ArrowCornerDownRight' +import {ProfileHoverCard} from '#/components/ProfileHoverCard' +import {Text} from '#/components/Typography' +import type * as bsky from '#/types/bsky' + +export function PostRepliedTo({ + parentAuthor, + isParentBlocked, + isParentNotFound, +}: { + parentAuthor: string | bsky.profile.AnyProfileView | undefined + isParentBlocked?: boolean + isParentNotFound?: boolean +}) { + const t = useTheme() + const {currentAccount} = useSession() + + const textStyle = [a.text_sm, t.atoms.text_contrast_medium, a.leading_snug] + + let label + if (isParentBlocked) { + label = <Trans context="description">Replied to a blocked post</Trans> + } else if (isParentNotFound) { + label = <Trans context="description">Replied to a post</Trans> + } else if (parentAuthor) { + const did = + typeof parentAuthor === 'string' ? parentAuthor : parentAuthor.did + const isMe = currentAccount?.did === did + if (isMe) { + label = <Trans context="description">Replied to you</Trans> + } else { + label = ( + <Trans context="description"> + Replied to{' '} + <ProfileHoverCard did={did}> + <UserInfoText did={did} attr="displayName" style={textStyle} /> + </ProfileHoverCard> + </Trans> + ) + } + } + + if (!label) { + // Should not happen. + return null + } + + return ( + <View style={[a.flex_row, a.align_center, a.pb_xs, a.gap_xs]}> + <ArrowCornerDownRightIcon + size="xs" + style={[t.atoms.text_contrast_medium, {top: -1}]} + /> + <Text style={textStyle}>{label}</Text> + </View> + ) +} diff --git a/src/components/icons/ArrowCornerDownRight.tsx b/src/components/icons/ArrowCornerDownRight.tsx new file mode 100644 index 000000000..86dde7015 --- /dev/null +++ b/src/components/icons/ArrowCornerDownRight.tsx @@ -0,0 +1,7 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ArrowCornerDownRight_Stroke2_Corner2_Rounded = createSinglePathSVG( + { + path: 'M15.793 10.293a1 1 0 0 1 1.338-.068l.076.068 3.293 3.293a2 2 0 0 1 .138 2.677l-.138.151-3.293 3.293a1 1 0 1 1-1.414-1.414L18.086 16H8a5 5 0 0 1-5-5V5a1 1 0 0 1 2 0v6a3 3 0 0 0 3 3h10.086l-2.293-2.293-.068-.076a1 1 0 0 1 .068-1.338Z', + }, +) diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 6079f5c10..a8e32268e 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -8,8 +8,6 @@ import { type ModerationDecision, RichText as RichTextAPI, } from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Trans} from '@lingui/macro' import {useQueryClient} from '@tanstack/react-query' import {MAX_POST_LINES} from '#/lib/constants' @@ -17,28 +15,25 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {usePalette} from '#/lib/hooks/usePalette' import {makeProfileLink} from '#/lib/routes/links' import {countLines} from '#/lib/strings/helpers' -import {colors, s} from '#/lib/styles' +import {colors} from '#/lib/styles' import { POST_TOMBSTONE, type Shadow, usePostShadow, } from '#/state/cache/post-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {precacheProfile} from '#/state/queries/profile' -import {useSession} from '#/state/session' +import {unstableCacheProfileView} from '#/state/queries/profile' import {Link} from '#/view/com/util/Link' import {PostMeta} from '#/view/com/util/PostMeta' -import {Text} from '#/view/com/util/text/Text' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' -import {UserInfoText} from '#/view/com/util/UserInfoText' import {atoms as a} from '#/alf' import {ContentHider} from '#/components/moderation/ContentHider' import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' import {PostAlerts} from '#/components/moderation/PostAlerts' import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' +import {PostRepliedTo} from '#/components/Post/PostRepliedTo' import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' import {PostControls} from '#/components/PostControls' -import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' import * as bsky from '#/types/bsky' @@ -145,12 +140,9 @@ function PostInner({ }, [setLimitLines]) const onBeforePress = useCallback(() => { - precacheProfile(queryClient, post.author) + unstableCacheProfileView(queryClient, post.author) }, [queryClient, post.author]) - const {currentAccount} = useSession() - const isMe = replyAuthorDid === currentAccount?.did - const [hover, setHover] = useState(false) return ( <Link @@ -187,34 +179,7 @@ function PostInner({ postHref={itemHref} /> {replyAuthorDid !== '' && ( - <View style={[s.flexRow, s.mb2, s.alignCenter]}> - <FontAwesomeIcon - icon="reply" - size={9} - style={[pal.textLight, s.mr5]} - /> - <Text - type="sm" - style={[pal.textLight, s.mr2]} - lineHeight={1.2} - numberOfLines={1}> - {isMe ? ( - <Trans context="description">Reply to you</Trans> - ) : ( - <Trans context="description"> - Reply to{' '} - <ProfileHoverCard did={replyAuthorDid}> - <UserInfoText - type="sm" - did={replyAuthorDid} - attr="displayName" - style={[pal.textLight]} - /> - </ProfileHoverCard> - </Trans> - )} - </Text> - </View> + <PostRepliedTo parentAuthor={replyAuthorDid} /> )} <LabelsOnMyPost post={post} /> <ContentHider diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index 0593ba931..14bbc4746 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -9,10 +9,6 @@ import { type ModerationDecision, RichText as RichTextAPI, } from '@atproto/api' -import { - FontAwesomeIcon, - type FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' @@ -26,7 +22,6 @@ import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {countLines} from '#/lib/strings/helpers' -import {s} from '#/lib/styles' import { POST_TOMBSTONE, type Shadow, @@ -54,6 +49,7 @@ import {PostAlerts} from '#/components/moderation/PostAlerts' import {type AppModerationCause} from '#/components/Pills' import {Embed} from '#/components/Post/Embed' import {PostEmbedViewContext} from '#/components/Post/Embed/types' +import {PostRepliedTo} from '#/components/Post/PostRepliedTo' import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' import {PostControls} from '#/components/PostControls' import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug' @@ -448,10 +444,10 @@ let FeedItemInner = ({ /> {showReplyTo && (parentAuthor || isParentBlocked || isParentNotFound) && ( - <ReplyToLabel - blocked={isParentBlocked} - notFound={isParentNotFound} - profile={parentAuthor} + <PostRepliedTo + parentAuthor={parentAuthor} + isParentBlocked={isParentBlocked} + isParentNotFound={isParentNotFound} /> )} <LabelsOnMyPost post={post} /> @@ -576,80 +572,10 @@ let PostContent = ({ } PostContent = memo(PostContent) -function ReplyToLabel({ - profile, - blocked, - notFound, -}: { - profile: AppBskyActorDefs.ProfileViewBasic | undefined - blocked?: boolean - notFound?: boolean -}) { - const pal = usePalette('default') - const {currentAccount} = useSession() - - let label - if (blocked) { - label = <Trans context="description">Reply to a blocked post</Trans> - } else if (notFound) { - label = <Trans context="description">Reply to a post</Trans> - } else if (profile != null) { - const isMe = profile.did === currentAccount?.did - if (isMe) { - label = <Trans context="description">Reply to you</Trans> - } else { - label = ( - <Trans context="description"> - Reply to{' '} - <ProfileHoverCard did={profile.did}> - <TextLinkOnWebOnly - type="md" - style={pal.textLight} - lineHeight={1.2} - numberOfLines={1} - href={makeProfileLink(profile)} - text={ - <Text emoji type="md" style={pal.textLight} lineHeight={1.2}> - {profile.displayName - ? sanitizeDisplayName(profile.displayName) - : sanitizeHandle(profile.handle)} - </Text> - } - /> - </ProfileHoverCard> - </Trans> - ) - } - } - - if (!label) { - // Should not happen. - return null - } - - return ( - <View style={[s.flexRow, s.mb2, s.alignCenter]}> - <FontAwesomeIcon - icon="reply" - size={9} - style={[{color: pal.colors.textLight} as FontAwesomeIconStyle, s.mr5]} - /> - <Text - type="md" - style={[pal.textLight, s.mr2]} - lineHeight={1.2} - numberOfLines={1}> - {label} - </Text> - </View> - ) -} - const styles = StyleSheet.create({ outer: { paddingLeft: 10, paddingRight: 15, - // @ts-ignore web only -prf cursor: 'pointer', }, replyLine: { diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 64aa37ff2..028b85d38 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -1,27 +1,25 @@ -import {StyleProp, StyleSheet, TextStyle} from 'react-native' -import {AppBskyActorGetProfile as GetProfile} from '@atproto/api' +import {type StyleProp, type TextStyle} from 'react-native' +import {type AppBskyActorGetProfile} from '@atproto/api' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' -import {TypographyVariant} from '#/lib/ThemeContext' import {STALE} from '#/state/queries' import {useProfileQuery} from '#/state/queries/profile' -import {TextLinkOnWebOnly} from './Link' +import {atoms as a} from '#/alf' +import {InlineLinkText} from '#/components/Link' +import {Text} from '#/components/Typography' import {LoadingPlaceholder} from './LoadingPlaceholder' -import {Text} from './text/Text' export function UserInfoText({ - type = 'md', did, attr, failed, prefix, style, }: { - type?: TypographyVariant did: string - attr?: keyof GetProfile.OutputSchema + attr?: keyof AppBskyActorGetProfile.OutputSchema loading?: string failed?: string prefix?: string @@ -35,45 +33,37 @@ export function UserInfoText({ staleTime: STALE.INFINITY, }) - let inner if (isError) { - inner = ( - <Text type={type} style={style} numberOfLines={1}> + return ( + <Text style={style} numberOfLines={1}> {failed} </Text> ) } else if (profile) { - inner = ( - <TextLinkOnWebOnly - type={type} + const text = `${prefix || ''}${sanitizeDisplayName( + typeof profile[attr] === 'string' && profile[attr] + ? (profile[attr] as string) + : sanitizeHandle(profile.handle), + )}` + return ( + <InlineLinkText + label={text} style={style} - lineHeight={1.2} numberOfLines={1} - href={makeProfileLink(profile)} - text={ - <Text emoji type={type} style={style} lineHeight={1.2}> - {`${prefix || ''}${sanitizeDisplayName( - typeof profile[attr] === 'string' && profile[attr] - ? (profile[attr] as string) - : sanitizeHandle(profile.handle), - )}`} - </Text> - } - /> - ) - } else { - inner = ( - <LoadingPlaceholder - width={80} - height={8} - style={styles.loadingPlaceholder} - /> + to={makeProfileLink(profile)}> + <Text emoji style={style}> + {text} + </Text> + </InlineLinkText> ) } - return inner + // eslint-disable-next-line bsky-internal/avoid-unwrapped-text + return ( + <LoadingPlaceholder + width={80} + height={8} + style={[a.relative, {top: 1, left: 2}]} + /> + ) } - -const styles = StyleSheet.create({ - loadingPlaceholder: {position: 'relative', top: 1, left: 2}, -}) |