Add canonical OP thread numbering (#11184)
Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -108,7 +108,7 @@
|
||||
"@bsky.app/expo-scroll-edge-effect": "^0.1.9",
|
||||
"@bsky.app/expo-translate-text": "^0.2.9",
|
||||
"@bsky.app/peek-menu": "^0.3.2",
|
||||
"@bsky.app/react-native-uitextview": "^2.5.0",
|
||||
"@bsky.app/react-native-uitextview": "^2.6.0",
|
||||
"@bsky.app/sift": "^0.3.9",
|
||||
"@bsky.app/tapper": "^0.6.1",
|
||||
"@bsky.app/video": "0.3.6",
|
||||
|
||||
Generated
+5
-5
@@ -278,8 +278,8 @@ importers:
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2(expo@54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
'@bsky.app/react-native-uitextview':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
'@bsky.app/sift':
|
||||
specifier: ^0.3.9
|
||||
version: 0.3.9(expo@54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
@@ -1676,8 +1676,8 @@ packages:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
'@bsky.app/react-native-uitextview@2.5.0':
|
||||
resolution: {integrity: sha512-8DyQAJEkr0ZPEb9QMmT3088V70B6xRsY8e/jvi2oq13hCsLEXCw5M1S48xKRnr8263bJxSH0BjK0nfhPS8Myqw==}
|
||||
'@bsky.app/react-native-uitextview@2.6.0':
|
||||
resolution: {integrity: sha512-d6efoPKixj7ycaQWYSDr4JDB1jzIewWie6UtP4Pylgh+K//aXCNaudbLjHN4WP6RVGBfFqNac13hSh3tRikH7A==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '>=0.81.5'
|
||||
@@ -10420,7 +10420,7 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
'@bsky.app/react-native-uitextview@2.5.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||
'@bsky.app/react-native-uitextview@2.6.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
@@ -24,6 +24,7 @@ export enum Features {
|
||||
SearchStarterPacksV2Enable = 'search_starter_packs_v2:enable',
|
||||
FollowSortEnable = 'follow_sort:enable',
|
||||
OnboardingInterestsRequiredEnable = 'onboarding:interests:required:enable',
|
||||
CanonicalPostNumberingEnable = 'canonical_post_numbering:enable',
|
||||
|
||||
// values
|
||||
TrendingDiscoverValues = 'trending_discover:values',
|
||||
|
||||
@@ -27,6 +27,7 @@ export type RichTextProps = TextStyleProp &
|
||||
interactiveStyle?: StyleProp<TextStyle>
|
||||
emojiMultiplier?: number
|
||||
shouldProxyLinks?: boolean
|
||||
suffix?: React.ReactNode
|
||||
/**
|
||||
* DANGEROUS: Disable facet lexicon validation
|
||||
*
|
||||
@@ -54,6 +55,7 @@ export function RichText({
|
||||
onLayout,
|
||||
onTextLayout,
|
||||
shouldProxyLinks,
|
||||
suffix,
|
||||
disableMentionFacetValidation,
|
||||
}: RichTextProps) {
|
||||
const richText = useMemo(() => {
|
||||
@@ -87,6 +89,8 @@ export function RichText({
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={WORD_WRAP}>
|
||||
{text}
|
||||
{suffix ? ' ' : null}
|
||||
{suffix}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -102,6 +106,8 @@ export function RichText({
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={WORD_WRAP}>
|
||||
{text}
|
||||
{suffix ? ' ' : null}
|
||||
{suffix}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -188,6 +194,8 @@ export function RichText({
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={WORD_WRAP}>
|
||||
{els}
|
||||
{suffix ? ' ' : null}
|
||||
{suffix}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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]}>
|
||||
|
||||
Reference in New Issue
Block a user