Fix post numbers being clipped in some cases (#11413)
This commit is contained in:
@@ -3,7 +3,7 @@ import {type StyleProp, 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, type TextStyleProp} from '#/alf'
|
import {atoms as a, flatten, ios, type TextStyleProp} from '#/alf'
|
||||||
import {isOnlyEmoji} from '#/alf/typography'
|
import {isOnlyEmoji} from '#/alf/typography'
|
||||||
import {InlineLinkText, type LinkProps} from '#/components/Link'
|
import {InlineLinkText, type LinkProps} from '#/components/Link'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
@@ -28,6 +28,18 @@ export type RichTextProps = TextStyleProp &
|
|||||||
emojiMultiplier?: number
|
emojiMultiplier?: number
|
||||||
shouldProxyLinks?: boolean
|
shouldProxyLinks?: boolean
|
||||||
suffix?: React.ReactNode
|
suffix?: React.ReactNode
|
||||||
|
/**
|
||||||
|
* How far below the text baseline `suffix` extends, in px.
|
||||||
|
*
|
||||||
|
* Inline views inside `Text` sit with their bottom edge on the baseline, so
|
||||||
|
* a suffix nudged below it overflows the `Text`'s measured bounds and iOS
|
||||||
|
* clips it. We reserve this much room as bottom padding and cancel it with
|
||||||
|
* an equal negative margin, so the suffix can paint without moving anything
|
||||||
|
* after it. Pass the same offset the suffix nudges itself by.
|
||||||
|
*
|
||||||
|
* Overrides any `paddingBottom`/`marginBottom` set via `style`.
|
||||||
|
*/
|
||||||
|
suffixOffset?: number
|
||||||
/**
|
/**
|
||||||
* DANGEROUS: Disable facet lexicon validation
|
* DANGEROUS: Disable facet lexicon validation
|
||||||
*
|
*
|
||||||
@@ -56,6 +68,7 @@ export function RichText({
|
|||||||
onTextLayout,
|
onTextLayout,
|
||||||
shouldProxyLinks,
|
shouldProxyLinks,
|
||||||
suffix,
|
suffix,
|
||||||
|
suffixOffset = 0,
|
||||||
disableMentionFacetValidation,
|
disableMentionFacetValidation,
|
||||||
}: RichTextProps) {
|
}: RichTextProps) {
|
||||||
const richText = useMemo(() => {
|
const richText = useMemo(() => {
|
||||||
@@ -69,6 +82,10 @@ export function RichText({
|
|||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
const plainStyles = style
|
const plainStyles = style
|
||||||
|
const suffixStyles =
|
||||||
|
suffix && suffixOffset
|
||||||
|
? ios({paddingBottom: suffixOffset, marginBottom: -suffixOffset})
|
||||||
|
: null
|
||||||
const interactiveStyles = [plainStyles, interactiveStyle]
|
const interactiveStyles = [plainStyles, interactiveStyle]
|
||||||
|
|
||||||
const {text, facets} = richText
|
const {text, facets} = richText
|
||||||
@@ -83,7 +100,7 @@ export function RichText({
|
|||||||
emoji
|
emoji
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
style={[plainStyles, {fontSize}]}
|
style={[plainStyles, {fontSize}, suffixStyles]}
|
||||||
onLayout={onLayout}
|
onLayout={onLayout}
|
||||||
onTextLayout={onTextLayout}
|
onTextLayout={onTextLayout}
|
||||||
// @ts-ignore web only -prf
|
// @ts-ignore web only -prf
|
||||||
@@ -99,7 +116,7 @@ export function RichText({
|
|||||||
emoji
|
emoji
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
style={plainStyles}
|
style={[plainStyles, suffixStyles]}
|
||||||
numberOfLines={numberOfLines}
|
numberOfLines={numberOfLines}
|
||||||
onLayout={onLayout}
|
onLayout={onLayout}
|
||||||
onTextLayout={onTextLayout}
|
onTextLayout={onTextLayout}
|
||||||
@@ -187,7 +204,7 @@ export function RichText({
|
|||||||
emoji
|
emoji
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
style={plainStyles}
|
style={[plainStyles, suffixStyles]}
|
||||||
numberOfLines={numberOfLines}
|
numberOfLines={numberOfLines}
|
||||||
onLayout={onLayout}
|
onLayout={onLayout}
|
||||||
onTextLayout={onTextLayout}
|
onTextLayout={onTextLayout}
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
|||||||
import {KnownLikers, LikesStat} from '#/screens/PostThread/components/LikesStat'
|
import {KnownLikers, LikesStat} from '#/screens/PostThread/components/LikesStat'
|
||||||
import {ThreadItemAnchorFollowButton} from '#/screens/PostThread/components/ThreadItemAnchorFollowButton'
|
import {ThreadItemAnchorFollowButton} from '#/screens/PostThread/components/ThreadItemAnchorFollowButton'
|
||||||
import {
|
import {
|
||||||
hasThreadItemPostNumber,
|
POST_NUMBER_INLINE_OFFSET,
|
||||||
ThreadItemPostNumber,
|
ThreadItemPostNumber,
|
||||||
|
useHasThreadItemPostNumber,
|
||||||
} from '#/screens/PostThread/components/ThreadItemPostNumber'
|
} from '#/screens/PostThread/components/ThreadItemPostNumber'
|
||||||
import {
|
import {
|
||||||
LINEAR_AVI_WIDTH,
|
LINEAR_AVI_WIDTH,
|
||||||
@@ -190,6 +191,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
const post = postShadow
|
const post = postShadow
|
||||||
const record = item.value.post.record
|
const record = item.value.post.record
|
||||||
const postNumbering = item.value
|
const postNumbering = item.value
|
||||||
|
const showPostNumber = useHasThreadItemPostNumber(postNumbering)
|
||||||
const moderation = item.moderation
|
const moderation = item.moderation
|
||||||
const authorShadow = useProfileShadow(post.author)
|
const authorShadow = useProfileShadow(post.author)
|
||||||
const {isActive: live} = useActorStatus(post.author)
|
const {isActive: live} = useActorStatus(post.author)
|
||||||
@@ -406,8 +408,9 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
style={[a.flex_1, a.text_lg]}
|
style={[a.flex_1, a.text_lg]}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
shouldProxyLinks={true}
|
shouldProxyLinks={true}
|
||||||
|
suffixOffset={POST_NUMBER_INLINE_OFFSET}
|
||||||
suffix={
|
suffix={
|
||||||
hasThreadItemPostNumber(postNumbering) ? (
|
showPostNumber ? (
|
||||||
<ThreadItemPostNumber value={postNumbering} />
|
<ThreadItemPostNumber value={postNumbering} />
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replie
|
|||||||
import {PostMeta} from '#/view/com/util/PostMeta'
|
import {PostMeta} from '#/view/com/util/PostMeta'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {
|
import {
|
||||||
hasThreadItemPostNumber,
|
POST_NUMBER_INLINE_OFFSET,
|
||||||
ThreadItemPostNumber,
|
ThreadItemPostNumber,
|
||||||
|
useHasThreadItemPostNumber,
|
||||||
} from '#/screens/PostThread/components/ThreadItemPostNumber'
|
} from '#/screens/PostThread/components/ThreadItemPostNumber'
|
||||||
import {
|
import {
|
||||||
LINEAR_AVI_WIDTH,
|
LINEAR_AVI_WIDTH,
|
||||||
@@ -204,6 +205,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
|||||||
const post = item.value.post
|
const post = item.value.post
|
||||||
const record = item.value.post.record
|
const record = item.value.post.record
|
||||||
const postNumbering = item.value
|
const postNumbering = item.value
|
||||||
|
const showPostNumber = useHasThreadItemPostNumber(postNumbering)
|
||||||
const moderation = item.moderation
|
const moderation = item.moderation
|
||||||
const richText = useMemo(
|
const richText = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -332,8 +334,9 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
|||||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
shouldProxyLinks={true}
|
shouldProxyLinks={true}
|
||||||
|
suffixOffset={POST_NUMBER_INLINE_OFFSET}
|
||||||
suffix={
|
suffix={
|
||||||
!limitLines && hasThreadItemPostNumber(postNumbering) ? (
|
!limitLines && showPostNumber ? (
|
||||||
<ThreadItemPostNumber value={postNumbering} />
|
<ThreadItemPostNumber value={postNumbering} />
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,22 @@ import {Trans, useLingui} from '@lingui/react/macro'
|
|||||||
import {atoms as a, native, platform, useTheme} from '#/alf'
|
import {atoms as a, native, platform, useTheme} from '#/alf'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
|
|
||||||
export function hasThreadItemPostNumber(
|
/**
|
||||||
|
* How far the inline badge is nudged below the text baseline to optically
|
||||||
|
* center it. The containing `RichText` must reserve matching room via
|
||||||
|
* `suffixOffset`, or iOS clips the overflow.
|
||||||
|
*/
|
||||||
|
export const POST_NUMBER_INLINE_OFFSET = 6
|
||||||
|
|
||||||
|
export function useHasThreadItemPostNumber(
|
||||||
value: AppBskyUnspeccedDefs.ThreadItemPost,
|
value: AppBskyUnspeccedDefs.ThreadItemPost,
|
||||||
) {
|
) {
|
||||||
|
const ax = useAnalytics()
|
||||||
const index = value.opThreadPostIndex
|
const index = value.opThreadPostIndex
|
||||||
const count = value.opThreadPostCount
|
const count = value.opThreadPostCount
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
ax.features.enabled(ax.features.CanonicalPostNumberingEnable) &&
|
||||||
index !== undefined &&
|
index !== undefined &&
|
||||||
count !== undefined &&
|
count !== undefined &&
|
||||||
index >= 1 &&
|
index >= 1 &&
|
||||||
@@ -27,17 +36,13 @@ export function ThreadItemPostNumber({
|
|||||||
value: AppBskyUnspeccedDefs.ThreadItemPost
|
value: AppBskyUnspeccedDefs.ThreadItemPost
|
||||||
inline?: boolean
|
inline?: boolean
|
||||||
}) {
|
}) {
|
||||||
const ax = useAnalytics()
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
const shouldRender = useHasThreadItemPostNumber(value)
|
||||||
const index = value.opThreadPostIndex
|
const index = value.opThreadPostIndex
|
||||||
const count = value.opThreadPostCount
|
const count = value.opThreadPostCount
|
||||||
|
|
||||||
const isEnabled = ax.features.enabled(
|
if (!shouldRender) {
|
||||||
ax.features.CanonicalPostNumberingEnable,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isEnabled || !hasThreadItemPostNumber(value)) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +59,7 @@ export function ThreadItemPostNumber({
|
|||||||
},
|
},
|
||||||
inline
|
inline
|
||||||
? platform({
|
? platform({
|
||||||
native: {transform: [{translateY: 6}]},
|
native: {transform: [{translateY: POST_NUMBER_INLINE_OFFSET}]},
|
||||||
web: {top: -2},
|
web: {top: -2},
|
||||||
})
|
})
|
||||||
: {top: -2},
|
: {top: -2},
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ import {type OnPostSuccessData} from '#/state/shell/composer'
|
|||||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||||
import {PostMeta} from '#/view/com/util/PostMeta'
|
import {PostMeta} from '#/view/com/util/PostMeta'
|
||||||
import {
|
import {
|
||||||
hasThreadItemPostNumber,
|
POST_NUMBER_INLINE_OFFSET,
|
||||||
ThreadItemPostNumber,
|
ThreadItemPostNumber,
|
||||||
|
useHasThreadItemPostNumber,
|
||||||
} from '#/screens/PostThread/components/ThreadItemPostNumber'
|
} from '#/screens/PostThread/components/ThreadItemPostNumber'
|
||||||
import {
|
import {
|
||||||
OUTER_SPACE,
|
OUTER_SPACE,
|
||||||
@@ -264,6 +265,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
|||||||
const post = item.value.post
|
const post = item.value.post
|
||||||
const record = item.value.post.record
|
const record = item.value.post.record
|
||||||
const postNumbering = item.value
|
const postNumbering = item.value
|
||||||
|
const showPostNumber = useHasThreadItemPostNumber(postNumbering)
|
||||||
const moderation = item.moderation
|
const moderation = item.moderation
|
||||||
const richText = useMemo(
|
const richText = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -360,9 +362,9 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
|||||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
shouldProxyLinks={true}
|
shouldProxyLinks={true}
|
||||||
|
suffixOffset={POST_NUMBER_INLINE_OFFSET}
|
||||||
suffix={
|
suffix={
|
||||||
!limitLines &&
|
!limitLines && showPostNumber ? (
|
||||||
hasThreadItemPostNumber(postNumbering) ? (
|
|
||||||
<ThreadItemPostNumber value={postNumbering} />
|
<ThreadItemPostNumber value={postNumbering} />
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user