Emoji Tooltip: Various fixes (#2688)
This commit is contained in:
parent
92c1b0b281
commit
7c7e0c1d69
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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]);
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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[]) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user