Emoji Tooltip: Disable tooltip for a single character (#1178)

This commit is contained in:
Alexander Zinchuk 2021-06-18 12:19:44 +03:00
parent 824c2f684a
commit 3eb8d6259f
5 changed files with 43 additions and 20 deletions

View File

@ -33,6 +33,7 @@ export type OwnProps = {
usersById?: Record<number, ApiUser>;
recentEmojis: string[];
language: LangCode;
emojiKeywords?: Record<string, string[]>;
addRecentEmoji: AnyToVoidFunction;
loadEmojiKeywords: AnyToVoidFunction;
onCaptionUpdate: (html: string) => void;
@ -52,6 +53,7 @@ const AttachmentModal: FC<OwnProps> = ({
usersById,
recentEmojis,
language,
emojiKeywords,
onCaptionUpdate,
addRecentEmoji,
loadEmojiKeywords,
@ -89,6 +91,7 @@ const AttachmentModal: FC<OwnProps> = ({
recentEmojis,
EDITABLE_INPUT_MODAL_ID,
onCaptionUpdate,
emojiKeywords,
);
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);

View File

@ -673,6 +673,10 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
: (isSymbolMenuOpen && 'is-loading'),
);
const onSend = mainButtonState === MainButtonState.Edit
? handleEditComplete
: (shouldSchedule ? openCalendar : handleSend);
return (
<div className={className}>
{allowedAttachmentOptions.canAttachMedia && (
@ -695,6 +699,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
recentEmojis={recentEmojis}
onCaptionUpdate={setHtml}
language={language}
emojiKeywords={emojiKeywords}
addRecentEmoji={addRecentEmoji}
loadEmojiKeywords={loadEmojiKeywords}
onSend={shouldSchedule ? openCalendar : handleSend}
@ -774,9 +779,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
shouldSupressFocus={IS_MOBILE_SCREEN && isSymbolMenuOpen}
shouldSupressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen}
onUpdate={setHtml}
onSend={mainButtonState === MainButtonState.Edit
? handleEditComplete
: (shouldSchedule ? openCalendar : handleSend)}
onSend={onSend}
onSupressedFocus={closeSymbolMenu}
/>
{withScheduledButton && (

View File

@ -18,7 +18,7 @@
&.focus,
&:hover {
background-color: var(--color-interactive-element-hover);
background-color: var(--color-background-selected);
}
& > img {

View File

@ -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<string, any>) {
const container = containerRef.current!;
@ -59,8 +62,6 @@ export type OwnProps = {
emojis: Emoji[];
};
const CLOSE_DURATION = 350;
const EmojiTooltip: FC<OwnProps> = ({
isOpen,
language,
@ -75,7 +76,7 @@ const EmojiTooltip: FC<OwnProps> = ({
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<OwnProps> = ({
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();

View File

@ -17,8 +17,16 @@ let emojiDataPromise: Promise<EmojiModule>;
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<Record<string, Emoji> | undefined>();
const [keywords, setKeywords] = useState<string[]>();
const [byKeyword, setByKeyword] = useState<Record<string, Emoji[]>>({});
const [names, setNames] = useState<string[]>();
const [byName, setByName] = useState<Record<string, Emoji[]>>({});
const [filteredEmojis, setFilteredEmojis] = useState<Emoji[]>([]);
@ -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<string, Emoji[]>));
}, {} as Record<string, Emoji[]>);
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(':');