From eb01ad6e479a6b0bda337db6ea83fd3fccf41042 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Wed, 15 Jul 2026 15:28:41 -0400 Subject: [PATCH] migrate people lists off actor-autocomplete --- .../dialogs/SearchablePeopleList.tsx | 13 +- src/components/dms/AddMembersFlow.tsx | 16 ++- src/components/dms/InitiateChatFlow.tsx | 16 ++- .../StarterPack/Wizard/StepProfiles.tsx | 15 ++- src/state/queries/actor-autocomplete.ts | 126 ------------------ 5 files changed, 47 insertions(+), 139 deletions(-) delete mode 100644 src/state/queries/actor-autocomplete.ts diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index cf106684a4..fe2c24cf3d 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -13,12 +13,12 @@ import {Plural, Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useListConvosQuery} from '#/state/queries/messages/list-conversations' import {useProfileFollowsQuery} from '#/state/queries/profile-follows' import {useSession} from '#/state/session' import {type ListMethods} from '#/view/com/util/List' import {android, atoms as a, native, useTheme, web} from '#/alf' +import {useAutocomplete} from '#/components/Autocomplete' import {Button, ButtonIcon} from '#/components/Button' import * as Dialog from '#/components/Dialog' import { @@ -106,10 +106,17 @@ export function SearchablePeopleList({ const [searchText, setSearchText] = useState('') const { - data: results, + items: autocompleteItems, isError, isFetching, - } = useActorAutocompleteQuery(searchText, true, 12) + } = useAutocomplete({ + type: 'profile', + query: searchText, + limit: 12, + }) + const results = autocompleteItems + .filter(item => item.type === 'profile') + .map(item => item.profile) const {data: follows} = useProfileFollowsQuery(currentAccount?.did) const {data: convos} = useListConvosQuery({ enabled: showRecentConvos, diff --git a/src/components/dms/AddMembersFlow.tsx b/src/components/dms/AddMembersFlow.tsx index bb591fb8c6..0f2f86d9df 100644 --- a/src/components/dms/AddMembersFlow.tsx +++ b/src/components/dms/AddMembersFlow.tsx @@ -10,12 +10,12 @@ import {LayoutAnimation, type TextInput, View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members' import {useProfileFollowsQuery} from '#/state/queries/profile-follows' import {useSession} from '#/state/session' import {type ListMethods} from '#/view/com/util/List' import {android, atoms as a, native, useTheme, web} from '#/alf' +import {useAutocomplete} from '#/components/Autocomplete' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {canBeAddedToGroup, type ConvoWithDetails} from '#/components/dms/util' @@ -25,6 +25,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Ti import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_NATIVE, IS_WEB} from '#/env' +import {useFollowsSource} from '#/features/autocomplete/useFollowsSource' import type * as bsky from '#/types/bsky' import {ChatProfileTabs} from './ChatProfileTabs' import {EmptyMemberList} from './components/EmptyMemberList' @@ -145,11 +146,20 @@ export function AddMembersFlow({ }, ) + const followsSource = useFollowsSource() const { - data: autocompleteResults, + items: autocompleteItems, isError, isFetching: isAutocompleteFetching, - } = useActorAutocompleteQuery(searchText, true, 12) + } = useAutocomplete({ + type: 'profile', + query: searchText, + limit: 12, + sources: [followsSource], + }) + const autocompleteResults = autocompleteItems + .filter(item => item.type === 'profile') + .map(item => item.profile) const {data: follows} = useProfileFollowsQuery(currentAccount?.did) const {data: memberListData = [], isPending: isMemberListPending} = useListConvoMembersQuery({ diff --git a/src/components/dms/InitiateChatFlow.tsx b/src/components/dms/InitiateChatFlow.tsx index ed4b9bc825..082465a890 100644 --- a/src/components/dms/InitiateChatFlow.tsx +++ b/src/components/dms/InitiateChatFlow.tsx @@ -15,12 +15,12 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useChatActorStatusQuery} from '#/state/queries/messages/get-status' import {useProfileFollowsQuery} from '#/state/queries/profile-follows' import {useSession} from '#/state/session' import {type ListMethods} from '#/view/com/util/List' import {android, atoms as a, native, useTheme, web} from '#/alf' +import {useAutocomplete} from '#/components/Autocomplete' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {canBeAddedToGroup, canBeMessaged} from '#/components/dms/util' @@ -38,6 +38,7 @@ import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {IS_NATIVE, IS_WEB} from '#/env' +import {useFollowsSource} from '#/features/autocomplete/useFollowsSource' import type * as bsky from '#/types/bsky' import {ChatProfileTabs} from './ChatProfileTabs' import {EmptyMemberList} from './components/EmptyMemberList' @@ -253,11 +254,20 @@ export function InitiateChatFlow({ searchText: '', }) + const followsSource = useFollowsSource() const { - data: results, + items: autocompleteItems, isError, isFetching, - } = useActorAutocompleteQuery(searchText, true, 12) + } = useAutocomplete({ + type: 'profile', + query: searchText, + limit: 12, + sources: [followsSource], + }) + const results = autocompleteItems + .filter(item => item.type === 'profile') + .map(item => item.profile) const {data: follows} = useProfileFollowsQuery(currentAccount?.did) const newGroupChatTitle = l`New group chat` diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx index 4aef058b27..ffeafd07d3 100644 --- a/src/screens/StarterPack/Wizard/StepProfiles.tsx +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -5,11 +5,11 @@ import {type ModerationOpts} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {useA11y} from '#/state/a11y' -import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useActorSearch} from '#/state/queries/actor-search' import {List} from '#/view/com/util/List' import {useWizardState} from '#/screens/StarterPack/Wizard/State' import {atoms as a, useTheme} from '#/alf' +import {useAutocomplete} from '#/components/Autocomplete' import {SearchInput} from '#/components/forms/SearchInput' import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/ScreenTransition' @@ -43,9 +43,16 @@ export function StepProfiles({ .flatMap(p => p.actors) .filter(p => !p.associated?.labeler) - const {data: resultsUnfiltered, isFetching: isFetchingResults} = - useActorAutocompleteQuery(query, true, 12) - const results = resultsUnfiltered?.filter(p => !p.associated?.labeler) + const {items: autocompleteItems, isFetching: isFetchingResults} = + useAutocomplete({ + type: 'profile', + query, + limit: 12, + }) + const results = autocompleteItems + .filter(item => item.type === 'profile') + .map(item => item.profile) + .filter(profile => !profile.associated?.labeler) const isLoading = isLoadingTopPages || isFetchingResults diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts deleted file mode 100644 index c8acb39f96..0000000000 --- a/src/state/queries/actor-autocomplete.ts +++ /dev/null @@ -1,126 +0,0 @@ -import {useCallback} from 'react' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' -import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query' - -import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation' -import {logger} from '#/logger' -import {STALE} from '#/state/queries' -import {useAgent} from '#/state/session' -import {useModerationOpts} from '../preferences/moderation-opts' -import {DEFAULT_LOGGED_OUT_PREFERENCES} from './preferences' - -const DEFAULT_MOD_OPTS = { - userDid: undefined, - prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs, -} - -const RQKEY_ROOT = 'actor-autocomplete' -export const RQKEY = (prefix: string) => [RQKEY_ROOT, prefix] - -export function useActorAutocompleteQuery( - prefix: string, - maintainData?: boolean, - limit?: number, -) { - const moderationOpts = useModerationOpts() - const agent = useAgent() - - prefix = prefix.toLowerCase().trim() - if (prefix.endsWith('.')) { - // Going from "foo" to "foo." should not clear matches. - prefix = prefix.slice(0, -1) - } - - return useQuery({ - staleTime: STALE.MINUTES.ONE, - queryKey: RQKEY(prefix || ''), - async queryFn() { - const res = prefix - ? await agent.searchActorsTypeahead({ - q: prefix, - limit: limit || 8, - }) - : undefined - return res?.data.actors || [] - }, - select: useCallback( - (data: AppBskyActorDefs.ProfileViewBasic[]) => { - return computeSuggestions({ - q: prefix, - searched: data, - moderationOpts: moderationOpts || DEFAULT_MOD_OPTS, - }) - }, - [prefix, moderationOpts], - ), - placeholderData: maintainData ? keepPreviousData : undefined, - }) -} - -export type ActorAutocompleteFn = ReturnType -export function useActorAutocompleteFn() { - const queryClient = useQueryClient() - const moderationOpts = useModerationOpts() - const agent = useAgent() - - return useCallback( - async ({query, limit = 8}: {query: string; limit?: number}) => { - query = query.toLowerCase() - let res - if (query) { - try { - res = await queryClient.fetchQuery({ - staleTime: STALE.MINUTES.ONE, - queryKey: RQKEY(query || ''), - queryFn: () => - agent.searchActorsTypeahead({ - q: query, - limit, - }), - }) - } catch (e) { - logger.error('useActorSearch: searchActorsTypeahead failed', { - message: e, - }) - } - } - - return computeSuggestions({ - q: query, - searched: res?.data.actors, - moderationOpts: moderationOpts || DEFAULT_MOD_OPTS, - }) - }, - [queryClient, moderationOpts, agent], - ) -} - -function computeSuggestions({ - q, - searched = [], - moderationOpts, -}: { - q?: string - searched?: AppBskyActorDefs.ProfileViewBasic[] - moderationOpts: ModerationOpts -}) { - let items: AppBskyActorDefs.ProfileViewBasic[] = [] - for (const item of searched) { - if (!items.find(item2 => item2.handle === item.handle)) { - items.push(item) - } - } - return items.filter(profile => { - const modui = moderateProfile(profile, moderationOpts).ui('profileList') - const isExactMatch = q && profile.handle.toLowerCase() === q - return ( - (isExactMatch && !moduiContainsHideableOffense(modui)) || - !modui.filter || - isJustAMute(modui) - ) - }) -}