Emoji Tooltip: Various fixes (#2688)

This commit is contained in:
Alexander Zinchuk 2023-02-28 18:44:00 +01:00
parent 92c1b0b281
commit 7c7e0c1d69
8 changed files with 45 additions and 14 deletions

View File

@ -2,6 +2,7 @@ import React, { memo, useCallback } from '../../../lib/teact/teact';
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import type { ApiSticker } from '../../../api/types'; import type { ApiSticker } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -15,10 +16,11 @@ type OwnProps = {
emoji: ApiSticker; emoji: ApiSticker;
focus?: boolean; focus?: boolean;
onClick?: (emoji: ApiSticker) => void; onClick?: (emoji: ApiSticker) => void;
observeIntersection?: ObserveFn;
}; };
const CustomEmojiButton: FC<OwnProps> = ({ const CustomEmojiButton: FC<OwnProps> = ({
emoji, focus, onClick, emoji, focus, onClick, observeIntersection,
}) => { }) => {
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
// Preventing safari from losing focus on Composer MessageInput // Preventing safari from losing focus on Composer MessageInput
@ -38,7 +40,13 @@ const CustomEmojiButton: FC<OwnProps> = ({
onMouseDown={handleClick} onMouseDown={handleClick}
title={emoji.emoji} title={emoji.emoji}
> >
<CustomEmoji documentId={emoji.id} size={CUSTOM_EMOJI_SIZE} withSharedAnimation shouldPreloadPreview /> <CustomEmoji
documentId={emoji.id}
size={CUSTOM_EMOJI_SIZE}
withSharedAnimation
shouldPreloadPreview
observeIntersectionForPlaying={observeIntersection}
/>
</div> </div>
); );
}; };

View File

@ -10,7 +10,7 @@
} }
.emojiButton { .emojiButton {
flex: 0 0 2.5rem; flex: 0 0 2rem;
--custom-emoji-size: 2rem; --custom-emoji-size: 2rem;
margin: 0.5rem 0 0.5rem 0.25rem; margin: 0.5rem 0 0.5rem 0.25rem;
} }

View File

@ -59,7 +59,7 @@ const CustomEmojiTooltip: FC<OwnProps & StateProps> = ({
const { const {
observe: observeIntersection, observe: observeIntersection,
} = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE }); } = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
useEffect(() => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]); useEffect(() => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]);

View File

