diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 9aab1b5cc..86b3c29ad 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -33,6 +33,7 @@ export type OwnProps = { usersById?: Record; recentEmojis: string[]; language: LangCode; + emojiKeywords?: Record; addRecentEmoji: AnyToVoidFunction; loadEmojiKeywords: AnyToVoidFunction; onCaptionUpdate: (html: string) => void; @@ -52,6 +53,7 @@ const AttachmentModal: FC = ({ usersById, recentEmojis, language, + emojiKeywords, onCaptionUpdate, addRecentEmoji, loadEmojiKeywords, @@ -89,6 +91,7 @@ const AttachmentModal: FC = ({ recentEmojis, EDITABLE_INPUT_MODAL_ID, onCaptionUpdate, + emojiKeywords, ); useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]); diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 226fbd4e9..f3e5245ec 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -673,6 +673,10 @@ const Composer: FC = ({ : (isSymbolMenuOpen && 'is-loading'), ); + const onSend = mainButtonState === MainButtonState.Edit + ? handleEditComplete + : (shouldSchedule ? openCalendar : handleSend); + return (
{allowedAttachmentOptions.canAttachMedia && ( @@ -695,6 +699,7 @@ const Composer: FC = ({ recentEmojis={recentEmojis} onCaptionUpdate={setHtml} language={language} + emojiKeywords={emojiKeywords} addRecentEmoji={addRecentEmoji} loadEmojiKeywords={loadEmojiKeywords} onSend={shouldSchedule ? openCalendar : handleSend} @@ -774,9 +779,7 @@ const Composer: FC = ({ shouldSupressFocus={IS_MOBILE_SCREEN && isSymbolMenuOpen} shouldSupressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen} onUpdate={setHtml} - onSend={mainButtonState === MainButtonState.Edit - ? handleEditComplete - : (shouldSchedule ? openCalendar : handleSend)} + onSend={onSend} onSupressedFocus={closeSymbolMenu} /> {withScheduledButton && ( diff --git a/src/components/middle/composer/EmojiButton.scss b/src/components/middle/composer/EmojiButton.scss index 2bc6c0046..e1834d658 100644 --- a/src/components/middle/composer/EmojiButton.scss +++ b/src/components/middle/composer/EmojiButton.scss @@ -18,7 +18,7 @@ &.focus, &:hover { - background-color: var(--color-interactive-element-hover); + background-color: var(--color-background-selected); } & > img { diff --git a/src/components/middle/composer/EmojiTooltip.tsx b/src/components/middle/composer/EmojiTooltip.tsx index 592e06a88..47a989520 100644 --- a/src/components/middle/composer/EmojiTooltip.tsx +++ b/src/components/middle/composer/EmojiTooltip.tsx @@ -2,6 +2,8 @@ import React, { FC, memo, useCallback, useEffect, useRef, useState, } from '../../../lib/teact/teact'; +import { LangCode } from '../../../types'; + import { IS_TOUCH_ENV } from '../../../util/environment'; import buildClassName from '../../../util/buildClassName'; import cycleRestrict from '../../../util/cycleRestrict'; @@ -16,10 +18,11 @@ import Loading from '../../ui/Loading'; import EmojiButton from './EmojiButton'; import './EmojiTooltip.scss'; -import { LangCode } from '../../../types'; const VIEWPORT_MARGIN = 8; const EMOJI_BUTTON_WIDTH = 44; +const CLOSE_DURATION = 350; +const NO_EMOJI_SELECTED_INDEX = -1; function setItemVisible(index: number, containerRef: Record) { const container = containerRef.current!; @@ -59,8 +62,6 @@ export type OwnProps = { emojis: Emoji[]; }; -const CLOSE_DURATION = 350; - const EmojiTooltip: FC = ({ isOpen, language, @@ -75,7 +76,7 @@ const EmojiTooltip: FC = ({ const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); const listEmojis: Emoji[] = usePrevDuringAnimation(emojis.length ? emojis : undefined, CLOSE_DURATION) || []; - const [selectedIndex, setSelectedIndex] = useState(-1); + const [selectedIndex, setSelectedIndex] = useState(NO_EMOJI_SELECTED_INDEX); useEffect(() => { loadEmojiKeywords({ language }); @@ -91,21 +92,20 @@ const EmojiTooltip: FC = ({ const getSelectedIndex = useCallback((newIndex: number) => { if (!emojis.length) { - return -1; + return NO_EMOJI_SELECTED_INDEX; } const emojisCount = emojis.length; return cycleRestrict(emojisCount, newIndex); }, [emojis]); - const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => { e.preventDefault(); setSelectedIndex((index) => (getSelectedIndex(index + value))); }, [setSelectedIndex, getSelectedIndex]); const handleSelectEmoji = useCallback((e: KeyboardEvent) => { - if (emojis.length && selectedIndex > -1) { + if (emojis.length && selectedIndex > NO_EMOJI_SELECTED_INDEX) { const emoji = emojis[selectedIndex]; if (emoji) { e.preventDefault(); diff --git a/src/components/middle/composer/hooks/useEmojiTooltip.ts b/src/components/middle/composer/hooks/useEmojiTooltip.ts index 60a89f485..98e9fb1ea 100644 --- a/src/components/middle/composer/hooks/useEmojiTooltip.ts +++ b/src/components/middle/composer/hooks/useEmojiTooltip.ts @@ -17,8 +17,16 @@ let emojiDataPromise: Promise; let emojiRawData: EmojiRawData; let emojiData: EmojiData; -const RE_NOT_EMOJI_SEARCH = /[^-_:\p{L}\p{N}]+/iu; +let RE_NOT_EMOJI_SEARCH: RegExp; const EMOJIS_LIMIT = 36; +const FILTER_MIN_LENGTH = 2; + +try { + RE_NOT_EMOJI_SEARCH = new RegExp('[^-_:\\p{L}\\p{N}]+', 'iu'); +} catch (e) { + // Support for older versions of firefox + RE_NOT_EMOJI_SEARCH = new RegExp('[^-_:\\d\\wа-яё]+', 'i'); +} export default function useEmojiTooltip( isAllowed: boolean, @@ -31,7 +39,9 @@ export default function useEmojiTooltip( const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); const [byId, setById] = useState | undefined>(); + const [keywords, setKeywords] = useState(); const [byKeyword, setByKeyword] = useState>({}); + const [names, setNames] = useState(); const [byName, setByName] = useState>({}); const [filteredEmojis, setFilteredEmojis] = useState([]); @@ -70,12 +80,14 @@ export default function useEmojiTooltip( if (emojiKeywords) { const byNative = buildCollectionByKey(emojis, 'native'); - setByKeyword(mapValues(emojiKeywords, (natives) => { + const emojisByKeyword = mapValues(emojiKeywords, (natives) => { return Object.values(pickTruthy(byNative, natives)); - })); + }); + setByKeyword(emojisByKeyword); + setKeywords(Object.keys(emojisByKeyword)); } - setByName(emojis.reduce((result, emoji) => { + const emojisByName = emojis.reduce((result, emoji) => { emoji.names.forEach((name) => { if (!result[name]) { result[name] = []; @@ -85,7 +97,9 @@ export default function useEmojiTooltip( }); return result; - }, {} as Record)); + }, {} as Record); + setByName(emojisByName); + setNames(Object.keys(emojisByName)); }, [byId, emojiKeywords]); useEffect(() => { @@ -106,12 +120,12 @@ export default function useEmojiTooltip( if (!filter) { matched = recentEmojis; - } else { - const matchedKeywords = Object.keys(byKeyword).filter((keyword) => keyword.startsWith(filter)); + } else if (filter.length >= FILTER_MIN_LENGTH) { + const matchedKeywords = keywords.filter((keyword) => keyword.startsWith(filter)).sort(); matched = matched.concat(flatten(Object.values(pickTruthy(byKeyword, matchedKeywords)))); // Also search by names, which is useful for non-English languages - const matchedNames = Object.keys(byName).filter((name) => name.startsWith(filter)); + const matchedNames = names.filter((name) => name.startsWith(filter)); matched = matched.concat(flatten(Object.values(pickTruthy(byName, matchedNames)))); matched = unique(matched); @@ -123,7 +137,10 @@ export default function useEmojiTooltip( } else { unmarkIsOpen(); } - }, [byId, byKeyword, byName, html, isAllowed, markIsOpen, recentEmojis, unmarkIsOpen]); + }, [ + byId, byKeyword, keywords, byName, names, + html, isAllowed, markIsOpen, recentEmojis, unmarkIsOpen, + ]); const insertEmoji = useCallback((textEmoji: string) => { const atIndex = html.lastIndexOf(':');