migrate post composer to unified autocomplete
This commit is contained in:
@@ -29,7 +29,6 @@ import {splitGraphemes} from 'unicode-segmenter/grapheme'
|
|||||||
|
|
||||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||||
import {blobToDataUri, isUriImage} from '#/lib/media/util'
|
import {blobToDataUri, isUriImage} from '#/lib/media/util'
|
||||||
import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
|
|
||||||
import {
|
import {
|
||||||
type LinkFacetMatch,
|
type LinkFacetMatch,
|
||||||
suggestLinkCardUri,
|
suggestLinkCardUri,
|
||||||
@@ -37,9 +36,11 @@ import {
|
|||||||
import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter'
|
import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter'
|
||||||
import {atoms as a, useAlf} from '#/alf'
|
import {atoms as a, useAlf} from '#/alf'
|
||||||
import {normalizeTextStyles} from '#/alf/typography'
|
import {normalizeTextStyles} from '#/alf/typography'
|
||||||
|
import {useAutocompleteFn} from '#/components/Autocomplete'
|
||||||
import {type Emoji} from '#/components/EmojiPicker'
|
import {type Emoji} from '#/components/EmojiPicker'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useFollowsSource} from '#/features/autocomplete/useFollowsSource'
|
||||||
import {type TextInputProps} from './TextInput.types'
|
import {type TextInputProps} from './TextInput.types'
|
||||||
import {type AutocompleteRef, createSuggestion} from './web/Autocomplete'
|
import {type AutocompleteRef, createSuggestion} from './web/Autocomplete'
|
||||||
import {LinkDecorator} from './web/LinkDecorator'
|
import {LinkDecorator} from './web/LinkDecorator'
|
||||||
@@ -60,7 +61,24 @@ export function TextInput({
|
|||||||
autoFocus,
|
autoFocus,
|
||||||
}: TextInputProps) {
|
}: TextInputProps) {
|
||||||
const {theme: t, fonts} = useAlf()
|
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 modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark')
|
||||||
|
|
||||||
const [isDropping, setIsDropping] = useState(false)
|
const [isDropping, setIsDropping] = useState(false)
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated'
|
import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated'
|
||||||
import {type AppBskyActorDefs} from '@atproto/api'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, platform, useTheme} from '#/alf'
|
import {atoms as a, platform, useTheme} from '#/alf'
|
||||||
|
import {useAutocomplete} from '#/components/Autocomplete'
|
||||||
import {ProfileBadges} from '#/components/ProfileBadges'
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useFollowsSource} from '#/features/autocomplete/useFollowsSource'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export function Autocomplete({
|
export function Autocomplete({
|
||||||
prefix,
|
prefix,
|
||||||
@@ -22,10 +23,15 @@ export function Autocomplete({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
const isActive = !!prefix
|
const isActive = !!prefix
|
||||||
const {data: suggestions, isFetching} = useActorAutocompleteQuery(
|
const follows = useFollowsSource()
|
||||||
prefix,
|
const {items, isFetching} = useAutocomplete({
|
||||||
true,
|
type: 'profile',
|
||||||
)
|
query: prefix,
|
||||||
|
sources: isActive ? [follows] : undefined,
|
||||||
|
})
|
||||||
|
const suggestions = items
|
||||||
|
.filter(item => item.type === 'profile')
|
||||||
|
.map(item => item.profile)
|
||||||
|
|
||||||
if (!isActive) return null
|
if (!isActive) return null
|
||||||
|
|
||||||
@@ -41,7 +47,7 @@ export function Autocomplete({
|
|||||||
t.atoms.border_contrast_high,
|
t.atoms.border_contrast_high,
|
||||||
{marginLeft: -62},
|
{marginLeft: -62},
|
||||||
]}>
|
]}>
|
||||||
{suggestions?.length ? (
|
{suggestions.length ? (
|
||||||
suggestions.slice(0, 5).map((item, index, arr) => {
|
suggestions.slice(0, 5).map((item, index, arr) => {
|
||||||
return (
|
return (
|
||||||
<AutocompleteProfileCard
|
<AutocompleteProfileCard
|
||||||
@@ -70,7 +76,7 @@ function AutocompleteProfileCard({
|
|||||||
totalItems,
|
totalItems,
|
||||||
onPress,
|
onPress,
|
||||||
}: {
|
}: {
|
||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
profile: bsky.profile.AnyProfileView
|
||||||
itemIndex: number
|
itemIndex: number
|
||||||
totalItems: number
|
totalItems: number
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {forwardRef, useEffect, useImperativeHandle, useState} from 'react'
|
import {forwardRef, useEffect, useImperativeHandle, useState} from 'react'
|
||||||
import {Pressable, View} from 'react-native'
|
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 {Trans} from '@lingui/react/macro'
|
||||||
import {ReactRenderer} from '@tiptap/react'
|
import {ReactRenderer} from '@tiptap/react'
|
||||||
import {
|
import {
|
||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
import tippy, {type Instance as TippyInstance} from 'tippy.js'
|
import tippy, {type Instance as TippyInstance} from 'tippy.js'
|
||||||
|
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {type ActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
interface MentionListRef {
|
interface MentionListRef {
|
||||||
onKeyDown: (props: SuggestionKeyDownProps) => boolean
|
onKeyDown: (props: SuggestionKeyDownProps) => boolean
|
||||||
@@ -24,11 +24,19 @@ export interface AutocompleteRef {
|
|||||||
maybeClose: () => boolean
|
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({
|
export function createSuggestion({
|
||||||
autocomplete,
|
autocomplete,
|
||||||
autocompleteRef,
|
autocompleteRef,
|
||||||
}: {
|
}: {
|
||||||
autocomplete: ActorAutocompleteFn
|
autocomplete: MentionAutocompleteFn
|
||||||
autocompleteRef: React.Ref<AutocompleteRef>
|
autocompleteRef: React.Ref<AutocompleteRef>
|
||||||
}): Omit<SuggestionOptions, 'editor'> {
|
}): Omit<SuggestionOptions, 'editor'> {
|
||||||
return {
|
return {
|
||||||
@@ -204,7 +212,7 @@ function AutocompleteProfileCard({
|
|||||||
onHover,
|
onHover,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
}: {
|
}: {
|
||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
profile: bsky.profile.AnyProfileView
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
onHover: () => void
|
onHover: () => void
|
||||||
|
|||||||
Reference in New Issue
Block a user