Fix RTL post alignment on native
This commit is contained in:
@@ -326,6 +326,11 @@ export function QuoteEmbed({
|
|||||||
{richText ? (
|
{richText ? (
|
||||||
<RichText
|
<RichText
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={
|
||||||
|
bsky.isType(app.bsky.feed.post, quote.record)
|
||||||
|
? quote.record.langs?.[0]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
style={a.text_md}
|
style={a.text_md}
|
||||||
numberOfLines={20}
|
numberOfLines={20}
|
||||||
disableLinks
|
disableLinks
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import {useMemo} from 'react'
|
|||||||
import {type StyleProp, type TextStyle} from 'react-native'
|
import {type StyleProp, type TextStyle} from 'react-native'
|
||||||
import {RichText as RichTextAPI} from '@bsky/sdk/richtext'
|
import {RichText as RichTextAPI} from '@bsky/sdk/richtext'
|
||||||
|
|
||||||
|
import {isRTL} from '#/lib/strings/bidi'
|
||||||
import {toShortUrl} from '#/lib/strings/url-helpers'
|
import {toShortUrl} from '#/lib/strings/url-helpers'
|
||||||
import {android, atoms as a, flatten, type TextStyleProp} from '#/alf'
|
import {android, atoms as a, flatten, native, 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'
|
||||||
@@ -20,6 +21,7 @@ const URL_REGEX =
|
|||||||
export type RichTextProps = TextStyleProp &
|
export type RichTextProps = TextStyleProp &
|
||||||
Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
|
Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
|
||||||
value: RichTextAPI | string
|
value: RichTextAPI | string
|
||||||
|
language?: string
|
||||||
testID?: string
|
testID?: string
|
||||||
numberOfLines?: number
|
numberOfLines?: number
|
||||||
disableLinks?: boolean
|
disableLinks?: boolean
|
||||||
@@ -56,6 +58,7 @@ export type RichTextProps = TextStyleProp &
|
|||||||
export function RichText({
|
export function RichText({
|
||||||
testID,
|
testID,
|
||||||
value,
|
value,
|
||||||
|
language,
|
||||||
style,
|
style,
|
||||||
numberOfLines,
|
numberOfLines,
|
||||||
disableLinks,
|
disableLinks,
|
||||||
@@ -82,7 +85,10 @@ export function RichText({
|
|||||||
}
|
}
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
const plainStyles = style
|
const plainStyles = [
|
||||||
|
style,
|
||||||
|
isRTL(language) ? native({textAlign: 'right'}) : null,
|
||||||
|
]
|
||||||
const suffixStyles =
|
const suffixStyles =
|
||||||
suffix && suffixOffset
|
suffix && suffixOffset
|
||||||
? android({paddingBottom: suffixOffset, marginBottom: -suffixOffset})
|
? android({paddingBottom: suffixOffset, marginBottom: -suffixOffset})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jest.mock('#/env', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
import {forceLTR} from '../bidi'
|
import {forceLTR, isRTL} from '../bidi'
|
||||||
|
|
||||||
const LEFT_TO_RIGHT_EMBEDDING = '\u202A'
|
const LEFT_TO_RIGHT_EMBEDDING = '\u202A'
|
||||||
const POP_DIRECTIONAL_FORMATTING = '\u202C'
|
const POP_DIRECTIONAL_FORMATTING = '\u202C'
|
||||||
@@ -31,3 +31,21 @@ describe('forceLTR', () => {
|
|||||||
expect(forceLTR('@alice.bsky.social')).toBe('@alice.bsky.social')
|
expect(forceLTR('@alice.bsky.social')).toBe('@alice.bsky.social')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('isRTL', () => {
|
||||||
|
it('recognizes right-to-left languages and scripts', () => {
|
||||||
|
expect(isRTL('he')).toBe(true)
|
||||||
|
expect(isRTL('ar')).toBe(true)
|
||||||
|
expect(isRTL('az-Arab')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('recognizes left-to-right languages and scripts', () => {
|
||||||
|
expect(isRTL('en')).toBe(false)
|
||||||
|
expect(isRTL('az-Latn')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles missing and invalid language tags', () => {
|
||||||
|
expect(isRTL(undefined)).toBe(false)
|
||||||
|
expect(isRTL('not_a_language')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -17,3 +17,17 @@ export function forceLTR(str: string) {
|
|||||||
if (IS_WEB) return str
|
if (IS_WEB) return str
|
||||||
return LEFT_TO_RIGHT_EMBEDDING + str + POP_DIRECTIONAL_FORMATTING
|
return LEFT_TO_RIGHT_EMBEDDING + str + POP_DIRECTIONAL_FORMATTING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a BCP 47 language tag uses a right-to-left script.
|
||||||
|
*/
|
||||||
|
export function isRTL(language: string | undefined) {
|
||||||
|
if (!language) return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Intl.Locale(language).getTextInfo().direction === 'rtl'
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof RangeError) return false
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ function MessageInputPostEmbed({
|
|||||||
enableTags
|
enableTags
|
||||||
testID="postText"
|
testID="postText"
|
||||||
value={rt}
|
value={rt}
|
||||||
|
language={record.langs?.[0]}
|
||||||
style={[a.text_sm, t.atoms.text_contrast_high]}
|
style={[a.text_sm, t.atoms.text_contrast_high]}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
numberOfLines={3}
|
numberOfLines={3}
|
||||||
|
|||||||
@@ -405,6 +405,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
enableTags
|
enableTags
|
||||||
selectable
|
selectable
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={record.langs?.[0]}
|
||||||
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}
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
|||||||
<RichText
|
<RichText
|
||||||
enableTags
|
enableTags
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={record.langs?.[0]}
|
||||||
style={[a.flex_1, a.text_md]}
|
style={[a.flex_1, a.text_md]}
|
||||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
|
|||||||
@@ -355,6 +355,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
|||||||
<RichText
|
<RichText
|
||||||
enableTags
|
enableTags
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={record.langs?.[0]}
|
||||||
style={[a.flex_1, a.text_md]}
|
style={[a.flex_1, a.text_md]}
|
||||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
|
|||||||
@@ -963,6 +963,7 @@ function Overlay({
|
|||||||
{record?.text?.trim() && (
|
{record?.text?.trim() && (
|
||||||
<ExpandableRichTextView
|
<ExpandableRichTextView
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={record.langs?.[0]}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -1020,9 +1021,11 @@ function Overlay({
|
|||||||
|
|
||||||
function ExpandableRichTextView({
|
function ExpandableRichTextView({
|
||||||
value,
|
value,
|
||||||
|
language,
|
||||||
authorHandle,
|
authorHandle,
|
||||||
}: {
|
}: {
|
||||||
value: RichTextAPI
|
value: RichTextAPI
|
||||||
|
language?: string
|
||||||
authorHandle?: string
|
authorHandle?: string
|
||||||
}) {
|
}) {
|
||||||
const {height: screenHeight} = useSafeAreaFrame()
|
const {height: screenHeight} = useSafeAreaFrame()
|
||||||
@@ -1057,6 +1060,7 @@ function ExpandableRichTextView({
|
|||||||
]}>
|
]}>
|
||||||
<RichText
|
<RichText
|
||||||
value={value}
|
value={value}
|
||||||
|
language={language}
|
||||||
style={[a.text_sm, a.flex_1, a.leading_relaxed]}
|
style={[a.text_sm, a.flex_1, a.leading_relaxed]}
|
||||||
authorHandle={authorHandle}
|
authorHandle={authorHandle}
|
||||||
enableTags
|
enableTags
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ function PostInner({
|
|||||||
enableTags
|
enableTags
|
||||||
testID="postText"
|
testID="postText"
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={record?.langs?.[0]}
|
||||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||||
style={[a.flex_1, a.text_md]}
|
style={[a.flex_1, a.text_md]}
|
||||||
authorHandle={post.author.handle}
|
authorHandle={post.author.handle}
|
||||||
|
|||||||
@@ -520,6 +520,7 @@ let PostContent = ({
|
|||||||
enableTags
|
enableTags
|
||||||
testID="postText"
|
testID="postText"
|
||||||
value={richText}
|
value={richText}
|
||||||
|
language={record?.langs?.[0]}
|
||||||
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
|
||||||
style={[a.flex_1, a.text_md]}
|
style={[a.flex_1, a.text_md]}
|
||||||
authorHandle={postAuthor.handle}
|
authorHandle={postAuthor.handle}
|
||||||
|
|||||||
Reference in New Issue
Block a user