Add canonical OP thread numbering (#11184)

Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
This commit is contained in:
Eric Bailey
2026-08-05 12:20:43 -05:00
committed by GitHub
parent 8cb6c7babe
commit b79908ecea
8 changed files with 164 additions and 17 deletions
@@ -30,6 +30,10 @@ import {type PostSource} from '#/state/unstable-post-source'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {KnownLikers, LikesStat} from '#/screens/PostThread/components/LikesStat'
import {ThreadItemAnchorFollowButton} from '#/screens/PostThread/components/ThreadItemAnchorFollowButton'
import {
hasThreadItemPostNumber,
ThreadItemPostNumber,
} from '#/screens/PostThread/components/ThreadItemPostNumber'
import {
LINEAR_AVI_WIDTH,
OUTER_SPACE,
@@ -184,6 +188,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
const post = postShadow
const record = item.value.post.record
const postNumbering = item.value
const moderation = item.moderation
const authorShadow = useProfileShadow(post.author)
const {isActive: live} = useActorStatus(post.author)
@@ -400,8 +405,15 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
style={[a.flex_1, a.text_lg]}
authorHandle={post.author.handle}
shouldProxyLinks={true}
suffix={
hasThreadItemPostNumber(postNumbering) ? (
<ThreadItemPostNumber value={postNumbering} />
) : undefined
}
/>
) : undefined}
) : (
<ThreadItemPostNumber inline={false} value={postNumbering} />
)}
<TranslatedPost post={post} postTextStyle={[a.text_lg]} />
{post.embed && (
<View style={[richText?.text ? a.py_xs : []]}>
@@ -23,6 +23,10 @@ import {type OnPostSuccessData} from '#/state/shell/composer'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {PostMeta} from '#/view/com/util/PostMeta'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {
hasThreadItemPostNumber,
ThreadItemPostNumber,
} from '#/screens/PostThread/components/ThreadItemPostNumber'
import {
LINEAR_AVI_WIDTH,
OUTER_SPACE,
@@ -196,6 +200,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
const post = item.value.post
const record = item.value.post.record
const postNumbering = item.value
const moderation = item.moderation
const richText = useMemo(
() =>
@@ -324,15 +329,28 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
authorHandle={post.author.handle}
shouldProxyLinks={true}
suffix={
!limitLines && hasThreadItemPostNumber(postNumbering) ? (
<ThreadItemPostNumber value={postNumbering} />
) : undefined
}
/>
{limitLines && (
<ShowMoreTextButton
style={[a.text_md]}
onPress={onPressShowMore}
/>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<ShowMoreTextButton
style={[a.text_md]}
onPress={onPressShowMore}
/>
<ThreadItemPostNumber
inline={false}
value={postNumbering}
/>
</View>
)}
</View>
) : undefined}
) : (
<ThreadItemPostNumber inline={false} value={postNumbering} />
)}
<TranslatedPost hideTranslateLink post={post} />
{post.embed && (
<View
@@ -0,0 +1,86 @@
import {Text, View} from 'react-native'
import {type AppBskyUnspeccedDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {atoms as a, native, platform, useTheme} from '#/alf'
import {useAnalytics} from '#/analytics'
export function hasThreadItemPostNumber(
value: AppBskyUnspeccedDefs.ThreadItemPost,
) {
const index = value.opThreadPostIndex
const count = value.opThreadPostCount
return (
index !== undefined &&
count !== undefined &&
index >= 1 &&
count >= 1 &&
index <= count
)
}
export function ThreadItemPostNumber({
value,
inline = true,
}: {
value: AppBskyUnspeccedDefs.ThreadItemPost
inline?: boolean
}) {
const ax = useAnalytics()
const t = useTheme()
const {t: l} = useLingui()
const index = value.opThreadPostIndex
const count = value.opThreadPostCount
const isEnabled = ax.features.enabled(
ax.features.CanonicalPostNumberingEnable,
)
if (!isEnabled || !hasThreadItemPostNumber(value)) {
return null
}
return (
<View
style={[
a.flex_shrink_0,
a.rounded_full,
t.atoms.bg_contrast_50,
native(a.py_2xs),
{
paddingLeft: 5,
paddingRight: 5,
},
inline
? platform({
native: {transform: [{translateY: 6}]},
web: {top: -2},
})
: {top: -2},
]}>
<Text
accessibilityLabel={l({
message: `Post ${index} of ${count}`,
context: 'post-number-in-thread',
comment:
"Screen reader label indicating post count in a thread, e.g., the 3rd post of 5 total is 'Post 3 of 5'",
})}
accessibilityHint=""
style={[
a.text_xs,
a.font_medium,
t.atoms.text_contrast_high,
{
fontVariant: ['tabular-nums'],
},
]}>
<Trans
context="post-number-in-thread"
comment="Badge indicating post count in a thread, e.g., the 3rd post of 5 total is '3/5'">
{index}/{count}
</Trans>
</Text>
</View>
)
}
@@ -22,6 +22,10 @@ import {useSession} from '#/state/session'
import {type OnPostSuccessData} from '#/state/shell/composer'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {PostMeta} from '#/view/com/util/PostMeta'
import {
hasThreadItemPostNumber,
ThreadItemPostNumber,
} from '#/screens/PostThread/components/ThreadItemPostNumber'
import {
OUTER_SPACE,
REPLY_LINE_WIDTH,
@@ -258,6 +262,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
const post = item.value.post
const record = item.value.post.record
const postNumbering = item.value
const moderation = item.moderation
const richText = useMemo(
() =>
@@ -354,15 +359,32 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
authorHandle={post.author.handle}
shouldProxyLinks={true}
suffix={
!limitLines &&
hasThreadItemPostNumber(postNumbering) ? (
<ThreadItemPostNumber value={postNumbering} />
) : undefined
}
/>
{limitLines && (
<ShowMoreTextButton
style={[a.text_md]}
onPress={onPressShowMore}
/>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<ShowMoreTextButton
style={[a.text_md]}
onPress={onPressShowMore}
/>
<ThreadItemPostNumber
inline={false}
value={postNumbering}
/>
</View>
)}
</View>
) : null}
) : (
<ThreadItemPostNumber
inline={false}
value={postNumbering}
/>
)}
<TranslatedPost hideTranslateLink post={post} />
{post.embed && (
<View style={[a.pb_xs]}>