migrate people lists off actor-autocomplete

This commit is contained in:
vineyardbovines
2026-07-15 15:28:41 -04:00
parent b948c98b4e
commit eb01ad6e47
5 changed files with 47 additions and 139 deletions
@@ -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,
+13 -3
View File
@@ -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({
+13 -3
View File
@@ -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`
@@ -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
-126
View File
@@ -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<AppBskyActorDefs.ProfileViewBasic[]>({
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<typeof useActorAutocompleteFn>
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)
)
})
}