Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 210 lines 7.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/core/macro' 5import {useLingui} from '@lingui/react' 6import {useQueryClient} from '@tanstack/react-query' 7 8import {makeProfileLink} from '#/lib/routes/links' 9import {forceLTR} from '#/lib/strings/bidi' 10import {NON_BREAKING_SPACE} from '#/lib/strings/constants' 11import {sanitizeDisplayName} from '#/lib/strings/display-names' 12import {sanitizeHandle} from '#/lib/strings/handles' 13import {sanitizePronouns} from '#/lib/strings/pronouns' 14import {niceDate} from '#/lib/strings/time' 15import {useProfileShadow} from '#/state/cache/profile-shadow' 16import {unstableCacheProfileView} from '#/state/queries/profile' 17import {atoms as a, platform, useTheme, web} from '#/alf' 18import {WebOnlyInlineLinkText} from '#/components/Link' 19import {ProfileBadges} from '#/components/ProfileBadges' 20import {ProfileHoverCard} from '#/components/ProfileHoverCard' 21import {Text} from '#/components/Typography' 22import {IS_ANDROID} from '#/env' 23import {useActorStatus} from '#/features/liveNow' 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 linkDisabled?: boolean 33 showAvatar?: boolean 34 showPronouns?: boolean 35 avatarSize?: number 36 onOpenAuthor?: () => void 37 style?: StyleProp<ViewStyle> 38} 39 40let PostMeta = (opts: PostMetaOpts): React.ReactNode => { 41 const t = useTheme() 42 const {i18n, _} = useLingui() 43 44 const author = useProfileShadow(opts.author) 45 const displayName = author.displayName || author.handle 46 const handle = author.handle 47 // remove dumb typing when you update the atproto api package!! 48 const pronouns = (author as {pronouns?: string})?.pronouns 49 const profileLink = makeProfileLink(author) 50 const queryClient = useQueryClient() 51 const onOpenAuthor = opts.onOpenAuthor 52 const onBeforePressAuthor = useCallback(() => { 53 unstableCacheProfileView(queryClient, author) 54 onOpenAuthor?.() 55 }, [queryClient, author, onOpenAuthor]) 56 const onBeforePressPost = useCallback(() => { 57 unstableCacheProfileView(queryClient, author) 58 }, [queryClient, author]) 59 60 const timestampLabel = niceDate(i18n, opts.timestamp) 61 const {isActive: live} = useActorStatus(author) 62 63 const MaybeLinkText = opts.linkDisabled ? Text : WebOnlyInlineLinkText 64 65 return ( 66 <View 67 style={[ 68 IS_ANDROID ? a.flex_1 : a.flex_shrink, 69 a.flex_row, 70 a.align_center, 71 a.pb_xs, 72 a.gap_xs, 73 a.z_20, 74 opts.style, 75 ]}> 76 {opts.showAvatar && ( 77 <View style={[a.self_center, a.mr_2xs]}> 78 <PreviewableUserAvatar 79 size={opts.avatarSize || 16} 80 profile={author} 81 moderation={opts.moderation?.ui('avatar')} 82 type={author.associated?.labeler ? 'labeler' : 'user'} 83 live={live} 84 hideLiveBadge 85 disableNavigation={opts.linkDisabled} 86 /> 87 </View> 88 )} 89 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 90 <ProfileHoverCard did={author.did}> 91 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 92 <MaybeLinkText 93 emoji 94 numberOfLines={1} 95 to={profileLink} 96 label={_(msg`View profile`)} 97 disableMismatchWarning 98 onPress={opts.linkDisabled ? undefined : onBeforePressAuthor} 99 style={[ 100 a.text_md, 101 a.font_semi_bold, 102 t.atoms.text, 103 a.leading_tight, 104 a.flex_shrink, 105 ]}> 106 {forceLTR( 107 sanitizeDisplayName( 108 displayName, 109 opts.moderation?.ui('displayName'), 110 ), 111 )} 112 </MaybeLinkText> 113 <ProfileBadges 114 profile={author} 115 size="sm" 116 pdsInteractive={false} 117 style={[ 118 a.pl_2xs, 119 a.self_center, 120 { 121 marginTop: platform({web: 1, ios: 0, android: -1}), 122 }, 123 ]} 124 /> 125 <MaybeLinkText 126 emoji 127 numberOfLines={1} 128 to={profileLink} 129 label={_(msg`View profile`)} 130 disableMismatchWarning 131 disableUnderline 132 onPress={opts.linkDisabled ? undefined : onBeforePressAuthor} 133 style={[ 134 a.text_md, 135 t.atoms.text_contrast_medium, 136 {lineHeight: 1.17}, 137 {flexBasis: '30%'}, 138 a.flex_grow, 139 a.flex_shrink_0, 140 web({maxWidth: 'max-content'}), 141 ]}> 142 {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} 143 </MaybeLinkText> 144 {opts.showPronouns && pronouns && ( 145 <WebOnlyInlineLinkText 146 emoji 147 numberOfLines={1} 148 to={profileLink} 149 label={_(msg`View Profile`)} 150 disableMismatchWarning 151 disableUnderline 152 onPress={onBeforePressAuthor} 153 style={[ 154 t.atoms.text_contrast_low, 155 a.pl_2xs, 156 a.text_md, 157 {lineHeight: 1.17}, 158 {flexShrink: 5}, 159 ]}> 160 {NON_BREAKING_SPACE + sanitizePronouns(pronouns)} 161 </WebOnlyInlineLinkText> 162 )} 163 </View> 164 </ProfileHoverCard> 165 166 <TimeElapsed timestamp={opts.timestamp}> 167 {({timeElapsed}) => ( 168 <MaybeLinkText 169 to={opts.postHref} 170 label={timestampLabel} 171 title={timestampLabel} 172 disableMismatchWarning 173 disableUnderline 174 onPress={opts.linkDisabled ? undefined : onBeforePressPost} 175 style={[ 176 a.pl_xs, 177 a.text_md, 178 a.leading_tight, 179 IS_ANDROID && a.flex_grow, 180 a.text_right, 181 t.atoms.text_contrast_medium, 182 web({ 183 whiteSpace: 'nowrap', 184 }), 185 ]}> 186 {!opts.showPronouns && ( 187 <> 188 {!IS_ANDROID && ( 189 <Text 190 style={[ 191 a.text_md, 192 a.leading_tight, 193 t.atoms.text_contrast_medium, 194 ]} 195 accessible={false}> 196 &middot;{' '} 197 </Text> 198 )} 199 {timeElapsed} 200 </> 201 )} 202 </MaybeLinkText> 203 )} 204 </TimeElapsed> 205 </View> 206 </View> 207 ) 208} 209PostMeta = memo(PostMeta) 210export {PostMeta}