Bluesky app fork with some witchin' additions 💫

Use Button instead of TextLink for show more button (#8480)

* use button instead of TextLink for show more

* Match post text size, provide interaction feedback

* Move to new Post components dir

* Prettier

---------

Co-authored-by: Eric Bailey <git@esb.lol>

authored by samuel.fm Eric Bailey and committed by GitHub 00469314 ed969151

Changed files
+160 -125
src
+7 -3
src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx
··· 1 1 import React from 'react' 2 - import {ActivityIndicator, GestureResponderEvent, Pressable} from 'react-native' 2 + import { 3 + ActivityIndicator, 4 + type GestureResponderEvent, 5 + Pressable, 6 + } from 'react-native' 3 7 import {Image} from 'expo-image' 4 - import {AppBskyEmbedExternal} from '@atproto/api' 8 + import {type AppBskyEmbedExternal} from '@atproto/api' 5 9 import {msg} from '@lingui/macro' 6 10 import {useLingui} from '@lingui/react' 7 11 8 - import {EmbedPlayerParams} from '#/lib/strings/embed-player' 12 + import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 9 13 import {isIOS, isNative, isWeb} from '#/platform/detection' 10 14 import {useExternalEmbedsPrefs} from '#/state/preferences' 11 15 import {atoms as a, useTheme} from '#/alf'
+7 -4
src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx
··· 1 1 import React from 'react' 2 2 import { 3 3 ActivityIndicator, 4 - GestureResponderEvent, 4 + type GestureResponderEvent, 5 5 Pressable, 6 6 StyleSheet, 7 7 useWindowDimensions, ··· 16 16 import {useSafeAreaInsets} from 'react-native-safe-area-context' 17 17 import {WebView} from 'react-native-webview' 18 18 import {Image} from 'expo-image' 19 - import {AppBskyEmbedExternal} from '@atproto/api' 19 + import {type AppBskyEmbedExternal} from '@atproto/api' 20 20 import {msg} from '@lingui/macro' 21 21 import {useLingui} from '@lingui/react' 22 22 import {useNavigation} from '@react-navigation/native' 23 23 24 - import {NavigationProp} from '#/lib/routes/types' 25 - import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player' 24 + import {type NavigationProp} from '#/lib/routes/types' 25 + import { 26 + type EmbedPlayerParams, 27 + getPlayerAspect, 28 + } from '#/lib/strings/embed-player' 26 29 import {isNative} from '#/platform/detection' 27 30 import {useExternalEmbedsPrefs} from '#/state/preferences' 28 31 import {EventStopper} from '#/view/com/util/EventStopper'
+4 -4
src/components/Post/Embed/ExternalEmbed/Gif.tsx
··· 1 1 import React from 'react' 2 2 import { 3 3 Pressable, 4 - StyleProp, 4 + type StyleProp, 5 5 StyleSheet, 6 6 TouchableOpacity, 7 7 View, 8 - ViewStyle, 8 + type ViewStyle, 9 9 } from 'react-native' 10 10 import {msg, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 12 13 13 import {HITSLOP_20} from '#/lib/constants' 14 - import {EmbedPlayerParams} from '#/lib/strings/embed-player' 14 + import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 15 15 import {isWeb} from '#/platform/detection' 16 16 import {useAutoplayDisabled} from '#/state/preferences' 17 17 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' ··· 22 22 import {Text} from '#/components/Typography' 23 23 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 24 24 import {GifView} from '../../../../../modules/expo-bluesky-gif-view' 25 - import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types' 25 + import {type GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types' 26 26 27 27 function PlaybackControls({ 28 28 onPress,
+2 -2
src/components/Post/Embed/ListEmbed.tsx
··· 6 6 import {atoms as a, useTheme} from '#/alf' 7 7 import * as ListCard from '#/components/ListCard' 8 8 import {ContentHider} from '#/components/moderation/ContentHider' 9 - import {EmbedType} from '#/types/bsky/post' 10 - import {CommonProps} from './types' 9 + import {type EmbedType} from '#/types/bsky/post' 10 + import {type CommonProps} from './types' 11 11 12 12 export function ListEmbed({ 13 13 embed,
+1 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx
··· 1 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 2 2 import {View} from 'react-native' 3 3 import {msg, plural} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react'
+2 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx
··· 1 1 import React, {useRef} from 'react' 2 - import {Pressable, StyleProp, View, ViewStyle} from 'react-native' 3 - import {AppBskyEmbedVideo} from '@atproto/api' 2 + import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' 3 + import {type AppBskyEmbedVideo} from '@atproto/api' 4 4 import {BlueskyVideoView} from '@haileyok/bluesky-video' 5 5 import {msg} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react'
+1 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 2 import {msg, Trans} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 4 + import type React from 'react' 5 5 6 6 import {atoms as a, useTheme} from '#/alf' 7 7 import {Button, ButtonText} from '#/components/Button'
+2 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
··· 1 - import React from 'react' 2 - import {SvgProps} from 'react-native-svg' 1 + import {type SvgProps} from 'react-native-svg' 2 + import type React from 'react' 3 3 4 4 import {PressableWithHover} from '#/view/com/util/PressableWithHover' 5 5 import {atoms as a, useTheme, web} from '#/alf'
+2 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
··· 1 - import React, {useCallback, useEffect, useRef, useState} from 'react' 1 + import {useCallback, useEffect, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 5 6 6 7 import {isFirefox, isTouchDevice} from '#/lib/browser' 7 8 import {clamp} from '#/lib/numbers'
+2 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
··· 1 - import React, {useCallback} from 'react' 1 + import {useCallback} from 'react' 2 2 import {View} from 'react-native' 3 3 import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 + import type React from 'react' 6 7 7 8 import {isSafari, isTouchDevice} from '#/lib/browser' 8 9 import {atoms as a} from '#/alf'
+1 -1
src/components/Post/Embed/VideoEmbed/index.tsx
··· 1 1 import React, {useCallback, useState} from 'react' 2 2 import {ActivityIndicator, View} from 'react-native' 3 3 import {ImageBackground} from 'expo-image' 4 - import {AppBskyEmbedVideo} from '@atproto/api' 4 + import {type AppBskyEmbedVideo} from '@atproto/api' 5 5 import {msg, Trans} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7
+3 -2
src/components/Post/Embed/VideoEmbed/index.web.tsx
··· 1 - import React, {useCallback, useEffect, useRef, useState} from 'react' 1 + import {useCallback, useEffect, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyEmbedVideo} from '@atproto/api' 3 + import {type AppBskyEmbedVideo} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 + import type React from 'react' 6 7 7 8 import {isFirefox} from '#/lib/browser' 8 9 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
+56
src/components/Post/ShowMoreTextButton.tsx
··· 1 + import {useCallback, useMemo} from 'react' 2 + import {LayoutAnimation, type TextStyle} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {HITSLOP_10} from '#/lib/constants' 7 + import {atoms as a, flatten, type TextStyleProp, useTheme} from '#/alf' 8 + import {Button} from '#/components/Button' 9 + import {Text} from '#/components/Typography' 10 + 11 + export function ShowMoreTextButton({ 12 + onPress: onPressProp, 13 + style, 14 + }: TextStyleProp & {onPress: () => void}) { 15 + const t = useTheme() 16 + const {_} = useLingui() 17 + 18 + const onPress = useCallback(() => { 19 + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 20 + onPressProp() 21 + }, [onPressProp]) 22 + 23 + const textStyle = useMemo(() => { 24 + return flatten([a.leading_snug, a.text_sm, style]) as TextStyle & { 25 + fontSize: number 26 + lineHeight: number 27 + } 28 + }, [style]) 29 + 30 + return ( 31 + <Button 32 + label={_(msg`Expand post text`)} 33 + onPress={onPress} 34 + style={[ 35 + a.self_start, 36 + { 37 + paddingBottom: textStyle.fontSize / 3, 38 + }, 39 + ]} 40 + hitSlop={HITSLOP_10}> 41 + {({pressed, hovered}) => ( 42 + <Text 43 + style={[ 44 + textStyle, 45 + { 46 + color: t.palette.primary_500, 47 + opacity: pressed ? 0.6 : 1, 48 + textDecorationLine: hovered ? 'underline' : undefined, 49 + }, 50 + ]}> 51 + <Trans>Show More</Trans> 52 + </Text> 53 + )} 54 + </Button> 55 + ) 56 + }
+18 -22
src/screens/PostThread/components/ThreadItemPost.tsx
··· 6 6 AtUri, 7 7 RichText as RichTextAPI, 8 8 } from '@atproto/api' 9 - import {msg, Trans} from '@lingui/macro' 10 - import {useLingui} from '@lingui/react' 9 + import {Trans} from '@lingui/macro' 11 10 12 11 import {useActorStatus} from '#/lib/actor-status' 13 12 import {MAX_POST_LINES} from '#/lib/constants' 14 13 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 15 - import {usePalette} from '#/lib/hooks/usePalette' 16 14 import {makeProfileLink} from '#/lib/routes/links' 17 15 import {countLines} from '#/lib/strings/helpers' 18 16 import { ··· 24 22 import {useSession} from '#/state/session' 25 23 import {type OnPostSuccessData} from '#/state/shell/composer' 26 24 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 27 - import {TextLink} from '#/view/com/util/Link' 28 25 import {PostMeta} from '#/view/com/util/PostMeta' 29 26 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' 30 27 import { ··· 40 37 import {PostHider} from '#/components/moderation/PostHider' 41 38 import {type AppModerationCause} from '#/components/Pills' 42 39 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 40 + import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 43 41 import {PostControls} from '#/components/PostControls' 44 42 import {RichText} from '#/components/RichText' 45 43 import * as Skele from '#/components/Skeleton' ··· 187 185 postShadow: Shadow<AppBskyFeedDefs.PostView> 188 186 }) { 189 187 const t = useTheme() 190 - const pal = usePalette('default') 191 - const {_} = useLingui() 192 188 const {openComposer} = useOpenComposer() 193 189 const {currentAccount} = useSession() 194 190 ··· 304 300 additionalCauses={additionalPostAlerts} 305 301 /> 306 302 {richText?.text ? ( 307 - <RichText 308 - enableTags 309 - value={richText} 310 - style={[a.flex_1, a.text_md]} 311 - numberOfLines={limitLines ? MAX_POST_LINES : undefined} 312 - authorHandle={post.author.handle} 313 - shouldProxyLinks={true} 314 - /> 315 - ) : undefined} 316 - {limitLines ? ( 317 - <TextLink 318 - text={_(msg`Show More`)} 319 - style={pal.link} 320 - onPress={onPressShowMore} 321 - href="#" 322 - /> 303 + <> 304 + <RichText 305 + enableTags 306 + value={richText} 307 + style={[a.flex_1, a.text_md]} 308 + numberOfLines={limitLines ? MAX_POST_LINES : undefined} 309 + authorHandle={post.author.handle} 310 + shouldProxyLinks={true} 311 + /> 312 + {limitLines && ( 313 + <ShowMoreTextButton 314 + style={[a.text_md]} 315 + onPress={onPressShowMore} 316 + /> 317 + )} 318 + </> 323 319 ) : undefined} 324 320 {post.embed && ( 325 321 <View style={[a.pb_xs]}>
+16 -22
src/screens/PostThread/components/ThreadItemTreePost.tsx
··· 1 - import React, {memo, useMemo} from 'react' 1 + import {memo, useCallback, useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import { 4 4 type AppBskyFeedDefs, ··· 6 6 AtUri, 7 7 RichText as RichTextAPI, 8 8 } from '@atproto/api' 9 - import {msg, Trans} from '@lingui/macro' 10 - import {useLingui} from '@lingui/react' 9 + import {Trans} from '@lingui/macro' 11 10 12 11 import {MAX_POST_LINES} from '#/lib/constants' 13 12 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 14 - import {usePalette} from '#/lib/hooks/usePalette' 15 13 import {makeProfileLink} from '#/lib/routes/links' 16 14 import {countLines} from '#/lib/strings/helpers' 17 15 import { ··· 23 21 import {useSession} from '#/state/session' 24 22 import {type OnPostSuccessData} from '#/state/shell/composer' 25 23 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 26 - import {TextLink} from '#/view/com/util/Link' 27 24 import {PostMeta} from '#/view/com/util/PostMeta' 28 25 import { 29 26 OUTER_SPACE, ··· 39 36 import {PostHider} from '#/components/moderation/PostHider' 40 37 import {type AppModerationCause} from '#/components/Pills' 41 38 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 39 + import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 42 40 import {PostControls} from '#/components/PostControls' 43 41 import {RichText} from '#/components/RichText' 44 42 import * as Skele from '#/components/Skeleton' ··· 255 253 onPostSuccess?: (data: OnPostSuccessData) => void 256 254 threadgateRecord?: AppBskyFeedThreadgate.Record 257 255 }): React.ReactNode { 258 - const pal = usePalette('default') 259 - const {_} = useLingui() 260 256 const {openComposer} = useOpenComposer() 261 257 const {currentAccount} = useSession() 262 258 ··· 271 267 }), 272 268 [record], 273 269 ) 274 - const [limitLines, setLimitLines] = React.useState( 270 + const [limitLines, setLimitLines] = useState( 275 271 () => countLines(richText?.text) >= MAX_POST_LINES, 276 272 ) 277 273 const threadRootUri = record.reply?.root?.uri || post.uri 278 - const postHref = React.useMemo(() => { 274 + const postHref = useMemo(() => { 279 275 const urip = new AtUri(post.uri) 280 276 return makeProfileLink(post.author, 'post', urip.rkey) 281 277 }, [post.uri, post.author]) 282 278 const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({ 283 279 threadgateRecord, 284 280 }) 285 - const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => { 281 + const additionalPostAlerts: AppModerationCause[] = useMemo(() => { 286 282 const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) 287 283 const isControlledByViewer = 288 284 new AtUri(threadRootUri).host === currentAccount?.did ··· 297 293 : [] 298 294 }, [post, currentAccount?.did, threadgateHiddenReplies, threadRootUri]) 299 295 300 - const onPressReply = React.useCallback(() => { 296 + const onPressReply = useCallback(() => { 301 297 openComposer({ 302 298 replyTo: { 303 299 uri: post.uri, ··· 311 307 }) 312 308 }, [openComposer, post, record, onPostSuccess, moderation]) 313 309 314 - const onPressShowMore = React.useCallback(() => { 310 + const onPressShowMore = useCallback(() => { 315 311 setLimitLines(false) 316 312 }, [setLimitLines]) 317 313 ··· 348 344 additionalCauses={additionalPostAlerts} 349 345 /> 350 346 {richText?.text ? ( 351 - <View> 347 + <> 352 348 <RichText 353 349 enableTags 354 350 value={richText} ··· 357 353 authorHandle={post.author.handle} 358 354 shouldProxyLinks={true} 359 355 /> 360 - </View> 361 - ) : undefined} 362 - {limitLines ? ( 363 - <TextLink 364 - text={_(msg`Show More`)} 365 - style={pal.link} 366 - onPress={onPressShowMore} 367 - href="#" 368 - /> 356 + {limitLines && ( 357 + <ShowMoreTextButton 358 + style={[a.text_md]} 359 + onPress={onPressShowMore} 360 + /> 361 + )} 362 + </> 369 363 ) : undefined} 370 364 {post.embed && ( 371 365 <View style={[a.pb_xs]}>
+3 -3
src/screens/VideoFeed/components/Scrubber.tsx
··· 3 3 import { 4 4 Gesture, 5 5 GestureDetector, 6 - NativeGesture, 6 + type NativeGesture, 7 7 } from 'react-native-gesture-handler' 8 8 import Animated, { 9 9 interpolate, 10 10 runOnJS, 11 11 runOnUI, 12 - SharedValue, 12 + type SharedValue, 13 13 useAnimatedReaction, 14 14 useAnimatedStyle, 15 15 useSharedValue, ··· 20 20 useSafeAreaInsets, 21 21 } from 'react-native-safe-area-context' 22 22 import {useEventListener} from 'expo' 23 - import {VideoPlayer} from 'expo-video' 23 + import {type VideoPlayer} from 'expo-video' 24 24 25 25 import {tokens} from '#/alf' 26 26 import {atoms as a} from '#/alf'
+3 -1
src/view/com/notifications/NotificationFeedItem.tsx
··· 30 30 import {useNavigation} from '@react-navigation/native' 31 31 import {useQueryClient} from '@tanstack/react-query' 32 32 33 + import {MAX_POST_LINES} from '#/lib/constants' 33 34 import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' 34 35 import {usePalette} from '#/lib/hooks/usePalette' 35 36 import {makeProfileLink} from '#/lib/routes/links' ··· 918 919 {text?.length > 0 && ( 919 920 <Text 920 921 emoji 921 - style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 922 + style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]} 923 + numberOfLines={MAX_POST_LINES}> 922 924 {text} 923 925 </Text> 924 926 )}
+8 -9
src/view/com/post-thread/PostThreadItem.tsx
··· 44 44 import {type PostSource} from '#/state/unstable-post-source' 45 45 import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn' 46 46 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 47 - import {Link, TextLink} from '#/view/com/util/Link' 47 + import {Link} from '#/view/com/util/Link' 48 48 import {formatCount} from '#/view/com/util/numeric/format' 49 49 import {PostMeta} from '#/view/com/util/PostMeta' 50 50 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' ··· 62 62 import {PostHider} from '#/components/moderation/PostHider' 63 63 import {type AppModerationCause} from '#/components/Pills' 64 64 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 65 + import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 65 66 import {PostControls} from '#/components/PostControls' 66 67 import * as Prompt from '#/components/Prompt' 67 68 import {RichText} from '#/components/RichText' ··· 685 686 authorHandle={post.author.handle} 686 687 shouldProxyLinks={true} 687 688 /> 689 + {limitLines && ( 690 + <ShowMoreTextButton 691 + style={[a.text_md]} 692 + onPress={onPressShowMore} 693 + /> 694 + )} 688 695 </View> 689 - ) : undefined} 690 - {limitLines ? ( 691 - <TextLink 692 - text={_(msg`Show More`)} 693 - style={pal.link} 694 - onPress={onPressShowMore} 695 - href="#" 696 - /> 697 696 ) : undefined} 698 697 {post.embed && ( 699 698 <View style={[a.pb_xs]}>
+15 -24
src/view/com/post/Post.tsx
··· 1 - import React, {useMemo, useState} from 'react' 1 + import {useCallback, useMemo, useState} from 'react' 2 2 import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' 3 3 import { 4 4 type AppBskyFeedDefs, ··· 9 9 RichText as RichTextAPI, 10 10 } from '@atproto/api' 11 11 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12 - import {msg, Trans} from '@lingui/macro' 13 - import {useLingui} from '@lingui/react' 12 + import {Trans} from '@lingui/macro' 14 13 import {useQueryClient} from '@tanstack/react-query' 15 14 16 15 import {MAX_POST_LINES} from '#/lib/constants' ··· 27 26 import {useModerationOpts} from '#/state/preferences/moderation-opts' 28 27 import {precacheProfile} from '#/state/queries/profile' 29 28 import {useSession} from '#/state/session' 30 - import {Link, TextLink} from '#/view/com/util/Link' 29 + import {Link} from '#/view/com/util/Link' 31 30 import {PostMeta} from '#/view/com/util/PostMeta' 32 31 import {Text} from '#/view/com/util/text/Text' 33 32 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' ··· 37 36 import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' 38 37 import {PostAlerts} from '#/components/moderation/PostAlerts' 39 38 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 39 + import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 40 40 import {PostControls} from '#/components/PostControls' 41 41 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 42 42 import {RichText} from '#/components/RichText' ··· 115 115 }) { 116 116 const queryClient = useQueryClient() 117 117 const pal = usePalette('default') 118 - const {_} = useLingui() 119 118 const {openComposer} = useOpenComposer() 120 119 const [limitLines, setLimitLines] = useState( 121 120 () => countLines(richText?.text) >= MAX_POST_LINES, ··· 128 127 replyAuthorDid = urip.hostname 129 128 } 130 129 131 - const onPressReply = React.useCallback(() => { 130 + const onPressReply = useCallback(() => { 132 131 openComposer({ 133 132 replyTo: { 134 133 uri: post.uri, ··· 141 140 }) 142 141 }, [openComposer, post, record, moderation]) 143 142 144 - const onPressShowMore = React.useCallback(() => { 143 + const onPressShowMore = useCallback(() => { 145 144 setLimitLines(false) 146 145 }, [setLimitLines]) 147 146 148 - const onBeforePress = React.useCallback(() => { 147 + const onBeforePress = useCallback(() => { 149 148 precacheProfile(queryClient, post.author) 150 149 }, [queryClient, post.author]) 151 150 152 151 const {currentAccount} = useSession() 153 152 const isMe = replyAuthorDid === currentAccount?.did 154 153 155 - const [hover, setHover] = React.useState(false) 154 + const [hover, setHover] = useState(false) 156 155 return ( 157 156 <Link 158 157 href={itemHref} ··· 227 226 style={[a.py_xs]} 228 227 /> 229 228 {richText.text ? ( 230 - <View style={styles.postTextContainer}> 229 + <View> 231 230 <RichText 232 231 enableTags 233 232 testID="postText" ··· 237 236 authorHandle={post.author.handle} 238 237 shouldProxyLinks={true} 239 238 /> 239 + {limitLines && ( 240 + <ShowMoreTextButton 241 + style={[a.text_md]} 242 + onPress={onPressShowMore} 243 + /> 244 + )} 240 245 </View> 241 - ) : undefined} 242 - {limitLines ? ( 243 - <TextLink 244 - text={_(msg`Show More`)} 245 - style={pal.link} 246 - onPress={onPressShowMore} 247 - href="#" 248 - /> 249 246 ) : undefined} 250 247 {post.embed ? ( 251 248 <Embed ··· 289 286 }, 290 287 alert: { 291 288 marginBottom: 6, 292 - }, 293 - postTextContainer: { 294 - flexDirection: 'row', 295 - alignItems: 'center', 296 - flexWrap: 'wrap', 297 - overflow: 'hidden', 298 289 }, 299 290 replyLine: { 300 291 position: 'absolute',
+7 -20
src/view/com/posts/PostFeedItem.tsx
··· 41 41 setUnstablePostSource, 42 42 } from '#/state/unstable-post-source' 43 43 import {FeedNameText} from '#/view/com/util/FeedInfoText' 44 - import {Link, TextLink, TextLinkOnWebOnly} from '#/view/com/util/Link' 44 + import {Link, TextLinkOnWebOnly} from '#/view/com/util/Link' 45 45 import {PostMeta} from '#/view/com/util/PostMeta' 46 46 import {Text} from '#/view/com/util/text/Text' 47 47 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' ··· 54 54 import {type AppModerationCause} from '#/components/Pills' 55 55 import {Embed} from '#/components/Post/Embed' 56 56 import {PostEmbedViewContext} from '#/components/Post/Embed/types' 57 + import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 57 58 import {PostControls} from '#/components/PostControls' 58 59 import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug' 59 60 import {ProfileHoverCard} from '#/components/ProfileHoverCard' ··· 501 502 post: AppBskyFeedDefs.PostView 502 503 threadgateRecord?: AppBskyFeedThreadgate.Record 503 504 }): React.ReactNode => { 504 - const pal = usePalette('default') 505 - const {_} = useLingui() 506 505 const {currentAccount} = useSession() 507 506 const [limitLines, setLimitLines] = useState( 508 507 () => countLines(richText.text) >= MAX_POST_LINES, ··· 547 546 additionalCauses={additionalPostAlerts} 548 547 /> 549 548 {richText.text ? ( 550 - <View style={styles.postTextContainer}> 549 + <> 551 550 <RichText 552 551 enableTags 553 552 testID="postText" ··· 557 556 authorHandle={postAuthor.handle} 558 557 shouldProxyLinks={true} 559 558 /> 560 - </View> 561 - ) : undefined} 562 - {limitLines ? ( 563 - <TextLink 564 - text={_(msg`Show More`)} 565 - style={pal.link} 566 - onPress={onPressShowMore} 567 - href="#" 568 - /> 559 + {limitLines && ( 560 + <ShowMoreTextButton style={[a.text_md]} onPress={onPressShowMore} /> 561 + )} 562 + </> 569 563 ) : undefined} 570 564 {postEmbed ? ( 571 565 <View style={[a.pb_xs]}> ··· 688 682 alert: { 689 683 marginTop: 6, 690 684 marginBottom: 6, 691 - }, 692 - postTextContainer: { 693 - flexDirection: 'row', 694 - alignItems: 'center', 695 - flexWrap: 'wrap', 696 - paddingBottom: 2, 697 - overflow: 'hidden', 698 685 }, 699 686 contentHiderChild: { 700 687 marginTop: 6,