Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Add check to composer user autocomplete (#8253)

* Add check to composer autocomplete web

* Add check to composer autocomplete mobile

authored by

Eric Bailey and committed by
GitHub
8fdefb7e 4aaf81fe

+154 -84
+73 -45
src/view/com/composer/text-input/mobile/Autocomplete.tsx
··· 1 1 import {View} from 'react-native' 2 2 import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' 3 + import {type AppBskyActorDefs} from '@atproto/api' 3 4 import {Trans} from '@lingui/macro' 4 5 5 6 import {PressableScale} from '#/lib/custom-animations/PressableScale' ··· 9 10 import {UserAvatar} from '#/view/com/util/UserAvatar' 10 11 import {atoms as a, useTheme} from '#/alf' 11 12 import {Text} from '#/components/Typography' 13 + import {useSimpleVerificationState} from '#/components/verification' 14 + import {VerificationCheck} from '#/components/verification/VerificationCheck' 12 15 13 16 export function Autocomplete({ 14 17 prefix, ··· 42 45 {suggestions?.length ? ( 43 46 suggestions.slice(0, 5).map((item, index, arr) => { 44 47 return ( 45 - <View 46 - style={[ 47 - index !== arr.length - 1 && a.border_b, 48 - t.atoms.border_contrast_high, 49 - a.px_sm, 50 - a.py_md, 51 - ]} 52 - key={item.did}> 53 - <PressableScale 54 - testID="autocompleteButton" 55 - style={[ 56 - a.flex_row, 57 - a.gap_sm, 58 - a.justify_between, 59 - a.align_center, 60 - ]} 61 - onPress={() => onSelect(item.handle)} 62 - accessibilityLabel={`Select ${item.handle}`} 63 - accessibilityHint=""> 64 - <View style={[a.flex_row, a.gap_sm, a.align_center]}> 65 - <UserAvatar 66 - avatar={item.avatar ?? null} 67 - size={24} 68 - type={item.associated?.labeler ? 'labeler' : 'user'} 69 - /> 70 - <Text 71 - style={[a.flex_1, a.text_md, a.font_bold]} 72 - emoji 73 - numberOfLines={1}> 74 - {sanitizeDisplayName( 75 - item.displayName || sanitizeHandle(item.handle), 76 - )} 77 - </Text> 78 - <Text 79 - style={[ 80 - t.atoms.text_contrast_medium, 81 - a.text_right, 82 - {maxWidth: '50%'}, 83 - ]} 84 - numberOfLines={1}> 85 - {sanitizeHandle(item.handle, '@')} 86 - </Text> 87 - </View> 88 - </PressableScale> 89 - </View> 48 + <AutocompleteProfileCard 49 + key={item.did} 50 + profile={item} 51 + itemIndex={index} 52 + totalItems={arr.length} 53 + onPress={() => { 54 + onSelect(item.handle) 55 + }} 56 + /> 90 57 ) 91 58 }) 92 59 ) : ( ··· 97 64 </Animated.View> 98 65 ) 99 66 } 67 + 68 + function AutocompleteProfileCard({ 69 + profile, 70 + itemIndex, 71 + totalItems, 72 + onPress, 73 + }: { 74 + profile: AppBskyActorDefs.ProfileViewBasic 75 + itemIndex: number 76 + totalItems: number 77 + onPress: () => void 78 + }) { 79 + const t = useTheme() 80 + const state = useSimpleVerificationState({profile}) 81 + const displayName = sanitizeDisplayName( 82 + profile.displayName || sanitizeHandle(profile.handle), 83 + ) 84 + return ( 85 + <View 86 + style={[ 87 + itemIndex !== totalItems - 1 && a.border_b, 88 + t.atoms.border_contrast_high, 89 + a.px_sm, 90 + a.py_md, 91 + ]} 92 + key={profile.did}> 93 + <PressableScale 94 + testID="autocompleteButton" 95 + style={[a.flex_row, a.gap_lg, a.justify_between, a.align_center]} 96 + onPress={onPress} 97 + accessibilityLabel={`Select ${profile.handle}`} 98 + accessibilityHint=""> 99 + <View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_1]}> 100 + <UserAvatar 101 + avatar={profile.avatar ?? null} 102 + size={24} 103 + type={profile.associated?.labeler ? 'labeler' : 'user'} 104 + /> 105 + <View style={[a.flex_row, a.align_center, a.gap_xs, a.flex_1]}> 106 + <Text style={[a.text_md, a.font_bold]} emoji numberOfLines={1}> 107 + {displayName} 108 + </Text> 109 + {state.isVerified && ( 110 + <View> 111 + <VerificationCheck 112 + width={12} 113 + verifier={state.role === 'verifier'} 114 + /> 115 + </View> 116 + )} 117 + </View> 118 + </View> 119 + <Text 120 + style={[t.atoms.text_contrast_medium, a.text_right]} 121 + numberOfLines={1}> 122 + {sanitizeHandle(profile.handle, '@')} 123 + </Text> 124 + </PressableScale> 125 + </View> 126 + ) 127 + }
+81 -39
src/view/com/composer/text-input/web/Autocomplete.tsx
··· 1 1 import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' 2 2 import {Pressable, StyleSheet, View} from 'react-native' 3 + import {type AppBskyActorDefs} from '@atproto/api' 3 4 import {Trans} from '@lingui/macro' 4 5 import {ReactRenderer} from '@tiptap/react' 5 6 import { 6 - SuggestionKeyDownProps, 7 - SuggestionOptions, 8 - SuggestionProps, 7 + type SuggestionKeyDownProps, 8 + type SuggestionOptions, 9 + type SuggestionProps, 9 10 } from '@tiptap/suggestion' 10 - import tippy, {Instance as TippyInstance} from 'tippy.js' 11 + import tippy, {type Instance as TippyInstance} from 'tippy.js' 11 12 12 13 import {usePalette} from '#/lib/hooks/usePalette' 13 14 import {sanitizeDisplayName} from '#/lib/strings/display-names' 14 15 import {sanitizeHandle} from '#/lib/strings/handles' 15 - import {ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' 16 + import {type ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' 16 17 import {Text} from '#/view/com/util/text/Text' 17 18 import {UserAvatar} from '#/view/com/util/UserAvatar' 19 + import {atoms as a} from '#/alf' 20 + import {useSimpleVerificationState} from '#/components/verification' 21 + import {VerificationCheck} from '#/components/verification/VerificationCheck' 18 22 import {useGrapheme} from '../hooks/useGrapheme' 19 23 20 24 interface MentionListRef { ··· 95 99 function MentionListImpl(props: SuggestionProps, ref) { 96 100 const [selectedIndex, setSelectedIndex] = useState(0) 97 101 const pal = usePalette('default') 98 - const {getGraphemeString} = useGrapheme() 99 102 100 103 const selectItem = (index: number) => { 101 104 const item = props.items[index] ··· 149 152 <View style={[pal.borderDark, pal.view, styles.container]}> 150 153 {items.length > 0 ? ( 151 154 items.map((item, index) => { 152 - const {name: displayName} = getGraphemeString( 153 - sanitizeDisplayName( 154 - item.displayName || sanitizeHandle(item.handle), 155 - ), 156 - 30, // Heuristic value; can be modified 157 - ) 158 155 const isSelected = selectedIndex === index 159 156 160 157 return ( 161 - <Pressable 158 + <AutocompleteProfileCard 162 159 key={item.handle} 163 - style={[ 164 - isSelected ? pal.viewLight : undefined, 165 - pal.borderDark, 166 - styles.mentionContainer, 167 - index === 0 168 - ? styles.firstMention 169 - : index === items.length - 1 170 - ? styles.lastMention 171 - : undefined, 172 - ]} 160 + profile={item} 161 + isSelected={isSelected} 162 + itemIndex={index} 163 + totalItems={items.length} 173 164 onPress={() => { 174 165 selectItem(index) 175 166 }} 176 - accessibilityRole="button"> 177 - <View style={styles.avatarAndDisplayName}> 178 - <UserAvatar 179 - avatar={item.avatar ?? null} 180 - size={26} 181 - type={item.associated?.labeler ? 'labeler' : 'user'} 182 - /> 183 - <Text emoji style={pal.text} numberOfLines={1}> 184 - {displayName} 185 - </Text> 186 - </View> 187 - <Text type="xs" style={pal.textLight} numberOfLines={1}> 188 - {sanitizeHandle(item.handle, '@')} 189 - </Text> 190 - </Pressable> 167 + /> 191 168 ) 192 169 }) 193 170 ) : ( ··· 201 178 }, 202 179 ) 203 180 181 + function AutocompleteProfileCard({ 182 + profile, 183 + isSelected, 184 + itemIndex, 185 + totalItems, 186 + onPress, 187 + }: { 188 + profile: AppBskyActorDefs.ProfileViewBasic 189 + isSelected: boolean 190 + itemIndex: number 191 + totalItems: number 192 + onPress: () => void 193 + }) { 194 + const pal = usePalette('default') 195 + const {getGraphemeString} = useGrapheme() 196 + const {name: displayName} = getGraphemeString( 197 + sanitizeDisplayName(profile.displayName || sanitizeHandle(profile.handle)), 198 + 30, // Heuristic value; can be modified 199 + ) 200 + const state = useSimpleVerificationState({ 201 + profile, 202 + }) 203 + return ( 204 + <Pressable 205 + style={[ 206 + isSelected ? pal.viewLight : undefined, 207 + pal.borderDark, 208 + styles.mentionContainer, 209 + itemIndex === 0 210 + ? styles.firstMention 211 + : itemIndex === totalItems - 1 212 + ? styles.lastMention 213 + : undefined, 214 + ]} 215 + onPress={onPress} 216 + accessibilityRole="button"> 217 + <View style={[styles.avatarAndDisplayName, a.flex_1]}> 218 + <UserAvatar 219 + avatar={profile.avatar ?? null} 220 + size={26} 221 + type={profile.associated?.labeler ? 'labeler' : 'user'} 222 + /> 223 + <View style={[a.flex_row, a.align_center, a.gap_xs, a.flex_1]}> 224 + <Text emoji style={[pal.text]} numberOfLines={1}> 225 + {displayName} 226 + </Text> 227 + {state.isVerified && ( 228 + <View> 229 + <VerificationCheck 230 + width={12} 231 + verifier={state.role === 'verifier'} 232 + /> 233 + </View> 234 + )} 235 + </View> 236 + </View> 237 + <View> 238 + <Text type="xs" style={pal.textLight} numberOfLines={1}> 239 + {sanitizeHandle(profile.handle, '@')} 240 + </Text> 241 + </View> 242 + </Pressable> 243 + ) 244 + } 245 + 204 246 const styles = StyleSheet.create({ 205 247 container: { 206 248 width: 500, ··· 216 258 flexDirection: 'row', 217 259 paddingHorizontal: 12, 218 260 paddingVertical: 8, 219 - gap: 4, 261 + gap: 16, 220 262 }, 221 263 firstMention: { 222 264 borderTopLeftRadius: 2,