Bluesky app fork with some witchin' additions 💫

Merge branch 'main' of https://github.com/bluesky-social/social-app

+176 -140
+3 -1
src/components/Post/PostRepliedTo.tsx
··· 57 57 size="xs" 58 58 style={[t.atoms.text_contrast_medium, {top: -1}]} 59 59 /> 60 - <Text style={textStyle}>{label}</Text> 60 + <Text style={[a.flex_1, textStyle]} numberOfLines={1}> 61 + {label} 62 + </Text> 61 63 </View> 62 64 ) 63 65 }
+4 -1
src/lib/moderation/create-sanitized-display-name.ts
··· 1 + import {type ModerationUI} from '@atproto/api' 2 + 1 3 import {sanitizeDisplayName} from '#/lib/strings/display-names' 2 4 import {sanitizeHandle} from '#/lib/strings/handles' 3 5 import type * as bsky from '#/types/bsky' ··· 5 7 export function createSanitizedDisplayName( 6 8 profile: bsky.profile.AnyProfileView, 7 9 noAt = false, 10 + moderation?: ModerationUI, 8 11 ) { 9 12 if (profile.displayName != null && profile.displayName !== '') { 10 - return sanitizeDisplayName(profile.displayName) 13 + return sanitizeDisplayName(profile.displayName, moderation) 11 14 } else { 12 15 return sanitizeHandle(profile.handle, noAt ? '' : '@') 13 16 }
+1 -1
src/view/com/post/Post.tsx
··· 195 195 childContainerStyle={styles.contentHiderChild}> 196 196 <PostAlerts 197 197 modui={moderation.ui('contentView')} 198 - style={[a.py_xs]} 198 + style={[a.pb_xs]} 199 199 /> 200 200 {richText.text ? ( 201 201 <View>
+12 -117
src/view/com/posts/PostFeedItem.tsx
··· 9 9 type ModerationDecision, 10 10 RichText as RichTextAPI, 11 11 } from '@atproto/api' 12 - import {msg, Trans} from '@lingui/macro' 13 - import {useLingui} from '@lingui/react' 14 12 import {useNavigation} from '@react-navigation/native' 15 13 import {useQueryClient} from '@tanstack/react-query' 16 14 17 15 import {useActorStatus} from '#/lib/actor-status' 18 - import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types' 16 + import {type ReasonFeedSource} from '#/lib/api/feed/types' 19 17 import {MAX_POST_LINES} from '#/lib/constants' 20 18 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 21 19 import {usePalette} from '#/lib/hooks/usePalette' 22 20 import {makeProfileLink} from '#/lib/routes/links' 23 21 import {type NavigationProp} from '#/lib/routes/types' 24 22 import {useGate} from '#/lib/statsig/statsig' 25 - import {sanitizeDisplayName} from '#/lib/strings/display-names' 26 - import {sanitizeHandle} from '#/lib/strings/handles' 27 23 import {countLines} from '#/lib/strings/helpers' 28 24 import { 29 25 POST_TOMBSTONE, ··· 38 34 buildPostSourceKey, 39 35 setUnstablePostSource, 40 36 } from '#/state/unstable-post-source' 41 - import {FeedNameText} from '#/view/com/util/FeedInfoText' 42 - import {Link, TextLinkOnWebOnly} from '#/view/com/util/Link' 37 + import {Link} from '#/view/com/util/Link' 43 38 import {PostMeta} from '#/view/com/util/PostMeta' 44 - import {Text} from '#/view/com/util/text/Text' 45 39 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' 46 40 import {atoms as a} from '#/alf' 47 - import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 48 - import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost' 49 41 import {ContentHider} from '#/components/moderation/ContentHider' 50 42 import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' 51 43 import {PostAlerts} from '#/components/moderation/PostAlerts' ··· 56 48 import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 57 49 import {PostControls} from '#/components/PostControls' 58 50 import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug' 59 - import {ProfileHoverCard} from '#/components/ProfileHoverCard' 60 51 import {RichText} from '#/components/RichText' 61 52 import {SubtleHover} from '#/components/SubtleHover' 62 53 import * as bsky from '#/types/bsky' 54 + import {PostFeedReason} from './PostFeedReason' 63 55 64 56 interface FeedItemProps { 65 57 record: AppBskyFeedPost.Record ··· 177 169 const navigation = useNavigation<NavigationProp>() 178 170 const pal = usePalette('default') 179 171 const gate = useGate() 180 - const {_} = useLingui() 181 172 182 173 const [hover, setHover] = useState(false) 183 174 ··· 279 270 }, 280 271 ] 281 272 282 - const {currentAccount} = useSession() 283 - const isOwner = 284 - AppBskyFeedDefs.isReasonRepost(reason) && 285 - reason.by.did === currentAccount?.did 286 - 287 273 /** 288 274 * If `post[0]` in this slice is the actual root post (not an orphan thread), 289 275 * then we may have a threadgate record to reference ··· 338 324 )} 339 325 </View> 340 326 341 - <View style={{paddingTop: 10, flexShrink: 1}}> 342 - {isReasonFeedSource(reason) ? ( 343 - <Link href={reason.href}> 344 - <Text 345 - type="sm-bold" 346 - style={pal.textLight} 347 - lineHeight={1.2} 348 - numberOfLines={1}> 349 - <Trans context="from-feed"> 350 - From{' '} 351 - <FeedNameText 352 - type="sm-bold" 353 - uri={reason.uri} 354 - href={reason.href} 355 - lineHeight={1.2} 356 - numberOfLines={1} 357 - style={pal.textLight} 358 - /> 359 - </Trans> 360 - </Text> 361 - </Link> 362 - ) : AppBskyFeedDefs.isReasonRepost(reason) ? ( 363 - <Link 364 - style={styles.includeReason} 365 - href={makeProfileLink(reason.by)} 366 - title={ 367 - isOwner 368 - ? _(msg`Reposted by you`) 369 - : _( 370 - msg`Reposted by ${sanitizeDisplayName( 371 - reason.by.displayName || reason.by.handle, 372 - )}`, 373 - ) 374 - } 375 - onBeforePress={onOpenReposter}> 376 - <RepostIcon 377 - style={{color: pal.colors.textLight, marginRight: 3}} 378 - width={13} 379 - height={13} 380 - /> 381 - <Text 382 - type="sm-bold" 383 - style={pal.textLight} 384 - lineHeight={1.2} 385 - numberOfLines={1}> 386 - {isOwner ? ( 387 - <Trans>Reposted by you</Trans> 388 - ) : ( 389 - <Trans> 390 - Reposted by{' '} 391 - <ProfileHoverCard did={reason.by.did}> 392 - <TextLinkOnWebOnly 393 - type="sm-bold" 394 - style={pal.textLight} 395 - lineHeight={1.2} 396 - numberOfLines={1} 397 - text={ 398 - <Text 399 - emoji 400 - type="sm-bold" 401 - style={pal.textLight} 402 - lineHeight={1.2}> 403 - {sanitizeDisplayName( 404 - reason.by.displayName || 405 - sanitizeHandle(reason.by.handle), 406 - moderation.ui('displayName'), 407 - )} 408 - </Text> 409 - } 410 - href={makeProfileLink(reason.by)} 411 - onBeforePress={onOpenReposter} 412 - /> 413 - </ProfileHoverCard> 414 - </Trans> 415 - )} 416 - </Text> 417 - </Link> 418 - ) : AppBskyFeedDefs.isReasonPin(reason) ? ( 419 - <View style={styles.includeReason}> 420 - <PinIcon 421 - style={{color: pal.colors.textLight, marginRight: 3}} 422 - width={13} 423 - height={13} 424 - /> 425 - <Text 426 - type="sm-bold" 427 - style={pal.textLight} 428 - lineHeight={1.2} 429 - numberOfLines={1}> 430 - <Trans>Pinned</Trans> 431 - </Text> 432 - </View> 433 - ) : null} 327 + <View style={[a.pt_sm, a.flex_shrink]}> 328 + {reason && ( 329 + <PostFeedReason 330 + reason={reason} 331 + moderation={moderation} 332 + onOpenReposter={onOpenReposter} 333 + /> 334 + )} 434 335 </View> 435 336 </View> 436 337 ··· 561 462 childContainerStyle={styles.contentHiderChild}> 562 463 <PostAlerts 563 464 modui={moderation.ui('contentList')} 564 - style={[a.py_2xs]} 465 + style={[a.pb_xs]} 565 466 additionalCauses={additionalPostAlerts} 566 467 /> 567 468 {richText.text ? ( ··· 605 506 width: 2, 606 507 marginLeft: 'auto', 607 508 marginRight: 'auto', 608 - }, 609 - includeReason: { 610 - flexDirection: 'row', 611 - alignItems: 'center', 612 - marginBottom: 2, 613 - marginLeft: -16, 614 509 }, 615 510 layout: { 616 511 flexDirection: 'row',
+139
src/view/com/posts/PostFeedReason.tsx
··· 1 + import {StyleSheet, View} from 'react-native' 2 + import {AppBskyFeedDefs, type ModerationDecision} from '@atproto/api' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types' 7 + import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 8 + import {makeProfileLink} from '#/lib/routes/links' 9 + import {useSession} from '#/state/session' 10 + import {atoms as a, useTheme} from '#/alf' 11 + import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 12 + import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost' 13 + import {Link, WebOnlyInlineLinkText} from '#/components/Link' 14 + import {ProfileHoverCard} from '#/components/ProfileHoverCard' 15 + import {Text} from '#/components/Typography' 16 + import {FeedNameText} from '../util/FeedInfoText' 17 + 18 + export function PostFeedReason({ 19 + reason, 20 + moderation, 21 + onOpenReposter, 22 + }: { 23 + reason: 24 + | ReasonFeedSource 25 + | AppBskyFeedDefs.ReasonRepost 26 + | AppBskyFeedDefs.ReasonPin 27 + | {[k: string]: unknown; $type: string} 28 + moderation?: ModerationDecision 29 + onOpenReposter?: () => void 30 + }) { 31 + const t = useTheme() 32 + const {_} = useLingui() 33 + 34 + const {currentAccount} = useSession() 35 + 36 + if (isReasonFeedSource(reason)) { 37 + return ( 38 + <Link label={_(msg`Go to feed`)} to={reason.href}> 39 + <Text 40 + style={[ 41 + t.atoms.text_contrast_medium, 42 + a.font_medium, 43 + a.leading_snug, 44 + a.leading_snug, 45 + ]} 46 + numberOfLines={1}> 47 + <Trans context="from-feed"> 48 + From{' '} 49 + <FeedNameText 50 + uri={reason.uri} 51 + href={reason.href} 52 + style={[ 53 + t.atoms.text_contrast_medium, 54 + a.font_medium, 55 + a.leading_snug, 56 + ]} 57 + numberOfLines={1} 58 + /> 59 + </Trans> 60 + </Text> 61 + </Link> 62 + ) 63 + } 64 + 65 + if (AppBskyFeedDefs.isReasonRepost(reason)) { 66 + const isOwner = reason.by.did === currentAccount?.did 67 + const reposter = createSanitizedDisplayName( 68 + reason.by, 69 + false, 70 + moderation?.ui('displayName'), 71 + ) 72 + return ( 73 + <Link 74 + style={styles.includeReason} 75 + to={makeProfileLink(reason.by)} 76 + label={ 77 + isOwner ? _(msg`Reposted by you`) : _(msg`Reposted by ${reposter}`) 78 + } 79 + onPress={onOpenReposter}> 80 + <RepostIcon 81 + style={[t.atoms.text_contrast_medium, {marginRight: 3}]} 82 + width={13} 83 + height={13} 84 + /> 85 + <Text 86 + style={[t.atoms.text_contrast_medium, a.font_medium, a.leading_snug]} 87 + numberOfLines={1}> 88 + {isOwner ? ( 89 + <Trans>Reposted by you</Trans> 90 + ) : ( 91 + <Trans> 92 + Reposted by{' '} 93 + <ProfileHoverCard did={reason.by.did}> 94 + <WebOnlyInlineLinkText 95 + label={reposter} 96 + numberOfLines={1} 97 + to={makeProfileLink(reason.by)} 98 + onPress={onOpenReposter} 99 + style={[ 100 + t.atoms.text_contrast_medium, 101 + a.font_medium, 102 + a.leading_snug, 103 + ]}> 104 + {reposter} 105 + </WebOnlyInlineLinkText> 106 + </ProfileHoverCard> 107 + </Trans> 108 + )} 109 + </Text> 110 + </Link> 111 + ) 112 + } 113 + 114 + if (AppBskyFeedDefs.isReasonPin(reason)) { 115 + return ( 116 + <View style={styles.includeReason}> 117 + <PinIcon 118 + style={[t.atoms.text_contrast_medium, {marginRight: 3}]} 119 + width={13} 120 + height={13} 121 + /> 122 + <Text 123 + style={[t.atoms.text_contrast_medium, a.font_medium, a.leading_snug]} 124 + numberOfLines={1}> 125 + <Trans>Pinned</Trans> 126 + </Text> 127 + </View> 128 + ) 129 + } 130 + } 131 + 132 + const styles = StyleSheet.create({ 133 + includeReason: { 134 + flexDirection: 'row', 135 + alignItems: 'center', 136 + marginBottom: 2, 137 + marginLeft: -16, 138 + }, 139 + })
+17 -20
src/view/com/util/FeedInfoText.tsx
··· 1 - import {type StyleProp, StyleSheet, type TextStyle} from 'react-native' 1 + import {type StyleProp, type TextStyle} from 'react-native' 2 2 3 3 import {sanitizeDisplayName} from '#/lib/strings/display-names' 4 - import {type TypographyVariant} from '#/lib/ThemeContext' 5 4 import {useFeedSourceInfoQuery} from '#/state/queries/feed' 6 - import {TextLinkOnWebOnly} from './Link' 5 + import {atoms as a, platform} from '#/alf' 6 + import {WebOnlyInlineLinkText} from '#/components/Link' 7 7 import {LoadingPlaceholder} from './LoadingPlaceholder' 8 8 9 9 export function FeedNameText({ 10 - type = 'md', 11 10 uri, 12 11 href, 13 - lineHeight, 14 12 numberOfLines, 15 13 style, 16 14 }: { 17 - type?: TypographyVariant 18 15 uri: string 19 16 href: string 20 - lineHeight?: number 21 17 numberOfLines?: number 22 18 style?: StyleProp<TextStyle> 23 19 }) { 24 20 const {data, isError} = useFeedSourceInfoQuery({uri}) 25 21 26 22 let inner 27 - if (data?.displayName || isError) { 23 + if (data || isError) { 28 24 const displayName = data?.displayName || uri.split('/').pop() || '' 29 25 inner = ( 30 - <TextLinkOnWebOnly 31 - type={type} 26 + <WebOnlyInlineLinkText 27 + to={href} 28 + label={displayName} 32 29 style={style} 33 - lineHeight={lineHeight} 34 - numberOfLines={numberOfLines} 35 - href={href} 36 - text={sanitizeDisplayName(displayName)} 37 - /> 30 + numberOfLines={numberOfLines}> 31 + {sanitizeDisplayName(displayName)} 32 + </WebOnlyInlineLinkText> 38 33 ) 39 34 } else { 40 35 inner = ( 41 36 <LoadingPlaceholder 42 37 width={80} 43 38 height={8} 44 - style={styles.loadingPlaceholder} 39 + style={[ 40 + a.ml_2xs, 41 + platform({ 42 + native: [a.mt_2xs], 43 + web: [{top: -1}], 44 + }), 45 + ]} 45 46 /> 46 47 ) 47 48 } 48 49 49 50 return inner 50 51 } 51 - 52 - const styles = StyleSheet.create({ 53 - loadingPlaceholder: {position: 'relative', top: 1, left: 2}, 54 - })