Polish known likers in feeds and posts
This commit is contained in:
@@ -13,13 +13,17 @@ export function AvatarStack({
|
||||
size = 26,
|
||||
numPending,
|
||||
backgroundColor,
|
||||
borderWidth = 1,
|
||||
overlap,
|
||||
}: {
|
||||
profiles: bsky.profile.AnyProfileView[]
|
||||
size?: number
|
||||
numPending?: number
|
||||
backgroundColor?: string
|
||||
borderWidth?: number
|
||||
overlap?: number
|
||||
}) {
|
||||
const translation = size / 3 // overlap by 1/3
|
||||
const translation = overlap ?? size / 3
|
||||
const t = useTheme()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
@@ -55,7 +59,7 @@ export function AvatarStack({
|
||||
width: size,
|
||||
height: size,
|
||||
left: i * -translation,
|
||||
borderWidth: 1,
|
||||
borderWidth,
|
||||
borderColor: backgroundColor ?? t.atoms.bg.backgroundColor,
|
||||
borderRadius: 999,
|
||||
zIndex: 3 - i,
|
||||
@@ -63,7 +67,7 @@ export function AvatarStack({
|
||||
]}>
|
||||
{item.profile && (
|
||||
<UserAvatar
|
||||
size={size - 2}
|
||||
size={size - borderWidth * 2}
|
||||
avatar={item.profile.avatar}
|
||||
type={item.profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
moderation={item.moderation.ui('avatar')}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {View} from 'react-native'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderateProfile} from '@bsky/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
@@ -33,9 +33,6 @@ export function KnownLikers({
|
||||
const moderationOpts = useModerationOpts()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const likeCount = post.likeCount ?? 0
|
||||
if (likeCount === 0) return null
|
||||
|
||||
const knownLikersAndModeration = moderationOpts
|
||||
? (post.viewer?.knownLikers?.actors ?? [])
|
||||
.map(actor => ({
|
||||
@@ -64,19 +61,13 @@ export function KnownLikers({
|
||||
moderation.ui('displayName'),
|
||||
),
|
||||
}))
|
||||
const isFeed = variant === 'feed'
|
||||
const rowLabel =
|
||||
variant === 'feed'
|
||||
? likeCount > 1
|
||||
? l`${names[0].displayName} and ${likeCount - 1} others liked this`
|
||||
: l`${names[0].displayName} liked this`
|
||||
: names.length >= 2
|
||||
? l`Liked by ${names[0].displayName} and ${names[1].displayName}`
|
||||
: l`Liked by ${names[0].displayName}`
|
||||
const textStyle = [
|
||||
variant === 'feed' ? a.text_xs : a.text_sm,
|
||||
t.atoms.text_contrast_medium,
|
||||
]
|
||||
const nameStyle = [a.text_sm, a.font_semi_bold, t.atoms.text]
|
||||
names.length >= 2
|
||||
? l`Liked by ${names[0].displayName} and ${names[1].displayName}`
|
||||
: l`Liked by ${names[0].displayName}`
|
||||
const textStyle = [a.text_sm, t.atoms.text_contrast_medium]
|
||||
const nameStyle = [a.text_sm, a.font_medium, t.atoms.text_contrast_medium]
|
||||
|
||||
const nameLink = (name: (typeof names)[number]) => (
|
||||
<ProfileHoverCard key={name.did} did={name.did} inline>
|
||||
@@ -92,35 +83,21 @@ export function KnownLikers({
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={[a.w_full, a.flex_row]}>
|
||||
<View style={[a.w_full, a.flex_row, isFeed && a.mt_sm]}>
|
||||
<Link
|
||||
to={likesHref}
|
||||
label={rowLabel}
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
variant === 'feed' ? a.gap_xs : a.gap_sm,
|
||||
a.flex_shrink,
|
||||
]}
|
||||
style={[a.flex_row, a.align_center, a.gap_xs, a.flex_shrink]}
|
||||
onPress={() => ax.metric('post:likedBy:click', {})}>
|
||||
<AvatarStack
|
||||
profiles={aviStackProfiles}
|
||||
size={variant === 'feed' ? 16 : 20}
|
||||
size={16}
|
||||
overlap={4}
|
||||
borderWidth={0.5}
|
||||
backgroundColor={t.atoms.bg_contrast_25.backgroundColor}
|
||||
/>
|
||||
<Text testID="knownLikersStat" style={[a.flex_shrink, textStyle]}>
|
||||
{variant === 'feed' ? (
|
||||
likeCount > 1 ? (
|
||||
<Trans comment="Social proof below a feed post; a person the viewer follows and the number of other people who liked the post">
|
||||
{names[0].displayName} and{' '}
|
||||
<Plural value={likeCount - 1} one="1 other" other="# others" />{' '}
|
||||
liked this
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans comment="Social proof below a feed post; a person the viewer follows who liked the post">
|
||||
{names[0].displayName} liked this
|
||||
</Trans>
|
||||
)
|
||||
) : names.length >= 2 ? (
|
||||
{names.length >= 2 ? (
|
||||
<Trans comment="Social proof below a post; the bolded names are people the viewer follows who liked the post">
|
||||
Liked by {nameLink(names[0])} and {nameLink(names[1])}
|
||||
</Trans>
|
||||
|
||||
@@ -114,7 +114,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_xs,
|
||||
{gap: 3},
|
||||
(hovered || focused || pressed) && native({opacity: 0.5}),
|
||||
style,
|
||||
]}>
|
||||
@@ -122,16 +122,16 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
|
||||
color={
|
||||
isThreadAuthor ? t.palette.primary_500 : t.palette.contrast_400
|
||||
}
|
||||
width={16}
|
||||
width={12}
|
||||
settings={settings}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.text_xs,
|
||||
a.leading_tight,
|
||||
isThreadAuthor
|
||||
? {color: t.palette.primary_500}
|
||||
: t.atoms.text_contrast_medium,
|
||||
: t.atoms.text_contrast_high,
|
||||
(hovered || focused || pressed) && web(a.underline),
|
||||
]}>
|
||||
{description}
|
||||
|
||||
@@ -32,9 +32,9 @@ export function LikesStat({post}: {post: app.bsky.feed.defs.PostView}) {
|
||||
onPress={() => ax.metric('post:likedBy:click', {})}>
|
||||
<Text
|
||||
testID="likeCount-expanded"
|
||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
style={[a.text_sm, t.atoms.text_contrast_high]}>
|
||||
<Trans comment="Like count display, the <0> tags enclose the number of likes in bold (will never be 0)">
|
||||
<Text style={[a.text_md, a.font_semi_bold, t.atoms.text]}>
|
||||
<Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
|
||||
{formatPostStatCount(likeCount)}
|
||||
</Text>{' '}
|
||||
<Plural value={likeCount} one="like" other="likes" />
|
||||
|
||||
@@ -452,7 +452,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
columnGap: a.gap_lg.gap,
|
||||
},
|
||||
a.border_t,
|
||||
a.border_b,
|
||||
a.mt_md,
|
||||
a.py_md,
|
||||
t.atoms.border_contrast_low,
|
||||
@@ -461,10 +460,10 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
<Link to={repostsHref} label={l`Reposts of this post`}>
|
||||
<Text
|
||||
testID="repostCount-expanded"
|
||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
style={[a.text_sm, t.atoms.text_contrast_high]}>
|
||||
<Trans comment="Repost count display, the <0> tags enclose the number of reposts in bold (will never be 0)">
|
||||
<Text
|
||||
style={[a.text_md, a.font_semi_bold, t.atoms.text]}>
|
||||
style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
|
||||
{formatPostStatCount(post.repostCount)}
|
||||
</Text>{' '}
|
||||
<Plural
|
||||
@@ -482,10 +481,10 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
<Link to={quotesHref} label={l`Quotes of this post`}>
|
||||
<Text
|
||||
testID="quoteCount-expanded"
|
||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
style={[a.text_sm, t.atoms.text_contrast_high]}>
|
||||
<Trans comment="Quote count display, the <0> tags enclose the number of quotes in bold (will never be 0)">
|
||||
<Text
|
||||
style={[a.text_md, a.font_semi_bold, t.atoms.text]}>
|
||||
style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
|
||||
{formatPostStatCount(post.quoteCount)}
|
||||
</Text>{' '}
|
||||
<Plural
|
||||
@@ -501,9 +500,9 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
{post.bookmarkCount != null && post.bookmarkCount !== 0 ? (
|
||||
<Text
|
||||
testID="bookmarkCount-expanded"
|
||||
style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
style={[a.text_sm, t.atoms.text_contrast_high]}>
|
||||
<Trans comment="Save count display, the <0> tags enclose the number of saves in bold (will never be 0)">
|
||||
<Text style={[a.text_md, a.font_semi_bold, t.atoms.text]}>
|
||||
<Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
|
||||
{formatPostStatCount(post.bookmarkCount)}
|
||||
</Text>{' '}
|
||||
<Plural
|
||||
@@ -514,12 +513,12 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
</Trans>
|
||||
</Text>
|
||||
) : null}
|
||||
<KnownLikers
|
||||
post={post}
|
||||
feature={Features.PostThreadKnownLikersEnable}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
<KnownLikers
|
||||
post={post}
|
||||
feature={Features.PostThreadKnownLikersEnable}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.pt_sm,
|
||||
@@ -566,7 +565,7 @@ function ExpandedPostDetails({
|
||||
<View style={[a.gap_md, a.pt_md, a.align_start]}>
|
||||
<BackdatedPostIndicator post={post} />
|
||||
<View style={[a.flex_row, a.align_center, a.flex_wrap, a.gap_sm]}>
|
||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||
<Text style={[a.text_xs, t.atoms.text_contrast_high]}>
|
||||
{niceDate(i18n, post.indexedAt, 'dot separated')}
|
||||
</Text>
|
||||
{isRootPost && (
|
||||
|
||||
@@ -33,6 +33,7 @@ import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies
|
||||
import {useBreakpoints} from '#/alf'
|
||||
import {IS_WEB} from '#/env'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export * from '#/state/queries/usePostThread/context'
|
||||
export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache'
|
||||
@@ -73,6 +74,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
||||
enabled: isThreadPreferencesLoaded && !!anchor && !!moderationOpts,
|
||||
queryKey: postThreadQueryKey,
|
||||
async queryFn(ctx) {
|
||||
const placeholder = getThreadPlaceholder(qc, anchor!)
|
||||
const data = await client.call(app.bsky.unspecced.getPostThreadV2, {
|
||||
anchor: anchor! as AtUriString,
|
||||
branchingFactor: view === 'linear' ? LINEAR_VIEW_BF : TREE_VIEW_BF,
|
||||
@@ -80,6 +82,28 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
||||
sort: sort,
|
||||
})
|
||||
|
||||
const cachedKnownLikers =
|
||||
placeholder &&
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemPost, placeholder.value)
|
||||
? placeholder.value.post.viewer?.knownLikers
|
||||
: undefined
|
||||
if (cachedKnownLikers?.actors.length) {
|
||||
const anchorItem = data.thread?.find(item => item.uri === anchor)
|
||||
if (
|
||||
anchorItem &&
|
||||
bsky.isType(
|
||||
app.bsky.unspecced.defs.threadItemPost,
|
||||
anchorItem.value,
|
||||
) &&
|
||||
!anchorItem.value.post.viewer?.knownLikers?.actors.length
|
||||
) {
|
||||
anchorItem.value.post.viewer = {
|
||||
...anchorItem.value.post.viewer,
|
||||
knownLikers: cachedKnownLikers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize `ctx.meta` to track if we know we have additional replies
|
||||
* we could fetch once we hit the end.
|
||||
|
||||
@@ -445,11 +445,6 @@ let FeedItemInner = ({
|
||||
additionalPostAlerts={additionalPostAlerts}
|
||||
feedDescriptor={feedDescriptor}
|
||||
/>
|
||||
<KnownLikers
|
||||
post={rootPost}
|
||||
feature={Features.PostFeedKnownLikersEnable}
|
||||
variant="feed"
|
||||
/>
|
||||
<PostControls
|
||||
post={post}
|
||||
record={record}
|
||||
@@ -462,6 +457,11 @@ let FeedItemInner = ({
|
||||
onShowLess={onShowLess}
|
||||
viaRepost={viaRepost}
|
||||
/>
|
||||
<KnownLikers
|
||||
post={post}
|
||||
feature={Features.PostFeedKnownLikersEnable}
|
||||
variant="feed"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<DiscoverDebug feedContext={feedContext} />
|
||||
|
||||
Reference in New Issue
Block a user