migrate post composer to unified autocomplete

This commit is contained in:
vineyardbovines
2026-07-14 19:58:18 -04:00
parent e740f60fc8
commit 19aef62cd5
3 changed files with 46 additions and 14 deletions
@@ -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)
@@ -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 (
<AutocompleteProfileCard
@@ -70,7 +76,7 @@ function AutocompleteProfileCard({
totalItems,
onPress,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
profile: bsky.profile.AnyProfileView
itemIndex: number
totalItems: number
onPress: () => void
@@ -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<bsky.profile.AnyProfileView[]>
export function createSuggestion({
autocomplete,
autocompleteRef,
}: {
autocomplete: ActorAutocompleteFn
autocomplete: MentionAutocompleteFn
autocompleteRef: React.Ref<AutocompleteRef>
}): Omit<SuggestionOptions, 'editor'> {
return {
@@ -204,7 +212,7 @@ function AutocompleteProfileCard({
onHover,
moderationOpts,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
profile: bsky.profile.AnyProfileView
isSelected: boolean
onPress: () => void
onHover: () => void