add imperative useAutocompleteFn
This commit is contained in:
@@ -129,3 +129,5 @@ export function useAutocomplete({
|
||||
isError: query.isError,
|
||||
}
|
||||
}
|
||||
|
||||
export * from './useAutocompleteFn'
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import {useCallback} from 'react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {
|
||||
type AutocompleteItem,
|
||||
type AutocompleteItemType,
|
||||
type LocalSource,
|
||||
} from '#/components/Autocomplete/types'
|
||||
import {mergeAutocompleteResults} from './mergeAutocompleteResults'
|
||||
import {DEFAULT_MOD_OPTS, moderateProfileItem} from './moderation'
|
||||
import {useEmojiSearch} from './useEmojiSearch'
|
||||
|
||||
/**
|
||||
* Imperative variant of useAutocomplete for callers that cannot re-render
|
||||
* per keystroke (e.g. the tiptap mention suggestion plugin). Shares the
|
||||
* same query cache, merge, and moderation pipeline.
|
||||
*/
|
||||
export function useAutocompleteFn({
|
||||
type,
|
||||
sources,
|
||||
}: {
|
||||
type: AutocompleteItemType
|
||||
sources?: LocalSource[]
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const emojiSearch = useEmojiSearch()
|
||||
|
||||
return useCallback(
|
||||
async (query: string, limit: number = 8): Promise<AutocompleteItem[]> => {
|
||||
let remoteItems: AutocompleteItem[] = []
|
||||
|
||||
if (type === 'profile' && query) {
|
||||
try {
|
||||
remoteItems = await queryClient.fetchQuery({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: ['autocomplete', {type, query}],
|
||||
async queryFn() {
|
||||
// Going from "foo" to "foo." should not clear matches.
|
||||
const q = query.toLowerCase().trim().replace(/\.$/, '')
|
||||
const res = await agent.searchActorsTypeahead({q, limit})
|
||||
return (res?.data.actors || []).map(profile => ({
|
||||
key: profile.did,
|
||||
type: 'profile' as const,
|
||||
value: '@' + profile.handle,
|
||||
profile,
|
||||
}))
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
logger.error('useAutocompleteFn: searchActorsTypeahead failed', {
|
||||
message: e,
|
||||
})
|
||||
}
|
||||
} else if (type === 'emoji' && query) {
|
||||
remoteItems = await emojiSearch(query, limit)
|
||||
}
|
||||
|
||||
// Match the hook's select: dedupe remote items by key.
|
||||
const seen = new Set<string>()
|
||||
remoteItems = remoteItems.filter(item => {
|
||||
if (seen.has(item.key)) return false
|
||||
seen.add(item.key)
|
||||
return true
|
||||
})
|
||||
|
||||
const opts = moderationOpts || DEFAULT_MOD_OPTS
|
||||
const moderate = (items: AutocompleteItem[]) =>
|
||||
items.filter(item => {
|
||||
if (item.type !== 'profile') return true
|
||||
return !!moderateProfileItem({query, item, moderationOpts: opts})
|
||||
})
|
||||
|
||||
const moderatedSources = sources?.map(source => ({
|
||||
...source,
|
||||
items: moderate(source.items),
|
||||
}))
|
||||
|
||||
const results = mergeAutocompleteResults({
|
||||
query,
|
||||
sources: moderatedSources,
|
||||
remoteItems: moderate(remoteItems),
|
||||
})
|
||||
|
||||
return results.slice(0, limit)
|
||||
},
|
||||
[type, sources, queryClient, agent, moderationOpts, emojiSearch],
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user