diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index e76d9e3f3d..98b2e7ca7b 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -29,7 +29,6 @@ import {splitGraphemes} from 'unicode-segmenter/grapheme' import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' import {blobToDataUri, isUriImage} from '#/lib/media/util' -import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import { type LinkFacetMatch, suggestLinkCardUri, @@ -37,9 +36,11 @@ import { import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import {atoms as a, useAlf} from '#/alf' import {normalizeTextStyles} from '#/alf/typography' +import {useAutocompleteFn} from '#/components/Autocomplete' import {type Emoji} from '#/components/EmojiPicker' import {Portal} from '#/components/Portal' import {Text} from '#/components/Typography' +import {useFollowsSource} from '#/features/autocomplete/useFollowsSource' import {type TextInputProps} from './TextInput.types' import {type AutocompleteRef, createSuggestion} from './web/Autocomplete' import {LinkDecorator} from './web/LinkDecorator' @@ -60,7 +61,24 @@ export function TextInput({ autoFocus, }: TextInputProps) { const {theme: t, fonts} = useAlf() - const autocomplete = useActorAutocompleteFn() + const follows = useFollowsSource() + const autocompleteFn = useAutocompleteFn({ + type: 'profile', + sources: [follows], + }) + /* + * Adapter for the tiptap mention plugin, which renders plain profile views. + * useCallback keeps the extensions useMemo below stable. + */ + const autocomplete = useCallback( + async ({query}: {query: string}) => { + const items = await autocompleteFn(query, 8) + return items + .filter(item => item.type === 'profile') + .map(item => item.profile) + }, + [autocompleteFn], + ) const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark') const [isDropping, setIsDropping] = useState(false) diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index 9621ebd82c..f4ad474d8d 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -1,16 +1,17 @@ import {View} from 'react-native' import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' -import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, platform, useTheme} from '#/alf' +import {useAutocomplete} from '#/components/Autocomplete' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {useFollowsSource} from '#/features/autocomplete/useFollowsSource' +import type * as bsky from '#/types/bsky' export function Autocomplete({ prefix, @@ -22,10 +23,15 @@ export function Autocomplete({ const t = useTheme() const isActive = !!prefix - const {data: suggestions, isFetching} = useActorAutocompleteQuery( - prefix, - true, - ) + const follows = useFollowsSource() + const {items, isFetching} = useAutocomplete({ + type: 'profile', + query: prefix, + sources: isActive ? [follows] : undefined, + }) + const suggestions = items + .filter(item => item.type === 'profile') + .map(item => item.profile) if (!isActive) return null @@ -41,7 +47,7 @@ export function Autocomplete({ t.atoms.border_contrast_high, {marginLeft: -62}, ]}> - {suggestions?.length ? ( + {suggestions.length ? ( suggestions.slice(0, 5).map((item, index, arr) => { return ( void diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 8371ad993a..40be4be117 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,6 +1,6 @@ import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {ReactRenderer} from '@tiptap/react' import { @@ -11,10 +11,10 @@ import { import tippy, {type Instance as TippyInstance} from 'tippy.js' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {type ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {atoms as a, useTheme} from '#/alf' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import type * as bsky from '#/types/bsky' interface MentionListRef { onKeyDown: (props: SuggestionKeyDownProps) => boolean @@ -24,11 +24,19 @@ export interface AutocompleteRef { maybeClose: () => boolean } +/** + * Adapter type for the tiptap mention suggestion plugin, which expects a + * function returning plain profile views rather than `AutocompleteItem`s. + */ +export type MentionAutocompleteFn = (opts: { + query: string +}) => Promise + export function createSuggestion({ autocomplete, autocompleteRef, }: { - autocomplete: ActorAutocompleteFn + autocomplete: MentionAutocompleteFn autocompleteRef: React.Ref }): Omit { return { @@ -204,7 +212,7 @@ function AutocompleteProfileCard({ onHover, moderationOpts, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: bsky.profile.AnyProfileView isSelected: boolean onPress: () => void onHover: () => void