import React, { KeyboardEvent as ReactKeyboardEvent, useEffect, useMemo } from 'react'; import { Editor } from 'slate'; import { Box, MenuItem, Text, toRem } from 'folds'; import { Room } from 'matrix-js-sdk'; import { AutocompleteQuery } from './autocompleteQuery'; import { AutocompleteMenu } from './AutocompleteMenu'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { SearchItemStrGetter, UseAsyncSearchOptions, useAsyncSearch, } from '../../../hooks/useAsyncSearch'; import { onTabPress } from '../../../utils/keyboard'; import { createEmoticonElement, moveCursor, replaceWithElement } from '../common'; import { useRecentEmoji } from '../../../hooks/useRecentEmoji'; import { useRelevantImagePacks } from '../../../hooks/useImagePacks'; import { IEmoji, emojis } from '../../../plugins/emoji'; import { ExtendedPackImage, PackUsage } from '../../../plugins/custom-emoji'; import { useKeyDown } from '../../../hooks/useKeyDown'; type EmoticonCompleteHandler = (key: string, shortcode: string) => void; type EmoticonSearchItem = ExtendedPackImage | IEmoji; type EmoticonAutocompleteProps = { imagePackRooms: Room[]; editor: Editor; query: AutocompleteQuery; requestClose: () => void; }; const SEARCH_OPTIONS: UseAsyncSearchOptions = { limit: 20, matchOptions: { contain: true, }, }; const getEmoticonStr: SearchItemStrGetter = (emoticon) => [ `:${emoticon.shortcode}:`, ]; export function EmoticonAutocomplete({ imagePackRooms, editor, query, requestClose, }: EmoticonAutocompleteProps) { const mx = useMatrixClient(); const imagePacks = useRelevantImagePacks(mx, PackUsage.Emoticon, imagePackRooms); const recentEmoji = useRecentEmoji(mx, 20); const searchList = useMemo(() => { const list: Array = []; return list.concat( imagePacks.flatMap((pack) => pack.getImagesFor(PackUsage.Emoticon)), emojis ); }, [imagePacks]); const [result, search] = useAsyncSearch(searchList, getEmoticonStr, SEARCH_OPTIONS); const autoCompleteEmoticon = result ? result.items : recentEmoji; useEffect(() => { search(query.text); }, [query.text, search]); const handleAutocomplete: EmoticonCompleteHandler = (key, shortcode) => { const emoticonEl = createEmoticonElement(key, shortcode); replaceWithElement(editor, query.range, emoticonEl); moveCursor(editor, true); requestClose(); }; useKeyDown(window, (evt: KeyboardEvent) => { onTabPress(evt, () => { if (autoCompleteEmoticon.length === 0) return; const emoticon = autoCompleteEmoticon[0]; const key = 'url' in emoticon ? emoticon.url : emoticon.unicode; handleAutocomplete(key, emoticon.shortcode); }); }); return autoCompleteEmoticon.length === 0 ? null : ( Emojis} requestClose={requestClose}> {autoCompleteEmoticon.map((emoticon) => { const isCustomEmoji = 'url' in emoticon; const key = isCustomEmoji ? emoticon.url : emoticon.unicode; return ( ) => onTabPress(evt, () => handleAutocomplete(key, emoticon.shortcode)) } onClick={() => handleAutocomplete(key, emoticon.shortcode)} before={ isCustomEmoji ? ( ) : ( {key} ) } > :{emoticon.shortcode}: ); })} ); }