my fork of the bluesky client

Profile hovers (#3518)

* Add hover card for mentions

* Reposted by

* Fix key

* Add to composer reply to

authored by

Eric Bailey and committed by
GitHub
f5bb348b a845587e

+64 -49
+4 -1
src/components/ProfileHoverCard/index.web.tsx
··· 116 116 ref={refs.setReference} 117 117 onPointerEnter={onPointerEnterTarget} 118 118 onPointerLeave={onPointerLeaveTarget} 119 - onMouseUp={onClickTarget}> 119 + onMouseUp={onClickTarget} 120 + style={{ 121 + display: props.inline ? 'inline' : 'block', 122 + }}> 120 123 {props.children} 121 124 122 125 {hovered && (
+1
src/components/ProfileHoverCard/types.ts
··· 3 3 export type ProfileHoverCardProps = { 4 4 children: React.ReactElement 5 5 did: string 6 + inline?: boolean 6 7 }
+12 -10
src/components/RichText.tsx
··· 8 8 import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf' 9 9 import {useInteractionState} from '#/components/hooks/useInteractionState' 10 10 import {InlineLinkText, LinkProps} from '#/components/Link' 11 + import {ProfileHoverCard} from '#/components/ProfileHoverCard' 11 12 import {TagMenu, useTagMenuControl} from '#/components/TagMenu' 12 13 import {Text, TextProps} from '#/components/Typography' 13 14 ··· 86 87 !disableLinks 87 88 ) { 88 89 els.push( 89 - <InlineLinkText 90 - selectable={selectable} 91 - key={key} 92 - to={`/profile/${mention.did}`} 93 - style={[...styles, {pointerEvents: 'auto'}]} 94 - // @ts-ignore TODO 95 - dataSet={WORD_WRAP} 96 - onPress={onLinkPress}> 97 - {segment.text} 98 - </InlineLinkText>, 90 + <ProfileHoverCard key={key} inline did={mention.did}> 91 + <InlineLinkText 92 + selectable={selectable} 93 + to={`/profile/${mention.did}`} 94 + style={[...styles, {pointerEvents: 'auto'}]} 95 + // @ts-ignore TODO 96 + dataSet={WORD_WRAP} 97 + onPress={onLinkPress}> 98 + {segment.text} 99 + </InlineLinkText> 100 + </ProfileHoverCard>, 99 101 ) 100 102 } else if (link && AppBskyRichtextFacet.validateLink(link).success) { 101 103 if (disableLinks) {
+11 -7
src/view/com/composer/ComposerReplyTo.tsx
··· 1 1 import React from 'react' 2 2 import {LayoutAnimation, Pressable, StyleSheet, View} from 'react-native' 3 3 import {Image} from 'expo-image' 4 - import {useLingui} from '@lingui/react' 5 - import {msg} from '@lingui/macro' 6 4 import { 7 5 AppBskyEmbedImages, 8 6 AppBskyEmbedRecord, 9 7 AppBskyEmbedRecordWithMedia, 10 8 AppBskyFeedPost, 11 9 } from '@atproto/api' 12 - import {ComposerOptsPostRef} from 'state/shell/composer' 10 + import {msg} from '@lingui/macro' 11 + import {useLingui} from '@lingui/react' 12 + 13 13 import {usePalette} from 'lib/hooks/usePalette' 14 14 import {sanitizeDisplayName} from 'lib/strings/display-names' 15 15 import {sanitizeHandle} from 'lib/strings/handles' 16 - import {UserAvatar} from 'view/com/util/UserAvatar' 16 + import {ComposerOptsPostRef} from 'state/shell/composer' 17 + import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed' 17 18 import {Text} from 'view/com/util/text/Text' 18 - import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed' 19 + import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' 19 20 20 21 export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { 21 22 const pal = usePalette('default') ··· 83 84 accessibilityHint={_( 84 85 msg`Expand or collapse the full post you are replying to`, 85 86 )}> 86 - <UserAvatar 87 - avatar={replyTo.author.avatar} 87 + <PreviewableUserAvatar 88 88 size={50} 89 + did={replyTo.author.did} 90 + handle={replyTo.author.handle} 91 + avatar={replyTo.author.avatar} 89 92 moderation={replyTo.moderation?.ui('avatar')} 90 93 type={replyTo.author.associated?.labeler ? 'labeler' : 'user'} 91 94 /> ··· 216 219 const styles = StyleSheet.create({ 217 220 replyToLayout: { 218 221 flexDirection: 'row', 222 + alignItems: 'flex-start', 219 223 borderTopWidth: 1, 220 224 paddingTop: 16, 221 225 paddingBottom: 16,
+36 -31
src/view/com/posts/FeedItem.tsx
··· 11 11 FontAwesomeIcon, 12 12 FontAwesomeIconStyle, 13 13 } from '@fortawesome/react-native-fontawesome' 14 - import {ReasonFeedSource, isReasonFeedSource} from 'lib/api/feed/types' 15 - import {Link, TextLinkOnWebOnly, TextLink} from '../util/Link' 16 - import {Text} from '../util/text/Text' 17 - import {UserInfoText} from '../util/UserInfoText' 18 - import {PostMeta} from '../util/PostMeta' 19 - import {PostCtrls} from '../util/post-ctrls/PostCtrls' 20 - import {PostEmbeds} from '../util/post-embeds' 21 - import {ContentHider} from '#/components/moderation/ContentHider' 22 - import {PostAlerts} from '../../../components/moderation/PostAlerts' 23 - import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' 24 - import {RichText} from '#/components/RichText' 25 - import {PreviewableUserAvatar} from '../util/UserAvatar' 26 - import {s} from 'lib/styles' 14 + import {msg, Trans} from '@lingui/macro' 15 + import {useLingui} from '@lingui/react' 16 + 17 + import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' 18 + import {useComposerControls} from '#/state/shell/composer' 19 + import {isReasonFeedSource, ReasonFeedSource} from 'lib/api/feed/types' 20 + import {MAX_POST_LINES} from 'lib/constants' 27 21 import {usePalette} from 'lib/hooks/usePalette' 22 + import {makeProfileLink} from 'lib/routes/links' 28 23 import {sanitizeDisplayName} from 'lib/strings/display-names' 29 24 import {sanitizeHandle} from 'lib/strings/handles' 30 - import {makeProfileLink} from 'lib/routes/links' 31 - import {MAX_POST_LINES} from 'lib/constants' 32 25 import {countLines} from 'lib/strings/helpers' 33 - import {useComposerControls} from '#/state/shell/composer' 34 - import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' 26 + import {s} from 'lib/styles' 27 + import {atoms as a} from '#/alf' 28 + import {ContentHider} from '#/components/moderation/ContentHider' 29 + import {ProfileHoverCard} from '#/components/ProfileHoverCard' 30 + import {RichText} from '#/components/RichText' 31 + import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' 32 + import {PostAlerts} from '../../../components/moderation/PostAlerts' 35 33 import {FeedNameText} from '../util/FeedInfoText' 36 - import {Trans, msg} from '@lingui/macro' 37 - import {useLingui} from '@lingui/react' 38 - import {atoms as a} from '#/alf' 34 + import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link' 35 + import {PostCtrls} from '../util/post-ctrls/PostCtrls' 36 + import {PostEmbeds} from '../util/post-embeds' 37 + import {PostMeta} from '../util/PostMeta' 38 + import {Text} from '../util/text/Text' 39 + import {PreviewableUserAvatar} from '../util/UserAvatar' 40 + import {UserInfoText} from '../util/UserInfoText' 39 41 40 42 export function FeedItem({ 41 43 post, ··· 213 215 numberOfLines={1}> 214 216 <Trans> 215 217 Reposted by{' '} 216 - <TextLinkOnWebOnly 217 - type="sm-bold" 218 - style={pal.textLight} 219 - lineHeight={1.2} 220 - numberOfLines={1} 221 - text={sanitizeDisplayName( 222 - reason.by.displayName || sanitizeHandle(reason.by.handle), 223 - moderation.ui('displayName'), 224 - )} 225 - href={makeProfileLink(reason.by)} 226 - /> 218 + <ProfileHoverCard inline did={reason.by.did}> 219 + <TextLinkOnWebOnly 220 + type="sm-bold" 221 + style={pal.textLight} 222 + lineHeight={1.2} 223 + numberOfLines={1} 224 + text={sanitizeDisplayName( 225 + reason.by.displayName || 226 + sanitizeHandle(reason.by.handle), 227 + moderation.ui('displayName'), 228 + )} 229 + href={makeProfileLink(reason.by)} 230 + /> 231 + </ProfileHoverCard> 227 232 </Trans> 228 233 </Text> 229 234 </Link>