Bluesky app fork with some witchin' additions 馃挮
at readme-update 182 lines 6.1 kB view raw
1import {memo, useCallback} from 'react' 2import {type StyleProp, View, type ViewStyle} from 'react-native' 3import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useQueryClient} from '@tanstack/react-query' 7 8import {useActorStatus} from '#/lib/actor-status' 9import {makeProfileLink} from '#/lib/routes/links' 10import {forceLTR} from '#/lib/strings/bidi' 11import {NON_BREAKING_SPACE} from '#/lib/strings/constants' 12import {sanitizeDisplayName} from '#/lib/strings/display-names' 13import {sanitizeHandle} from '#/lib/strings/handles' 14import {niceDate} from '#/lib/strings/time' 15import {useProfileShadow} from '#/state/cache/profile-shadow' 16import {precacheProfile} from '#/state/queries/profile' 17import {atoms as a, platform, useTheme, web} from '#/alf' 18import {WebOnlyInlineLinkText} from '#/components/Link' 19import {ProfileHoverCard} from '#/components/ProfileHoverCard' 20import {Text} from '#/components/Typography' 21import {useSimpleVerificationState} from '#/components/verification' 22import {VerificationCheck} from '#/components/verification/VerificationCheck' 23import {IS_ANDROID} from '#/env' 24import {TimeElapsed} from './TimeElapsed' 25import {PreviewableUserAvatar} from './UserAvatar' 26 27interface PostMetaOpts { 28 author: AppBskyActorDefs.ProfileViewBasic 29 moderation: ModerationDecision | undefined 30 postHref: string 31 timestamp: string 32 showAvatar?: boolean 33 avatarSize?: number 34 onOpenAuthor?: () => void 35 style?: StyleProp<ViewStyle> 36} 37 38let PostMeta = (opts: PostMetaOpts): React.ReactNode => { 39 const t = useTheme() 40 const {i18n, _} = useLingui() 41 42 const author = useProfileShadow(opts.author) 43 const displayName = author.displayName || author.handle 44 const handle = author.handle 45 const profileLink = makeProfileLink(author) 46 const queryClient = useQueryClient() 47 const onOpenAuthor = opts.onOpenAuthor 48 const onBeforePressAuthor = useCallback(() => { 49 precacheProfile(queryClient, author) 50 onOpenAuthor?.() 51 }, [queryClient, author, onOpenAuthor]) 52 const onBeforePressPost = useCallback(() => { 53 precacheProfile(queryClient, author) 54 }, [queryClient, author]) 55 56 const timestampLabel = niceDate(i18n, opts.timestamp) 57 const verification = useSimpleVerificationState({profile: author}) 58 const {isActive: live} = useActorStatus(author) 59 60 return ( 61 <View 62 style={[ 63 IS_ANDROID ? a.flex_1 : a.flex_shrink, 64 a.flex_row, 65 a.align_center, 66 a.pb_xs, 67 a.gap_xs, 68 a.z_20, 69 opts.style, 70 ]}> 71 {opts.showAvatar && ( 72 <View style={[a.self_center, a.mr_2xs]}> 73 <PreviewableUserAvatar 74 size={opts.avatarSize || 16} 75 profile={author} 76 moderation={opts.moderation?.ui('avatar')} 77 type={author.associated?.labeler ? 'labeler' : 'user'} 78 live={live} 79 hideLiveBadge 80 /> 81 </View> 82 )} 83 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 84 <ProfileHoverCard did={author.did}> 85 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 86 <WebOnlyInlineLinkText 87 emoji 88 numberOfLines={1} 89 to={profileLink} 90 label={_(msg`View profile`)} 91 disableMismatchWarning 92 onPress={onBeforePressAuthor} 93 style={[ 94 a.text_md, 95 a.font_semi_bold, 96 t.atoms.text, 97 a.leading_tight, 98 a.flex_shrink_0, 99 {maxWidth: '70%'}, 100 ]}> 101 {forceLTR( 102 sanitizeDisplayName( 103 displayName, 104 opts.moderation?.ui('displayName'), 105 ), 106 )} 107 </WebOnlyInlineLinkText> 108 {verification.showBadge && ( 109 <View 110 style={[ 111 a.pl_2xs, 112 a.self_center, 113 { 114 marginTop: platform({web: 1, ios: 0, android: -1}), 115 }, 116 ]}> 117 <VerificationCheck 118 width={platform({android: 13, default: 12})} 119 verifier={verification.role === 'verifier'} 120 /> 121 </View> 122 )} 123 <WebOnlyInlineLinkText 124 emoji 125 numberOfLines={1} 126 to={profileLink} 127 label={_(msg`View profile`)} 128 disableMismatchWarning 129 disableUnderline 130 onPress={onBeforePressAuthor} 131 style={[ 132 a.text_md, 133 t.atoms.text_contrast_medium, 134 {lineHeight:1.17}, 135 {flexShrink: 10}, 136 ]}> 137 {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} 138 </WebOnlyInlineLinkText> 139 </View> 140 </ProfileHoverCard> 141 142 <TimeElapsed timestamp={opts.timestamp}> 143 {({timeElapsed}) => ( 144 <WebOnlyInlineLinkText 145 to={opts.postHref} 146 label={timestampLabel} 147 title={timestampLabel} 148 disableMismatchWarning 149 disableUnderline 150 onPress={onBeforePressPost} 151 style={[ 152 a.pl_xs, 153 a.text_md, 154 a.leading_tight, 155 IS_ANDROID && a.flex_grow, 156 a.text_right, 157 t.atoms.text_contrast_medium, 158 web({ 159 whiteSpace: 'nowrap', 160 }), 161 ]}> 162 {!IS_ANDROID && ( 163 <Text 164 style={[ 165 a.text_md, 166 a.leading_tight, 167 t.atoms.text_contrast_medium, 168 ]} 169 accessible={false}> 170 &middot;{' '} 171 </Text> 172 )} 173 {timeElapsed} 174 </WebOnlyInlineLinkText> 175 )} 176 </TimeElapsed> 177 </View> 178 </View> 179 ) 180} 181PostMeta = memo(PostMeta) 182export {PostMeta}