Hover card on anchor displayName/handle (#8479)
* add hover to anchor display name / handle * use newer link component * Wrap using a single hover element --------- Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -3,6 +3,5 @@ import type React from 'react'
|
|||||||
export type ProfileHoverCardProps = {
|
export type ProfileHoverCardProps = {
|
||||||
children: React.ReactElement
|
children: React.ReactElement
|
||||||
did: string
|
did: string
|
||||||
inline?: boolean
|
|
||||||
disable?: boolean
|
disable?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {TextStyle} from 'react-native'
|
import {type TextStyle} from 'react-native'
|
||||||
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'
|
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'
|
||||||
|
|
||||||
import {toShortUrl} from '#/lib/strings/url-helpers'
|
import {toShortUrl} from '#/lib/strings/url-helpers'
|
||||||
import {atoms as a, flatten, TextStyleProp} from '#/alf'
|
import {atoms as a, flatten, type TextStyleProp} from '#/alf'
|
||||||
import {isOnlyEmoji} from '#/alf/typography'
|
import {isOnlyEmoji} from '#/alf/typography'
|
||||||
import {InlineLinkText, LinkProps} from '#/components/Link'
|
import {InlineLinkText, type LinkProps} from '#/components/Link'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {RichTextTag} from '#/components/RichTextTag'
|
import {RichTextTag} from '#/components/RichTextTag'
|
||||||
import {Text, TextProps} from '#/components/Typography'
|
import {Text, type TextProps} from '#/components/Typography'
|
||||||
|
|
||||||
const WORD_WRAP = {wordWrap: 1}
|
const WORD_WRAP = {wordWrap: 1}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ export function RichText({
|
|||||||
!disableLinks
|
!disableLinks
|
||||||
) {
|
) {
|
||||||
els.push(
|
els.push(
|
||||||
<ProfileHoverCard key={key} inline did={mention.did}>
|
<ProfileHoverCard key={key} did={mention.did}>
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
to={`/profile/${mention.did}`}
|
to={`/profile/${mention.did}`}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {makeProfileLink} from '#/lib/routes/links'
|
|||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {niceDate} from '#/lib/strings/time'
|
import {niceDate} from '#/lib/strings/time'
|
||||||
import {s} from '#/lib/styles'
|
|
||||||
import {getTranslatorLink, isPostInLanguage} from '#/locale/helpers'
|
import {getTranslatorLink, isPostInLanguage} from '#/locale/helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {
|
import {
|
||||||
@@ -34,7 +33,6 @@ import {type OnPostSuccessData} from '#/state/shell/composer'
|
|||||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||||
import {type PostSource} from '#/state/unstable-post-source'
|
import {type PostSource} from '#/state/unstable-post-source'
|
||||||
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
|
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
|
||||||
import {Link} from '#/view/com/util/Link'
|
|
||||||
import {formatCount} from '#/view/com/util/numeric/format'
|
import {formatCount} from '#/view/com/util/numeric/format'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {
|
import {
|
||||||
@@ -47,13 +45,14 @@ import {colors} from '#/components/Admonition'
|
|||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock'
|
import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock'
|
||||||
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText, Link} from '#/components/Link'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {type AppModerationCause} from '#/components/Pills'
|
import {type AppModerationCause} from '#/components/Pills'
|
||||||
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
||||||
import {PostControls} from '#/components/PostControls'
|
import {PostControls} from '#/components/PostControls'
|
||||||
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import * as Skele from '#/components/Skeleton'
|
import * as Skele from '#/components/Skeleton'
|
||||||
@@ -197,7 +196,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
|
|
||||||
const threadRootUri = record.reply?.root?.uri || post.uri
|
const threadRootUri = record.reply?.root?.uri || post.uri
|
||||||
const authorHref = makeProfileLink(post.author)
|
const authorHref = makeProfileLink(post.author)
|
||||||
const authorTitle = post.author.handle
|
|
||||||
const isThreadAuthor = getThreadAuthor(post, record) === currentAccount?.did
|
const isThreadAuthor = getThreadAuthor(post, record) === currentAccount?.did
|
||||||
|
|
||||||
const likesHref = useMemo(() => {
|
const likesHref = useMemo(() => {
|
||||||
@@ -321,42 +319,57 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
live={live}
|
live={live}
|
||||||
onBeforePress={onOpenAuthor}
|
onBeforePress={onOpenAuthor}
|
||||||
/>
|
/>
|
||||||
<View style={[a.flex_1]}>
|
<ProfileHoverCard did={post.author.did}>
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
<View style={[a.flex_1]}>
|
||||||
<Link
|
<View style={[a.flex_row, a.align_center]}>
|
||||||
style={[a.flex_shrink]}
|
<Link
|
||||||
href={authorHref}
|
to={authorHref}
|
||||||
title={authorTitle}
|
style={[a.flex_shrink]}
|
||||||
onBeforePress={onOpenAuthor}>
|
label={sanitizeDisplayName(
|
||||||
<Text
|
|
||||||
emoji
|
|
||||||
style={[a.text_lg, a.font_bold, a.leading_snug, a.self_start]}
|
|
||||||
numberOfLines={1}>
|
|
||||||
{sanitizeDisplayName(
|
|
||||||
post.author.displayName ||
|
post.author.displayName ||
|
||||||
sanitizeHandle(post.author.handle),
|
sanitizeHandle(post.author.handle),
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}
|
)}
|
||||||
</Text>
|
onPress={onOpenAuthor}>
|
||||||
</Link>
|
<Text
|
||||||
|
emoji
|
||||||
|
style={[
|
||||||
|
a.text_lg,
|
||||||
|
a.font_bold,
|
||||||
|
a.leading_snug,
|
||||||
|
a.self_start,
|
||||||
|
]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
{sanitizeDisplayName(
|
||||||
|
post.author.displayName ||
|
||||||
|
sanitizeHandle(post.author.handle),
|
||||||
|
moderation.ui('displayName'),
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<View style={[{paddingLeft: 3, top: -1}]}>
|
<View style={[{paddingLeft: 3, top: -1}]}>
|
||||||
<VerificationCheckButton profile={authorShadow} size="md" />
|
<VerificationCheckButton profile={authorShadow} size="md" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={[a.align_start]}>
|
||||||
|
<Link
|
||||||
|
style={[a.flex_shrink]}
|
||||||
|
to={authorHref}
|
||||||
|
label={sanitizeHandle(post.author.handle, '@')}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
{sanitizeHandle(post.author.handle, '@')}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Link style={s.flex1} href={authorHref} title={authorTitle}>
|
</ProfileHoverCard>
|
||||||
<Text
|
|
||||||
emoji
|
|
||||||
style={[
|
|
||||||
a.text_md,
|
|
||||||
a.leading_snug,
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
]}
|
|
||||||
numberOfLines={1}>
|
|
||||||
{sanitizeHandle(post.author.handle, '@')}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
</View>
|
|
||||||
{showFollowButton && (
|
{showFollowButton && (
|
||||||
<View>
|
<View>
|
||||||
<PostThreadFollowBtn did={post.author.did} />
|
<PostThreadFollowBtn did={post.author.did} />
|
||||||
@@ -417,7 +430,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
t.atoms.border_contrast_low,
|
t.atoms.border_contrast_low,
|
||||||
]}>
|
]}>
|
||||||
{post.repostCount != null && post.repostCount !== 0 ? (
|
{post.repostCount != null && post.repostCount !== 0 ? (
|
||||||
<Link href={repostsHref} title={_(msg`Reposts of this post`)}>
|
<Link to={repostsHref} label={_(msg`Reposts of this post`)}>
|
||||||
<Text
|
<Text
|
||||||
testID="repostCount-expanded"
|
testID="repostCount-expanded"
|
||||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
@@ -435,7 +448,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
{post.quoteCount != null &&
|
{post.quoteCount != null &&
|
||||||
post.quoteCount !== 0 &&
|
post.quoteCount !== 0 &&
|
||||||
!post.viewer?.embeddingDisabled ? (
|
!post.viewer?.embeddingDisabled ? (
|
||||||
<Link href={quotesHref} title={_(msg`Quotes of this post`)}>
|
<Link to={quotesHref} label={_(msg`Quotes of this post`)}>
|
||||||
<Text
|
<Text
|
||||||
testID="quoteCount-expanded"
|
testID="quoteCount-expanded"
|
||||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
@@ -451,7 +464,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
{post.likeCount != null && post.likeCount !== 0 ? (
|
{post.likeCount != null && post.likeCount !== 0 ? (
|
||||||
<Link href={likesHref} title={_(msg`Likes on this post`)}>
|
<Link to={likesHref} label={_(msg`Likes on this post`)}>
|
||||||
<Text
|
<Text
|
||||||
testID="likeCount-expanded"
|
testID="likeCount-expanded"
|
||||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ function PostInner({
|
|||||||
) : (
|
) : (
|
||||||
<Trans context="description">
|
<Trans context="description">
|
||||||
Reply to{' '}
|
Reply to{' '}
|
||||||
<ProfileHoverCard inline did={replyAuthorDid}>
|
<ProfileHoverCard did={replyAuthorDid}>
|
||||||
<UserInfoText
|
<UserInfoText
|
||||||
type="sm"
|
type="sm"
|
||||||
did={replyAuthorDid}
|
did={replyAuthorDid}
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ let FeedItemInner = ({
|
|||||||
) : (
|
) : (
|
||||||
<Trans>
|
<Trans>
|
||||||
Reposted by{' '}
|
Reposted by{' '}
|
||||||
<ProfileHoverCard inline did={reason.by.did}>
|
<ProfileHoverCard did={reason.by.did}>
|
||||||
<TextLinkOnWebOnly
|
<TextLinkOnWebOnly
|
||||||
type="sm-bold"
|
type="sm-bold"
|
||||||
style={pal.textLight}
|
style={pal.textLight}
|
||||||
@@ -607,7 +607,7 @@ function ReplyToLabel({
|
|||||||
label = (
|
label = (
|
||||||
<Trans context="description">
|
<Trans context="description">
|
||||||
Reply to{' '}
|
Reply to{' '}
|
||||||
<ProfileHoverCard inline did={profile.did}>
|
<ProfileHoverCard did={profile.did}>
|
||||||
<TextLinkOnWebOnly
|
<TextLinkOnWebOnly
|
||||||
type="md"
|
type="md"
|
||||||
style={pal.textLight}
|
style={pal.textLight}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View style={[a.flex_row, a.align_end, a.flex_shrink]}>
|
<View style={[a.flex_row, a.align_end, a.flex_shrink]}>
|
||||||
<ProfileHoverCard inline did={author.did}>
|
<ProfileHoverCard did={author.did}>
|
||||||
<View style={[a.flex_row, a.align_end, a.flex_shrink]}>
|
<View style={[a.flex_row, a.align_end, a.flex_shrink]}>
|
||||||
<WebOnlyInlineLinkText
|
<WebOnlyInlineLinkText
|
||||||
emoji
|
emoji
|
||||||
|
|||||||
Reference in New Issue
Block a user