@ -14,6 +14,7 @@ import useShowTransition from '../../../hooks/useShowTransition';
import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation'; import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import EmojiButton from './EmojiButton'; import EmojiButton from './EmojiButton';
@ -64,6 +65,8 @@ export type OwnProps = {
addRecentCustomEmoji: ({ documentId }: { documentId: string }) => void; addRecentCustomEmoji: ({ documentId }: { documentId: string }) => void;
}; };
const INTERSECTION_THROTTLE = 200;
const EmojiTooltip: FC<OwnProps> = ({ const EmojiTooltip: FC<OwnProps> = ({
isOpen, isOpen,
emojis, emojis,
@ -83,6 +86,10 @@ const EmojiTooltip: FC<OwnProps> = ({
useHorizontalScroll(containerRef); useHorizontalScroll(containerRef);
const {
observe: observeIntersection,
} = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
const handleSelectEmoji = useCallback((emoji: Emoji) => { const handleSelectEmoji = useCallback((emoji: Emoji) => {
onEmojiSelect(emoji.native); onEmojiSelect(emoji.native);
addRecentEmoji({ emoji: emoji.id }); addRecentEmoji({ emoji: emoji.id });
@ -148,6 +155,7 @@ const EmojiTooltip: FC<OwnProps> = ({
emoji={emoji} emoji={emoji}
focus={selectedIndex === index} focus={selectedIndex === index}
onClick={handleCustomEmojiClick} onClick={handleCustomEmojiClick}
observeIntersection={observeIntersection}
/> />
) )
)) ))

View File

@ -27,6 +27,7 @@ interface Library {
byKeyword: Record<string, Emoji[]>; byKeyword: Record<string, Emoji[]>;
names: string[]; names: string[];
byName: Record<string, Emoji[]>; byName: Record<string, Emoji[]>;
maxKeyLength: number;
} }
let emojiDataPromise: Promise<EmojiModule>; let emojiDataPromise: Promise<EmojiModule>;
@ -34,6 +35,7 @@ let emojiRawData: EmojiRawData;
let emojiData: EmojiData; let emojiData: EmojiData;
let RE_EMOJI_SEARCH: RegExp; let RE_EMOJI_SEARCH: RegExp;
let RE_LOWERCASE_TEST: RegExp;
const EMOJIS_LIMIT = 36; const EMOJIS_LIMIT = 36;
const FILTER_MIN_LENGTH = 2; const FILTER_MIN_LENGTH = 2;
@ -44,10 +46,12 @@ const prepareLibraryMemo = memoized(prepareLibrary);
const searchInLibraryMemo = memoized(searchInLibrary); const searchInLibraryMemo = memoized(searchInLibrary);
try { try {
RE_EMOJI_SEARCH = /(^|\s):[-+_:\p{L}\p{N}]*$/gui; RE_EMOJI_SEARCH = /(^|\s):(?!\s)[-+_:'\s\p{L}\p{N}]*$/gui;
RE_LOWERCASE_TEST = /\p{Ll}/u;
} catch (e) { } catch (e) {
// Support for older versions of firefox // Support for older versions of firefox
RE_EMOJI_SEARCH = /(^|\s):[-+_:\d\wа-яё]*$/gi; RE_EMOJI_SEARCH = /(^|\s):(?!\s)[-+_:'\s\d\wа-яёґєії]*$/gi;
RE_LOWERCASE_TEST = /[a-zяёґєії]/;
} }
export default function useEmojiTooltip( export default function useEmojiTooltip(
@ -141,12 +145,13 @@ export default function useEmojiTooltip(
if (!filter) { if (!filter) {
matched = prepareRecentEmojisMemo(byId, recentEmojiIds, EMOJIS_LIMIT); matched = prepareRecentEmojisMemo(byId, recentEmojiIds, EMOJIS_LIMIT);
} else if (filter.length >= FILTER_MIN_LENGTH) { } else if ((filter.length === 1 && RE_LOWERCASE_TEST.test(filter)) || filter.length >= FILTER_MIN_LENGTH) {
const library = prepareLibraryMemo(byId, baseEmojiKeywords, emojiKeywords); const library = prepareLibraryMemo(byId, baseEmojiKeywords, emojiKeywords);
matched = searchInLibraryMemo(library, filter, EMOJIS_LIMIT); matched = searchInLibraryMemo(library, filter.toLowerCase(), EMOJIS_LIMIT);
} }
if (!matched.length) { if (!matched.length) {
updateFiltered(MEMO_EMPTY_ARRAY);
return; return;
} }
@ -172,7 +177,7 @@ export default function useEmojiTooltip(
async function ensureEmojiData() { async function ensureEmojiData() {
if (!emojiDataPromise) { if (!emojiDataPromise) {
emojiDataPromise = import('emoji-data-ios/emoji-data.json') as unknown as Promise<EmojiModule>; emojiDataPromise = import('emoji-data-ios/emoji-data.json');
emojiRawData = (await emojiDataPromise).default; emojiRawData = (await emojiDataPromise).default;
emojiData = uncompressEmoji(emojiRawData); emojiData = uncompressEmoji(emojiRawData);
@ -224,21 +229,27 @@ function prepareLibrary(
}, {} as Record<string, Emoji[]>); }, {} as Record<string, Emoji[]>);
const names = Object.keys(byName); const names = Object.keys(byName);
const maxKeyLength = keywords.reduce((max, keyword) => Math.max(max, keyword.length), 0);
return { return {
byKeyword, byKeyword,
keywords, keywords,
byName, byName,
names, names,
maxKeyLength,
}; };
} }
function searchInLibrary(library: Library, filter: string, limit: number) { function searchInLibrary(library: Library, filter: string, limit: number) {
const { const {
byKeyword, keywords, byName, names, byKeyword, keywords, byName, names, maxKeyLength,
} = library; } = library;
let matched: Emoji[] = MEMO_EMPTY_ARRAY; let matched: Emoji[] = [];
if (filter.length > maxKeyLength) {
return MEMO_EMPTY_ARRAY;
}
const matchedKeywords = keywords.filter((keyword) => keyword.startsWith(filter)).sort(); const matchedKeywords = keywords.filter((keyword) => keyword.startsWith(filter)).sort();
matched = matched.concat(Object.values(pickTruthy(byKeyword!, matchedKeywords)).flat()); matched = matched.concat(Object.values(pickTruthy(byKeyword!, matchedKeywords)).flat());
@ -249,5 +260,9 @@ function searchInLibrary(library: Library, filter: string, limit: number) {
matched = unique(matched); matched = unique(matched);
if (!matched.length) {
return MEMO_EMPTY_ARRAY;
}
return matched.slice(0, limit); return matched.slice(0, limit);
} }

View File

@ -25,7 +25,7 @@ try {
RE_USERNAME_SEARCH = /(^|\s)@[-_\p{L}\p{M}\p{N}]*$/gui; RE_USERNAME_SEARCH = /(^|\s)@[-_\p{L}\p{M}\p{N}]*$/gui;
} catch (e) { } catch (e) {
// Support for older versions of Firefox // Support for older versions of Firefox
RE_USERNAME_SEARCH = /(^|\s)@[-_\d\wа-яё]*$/gi; RE_USERNAME_SEARCH = /(^|\s)@[-_\d\wа-яёґєії]*$/gi;
} }
export default function useMentionTooltip( export default function useMentionTooltip(

View File

@ -71,7 +71,7 @@ function scrollWithJs(container: HTMLElement, left: number, duration: number) {
if (t >= 1) { if (t >= 1) {
container.style.scrollSnapType = ''; container.style.scrollSnapType = '';
container.dataset.scrollId = undefined; delete container.dataset.scrollId;
stopById.delete(id); stopById.delete(id);
resolve(); resolve();
} }

View File

@ -4,7 +4,7 @@ try {
RE_NOT_LETTER = /[^\p{L}\p{M}]+/ui; RE_NOT_LETTER = /[^\p{L}\p{M}]+/ui;
} catch (e) { } catch (e) {
// Support for older versions of firefox // Support for older versions of firefox
RE_NOT_LETTER = /[^\wа-яё]+/i; RE_NOT_LETTER = /[^\wа-яёґєії]+/i;
} }
export default function searchWords(haystack: string, needle: string | string[]) { export default function searchWords(haystack: string, needle: string | string[]) {