Enable profile hover card on quoted post authors (#9896)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -285,15 +285,14 @@ export function QuoteEmbed({
|
|||||||
|
|
||||||
const contents = (
|
const contents = (
|
||||||
<>
|
<>
|
||||||
<View pointerEvents="none">
|
<PostMeta
|
||||||
<PostMeta
|
author={quote.author}
|
||||||
author={quote.author}
|
moderation={moderation}
|
||||||
moderation={moderation}
|
showAvatar
|
||||||
showAvatar
|
postHref={itemHref}
|
||||||
postHref={itemHref}
|
timestamp={quote.indexedAt}
|
||||||
timestamp={quote.indexedAt}
|
linkDisabled
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
{moderation ? (
|
{moderation ? (
|
||||||
<PostAlerts modui={moderation.ui('contentView')} style={[a.py_xs]} />
|
<PostAlerts modui={moderation.ui('contentView')} style={[a.py_xs]} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ 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 {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {precacheProfile} from '#/state/queries/profile'
|
import {unstableCacheProfileView} from '#/state/queries/profile'
|
||||||
import {atoms as a, platform, useTheme, web} from '#/alf'
|
import {atoms as a, platform, useTheme, web} from '#/alf'
|
||||||
import {WebOnlyInlineLinkText} from '#/components/Link'
|
import {WebOnlyInlineLinkText} from '#/components/Link'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
@@ -29,6 +29,7 @@ interface PostMetaOpts {
|
|||||||
moderation: ModerationDecision | undefined
|
moderation: ModerationDecision | undefined
|
||||||
postHref: string
|
postHref: string
|
||||||
timestamp: string
|
timestamp: string
|
||||||
|
linkDisabled?: boolean
|
||||||
showAvatar?: boolean
|
showAvatar?: boolean
|
||||||
avatarSize?: number
|
avatarSize?: number
|
||||||
onOpenAuthor?: () => void
|
onOpenAuthor?: () => void
|
||||||
@@ -46,17 +47,19 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const onOpenAuthor = opts.onOpenAuthor
|
const onOpenAuthor = opts.onOpenAuthor
|
||||||
const onBeforePressAuthor = useCallback(() => {
|
const onBeforePressAuthor = useCallback(() => {
|
||||||
precacheProfile(queryClient, author)
|
unstableCacheProfileView(queryClient, author)
|
||||||
onOpenAuthor?.()
|
onOpenAuthor?.()
|
||||||
}, [queryClient, author, onOpenAuthor])
|
}, [queryClient, author, onOpenAuthor])
|
||||||
const onBeforePressPost = useCallback(() => {
|
const onBeforePressPost = useCallback(() => {
|
||||||
precacheProfile(queryClient, author)
|
unstableCacheProfileView(queryClient, author)
|
||||||
}, [queryClient, author])
|
}, [queryClient, author])
|
||||||
|
|
||||||
const timestampLabel = niceDate(i18n, opts.timestamp)
|
const timestampLabel = niceDate(i18n, opts.timestamp)
|
||||||
const verification = useSimpleVerificationState({profile: author})
|
const verification = useSimpleVerificationState({profile: author})
|
||||||
const {isActive: live} = useActorStatus(author)
|
const {isActive: live} = useActorStatus(author)
|
||||||
|
|
||||||
|
const MaybeLinkText = opts.linkDisabled ? Text : WebOnlyInlineLinkText
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -77,19 +80,20 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
type={author.associated?.labeler ? 'labeler' : 'user'}
|
type={author.associated?.labeler ? 'labeler' : 'user'}
|
||||||
live={live}
|
live={live}
|
||||||
hideLiveBadge
|
hideLiveBadge
|
||||||
|
disableNavigation={opts.linkDisabled}
|
||||||
/>
|
/>
|
||||||
</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 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
|
<MaybeLinkText
|
||||||
emoji
|
emoji
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
to={profileLink}
|
to={profileLink}
|
||||||
label={_(msg`View profile`)}
|
label={_(msg`View profile`)}
|
||||||
disableMismatchWarning
|
disableMismatchWarning
|
||||||
onPress={onBeforePressAuthor}
|
onPress={opts.linkDisabled ? undefined : onBeforePressAuthor}
|
||||||
style={[
|
style={[
|
||||||
a.text_md,
|
a.text_md,
|
||||||
a.font_semi_bold,
|
a.font_semi_bold,
|
||||||
@@ -104,7 +108,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
opts.moderation?.ui('displayName'),
|
opts.moderation?.ui('displayName'),
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</WebOnlyInlineLinkText>
|
</MaybeLinkText>
|
||||||
{verification.showBadge && (
|
{verification.showBadge && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -120,14 +124,14 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<WebOnlyInlineLinkText
|
<MaybeLinkText
|
||||||
emoji
|
emoji
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
to={profileLink}
|
to={profileLink}
|
||||||
label={_(msg`View profile`)}
|
label={_(msg`View profile`)}
|
||||||
disableMismatchWarning
|
disableMismatchWarning
|
||||||
disableUnderline
|
disableUnderline
|
||||||
onPress={onBeforePressAuthor}
|
onPress={opts.linkDisabled ? undefined : onBeforePressAuthor}
|
||||||
style={[
|
style={[
|
||||||
a.text_md,
|
a.text_md,
|
||||||
t.atoms.text_contrast_medium,
|
t.atoms.text_contrast_medium,
|
||||||
@@ -135,19 +139,19 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
{flexShrink: 10},
|
{flexShrink: 10},
|
||||||
]}>
|
]}>
|
||||||
{NON_BREAKING_SPACE + sanitizeHandle(handle, '@')}
|
{NON_BREAKING_SPACE + sanitizeHandle(handle, '@')}
|
||||||
</WebOnlyInlineLinkText>
|
</MaybeLinkText>
|
||||||
</View>
|
</View>
|
||||||
</ProfileHoverCard>
|
</ProfileHoverCard>
|
||||||
|
|
||||||
<TimeElapsed timestamp={opts.timestamp}>
|
<TimeElapsed timestamp={opts.timestamp}>
|
||||||
{({timeElapsed}) => (
|
{({timeElapsed}) => (
|
||||||
<WebOnlyInlineLinkText
|
<MaybeLinkText
|
||||||
to={opts.postHref}
|
to={opts.postHref}
|
||||||
label={timestampLabel}
|
label={timestampLabel}
|
||||||
title={timestampLabel}
|
title={timestampLabel}
|
||||||
disableMismatchWarning
|
disableMismatchWarning
|
||||||
disableUnderline
|
disableUnderline
|
||||||
onPress={onBeforePressPost}
|
onPress={opts.linkDisabled ? undefined : onBeforePressPost}
|
||||||
style={[
|
style={[
|
||||||
a.pl_xs,
|
a.pl_xs,
|
||||||
a.text_md,
|
a.text_md,
|
||||||
@@ -171,7 +175,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{timeElapsed}
|
{timeElapsed}
|
||||||
</WebOnlyInlineLinkText>
|
</MaybeLinkText>
|
||||||
)}
|
)}
|
||||||
</TimeElapsed>
|
</TimeElapsed>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {type JSX} from 'react'
|
import {useState} from 'react'
|
||||||
import {type I18n} from '@lingui/core'
|
import {type I18n} from '@lingui/core'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -11,17 +11,17 @@ export function TimeElapsed({
|
|||||||
timeToString,
|
timeToString,
|
||||||
}: {
|
}: {
|
||||||
timestamp: string
|
timestamp: string
|
||||||
children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element
|
children: ({timeElapsed}: {timeElapsed: string}) => React.ReactElement
|
||||||
timeToString?: (i18n: I18n, timeElapsed: string) => string
|
timeToString?: (i18n: I18n, timeElapsed: string) => string
|
||||||
}) {
|
}) {
|
||||||
const {i18n} = useLingui()
|
const {i18n} = useLingui()
|
||||||
const ago = useGetTimeAgo()
|
const ago = useGetTimeAgo()
|
||||||
const tick = useTickEveryMinute()
|
const tick = useTickEveryMinute()
|
||||||
const [timeElapsed, setTimeAgo] = React.useState(() =>
|
const [timeElapsed, setTimeAgo] = useState(() =>
|
||||||
timeToString ? timeToString(i18n, timestamp) : ago(timestamp, tick),
|
timeToString ? timeToString(i18n, timestamp) : ago(timestamp, tick),
|
||||||
)
|
)
|
||||||
|
|
||||||
const [prevTick, setPrevTick] = React.useState(tick)
|
const [prevTick, setPrevTick] = useState(tick)
|
||||||
if (prevTick !== tick) {
|
if (prevTick !== tick) {
|
||||||
setPrevTick(tick)
|
setPrevTick(tick)
|
||||||
setTimeAgo(
|
setTimeAgo(
|
||||||
|
|||||||
Reference in New Issue
Block a user