Emoji Tooltip: Disable tooltip for a single character (#1178)
This commit is contained in:
parent
824c2f684a
commit
3eb8d6259f
@ -33,6 +33,7 @@ export type OwnProps = {
|
|||||||
usersById?: Record<number, ApiUser>;
|
usersById?: Record<number, ApiUser>;
|
||||||
recentEmojis: string[];
|
recentEmojis: string[];
|
||||||
language: LangCode;
|
language: LangCode;
|
||||||
|
emojiKeywords?: Record<string, string[]>;
|
||||||
addRecentEmoji: AnyToVoidFunction;
|
addRecentEmoji: AnyToVoidFunction;
|
||||||
loadEmojiKeywords: AnyToVoidFunction;
|
loadEmojiKeywords: AnyToVoidFunction;
|
||||||
onCaptionUpdate: (html: string) => void;
|
onCaptionUpdate: (html: string) => void;
|
||||||
@ -52,6 +53,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
usersById,
|
usersById,
|
||||||
recentEmojis,
|
recentEmojis,
|
||||||
language,
|
language,
|
||||||
|
emojiKeywords,
|
||||||
onCaptionUpdate,
|
onCaptionUpdate,
|
||||||
addRecentEmoji,
|
addRecentEmoji,
|
||||||
loadEmojiKeywords,
|
loadEmojiKeywords,
|
||||||
@ -89,6 +91,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
recentEmojis,
|
recentEmojis,
|
||||||
EDITABLE_INPUT_MODAL_ID,
|
EDITABLE_INPUT_MODAL_ID,
|
||||||
onCaptionUpdate,
|
onCaptionUpdate,
|
||||||
|
emojiKeywords,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
||||||
|
|||||||
@ -673,6 +673,10 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
: (isSymbolMenuOpen && 'is-loading'),
|
: (isSymbolMenuOpen && 'is-loading'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onSend = mainButtonState === MainButtonState.Edit
|
||||||
|
? handleEditComplete
|
||||||
|
: (shouldSchedule ? openCalendar : handleSend);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{allowedAttachmentOptions.canAttachMedia && (
|
{allowedAttachmentOptions.canAttachMedia && (
|
||||||
@ -695,6 +699,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
recentEmojis={recentEmojis}
|
recentEmojis={recentEmojis}
|
||||||
onCaptionUpdate={setHtml}
|
onCaptionUpdate={setHtml}
|
||||||
language={language}
|
language={language}
|
||||||
|
emojiKeywords={emojiKeywords}
|
||||||
addRecentEmoji={addRecentEmoji}
|
addRecentEmoji={addRecentEmoji}
|
||||||
loadEmojiKeywords={loadEmojiKeywords}
|
loadEmojiKeywords={loadEmojiKeywords}
|
||||||
onSend={shouldSchedule ? openCalendar : handleSend}
|
onSend={shouldSchedule ? openCalendar : handleSend}
|
||||||
@ -774,9 +779,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
shouldSupressFocus={IS_MOBILE_SCREEN && isSymbolMenuOpen}
|
shouldSupressFocus={IS_MOBILE_SCREEN && isSymbolMenuOpen}
|
||||||
shouldSupressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen}
|
shouldSupressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen}
|
||||||
onUpdate={setHtml}
|
onUpdate={setHtml}
|
||||||
onSend={mainButtonState === MainButtonState.Edit
|
onSend={onSend}
|
||||||
? handleEditComplete
|
|
||||||
: (shouldSchedule ? openCalendar : handleSend)}
|
|
||||||
onSupressedFocus={closeSymbolMenu}
|
onSupressedFocus={closeSymbolMenu}
|
||||||
/>
|
/>
|
||||||
{withScheduledButton && (
|
{withScheduledButton && (
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
&.focus,
|
&.focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-interactive-element-hover);
|
background-color: var(--color-background-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > img {
|
& > img {
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import React, {
|
|||||||
FC, memo, useCallback, useEffect, useRef, useState,
|
FC, memo, useCallback, useEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import { LangCode } from '../../../types';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import cycleRestrict from '../../../util/cycleRestrict';
|
import cycleRestrict from '../../../util/cycleRestrict';
|
||||||
@ -16,10 +18,11 @@ import Loading from '../../ui/Loading';
|
|||||||
import EmojiButton from './EmojiButton';
|
import EmojiButton from './EmojiButton';
|
||||||
|
|
||||||
import './EmojiTooltip.scss';
|
import './EmojiTooltip.scss';
|
||||||
import { LangCode } from '../../../types';
|
|
||||||
|
|
||||||
const VIEWPORT_MARGIN = 8;
|
const VIEWPORT_MARGIN = 8;
|
||||||
const EMOJI_BUTTON_WIDTH = 44;
|
const EMOJI_BUTTON_WIDTH = 44;
|
||||||
|
const CLOSE_DURATION = 350;
|
||||||
|
const NO_EMOJI_SELECTED_INDEX = -1;
|
||||||
|
|
||||||
function setItemVisible(index: number, containerRef: Record<string, any>) {
|
function setItemVisible(index: number, containerRef: Record<string, any>) {
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
@ -59,8 +62,6 @@ export type OwnProps = {
|
|||||||
emojis: Emoji[];
|
emojis: Emoji[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const CLOSE_DURATION = 350;
|
|
||||||
|
|
||||||
const EmojiTooltip: FC<OwnProps> = ({
|
const EmojiTooltip: FC<OwnProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
language,
|
language,
|
||||||
@ -75,7 +76,7 @@ const EmojiTooltip: FC<OwnProps> = ({
|
|||||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
||||||
const listEmojis: Emoji[] = usePrevDuringAnimation(emojis.length ? emojis : undefined, CLOSE_DURATION) || [];
|
const listEmojis: Emoji[] = usePrevDuringAnimation(emojis.length ? emojis : undefined, CLOSE_DURATION) || [];
|
||||||
|
|
||||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
const [selectedIndex, setSelectedIndex] = useState(NO_EMOJI_SELECTED_INDEX);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadEmojiKeywords({ language });
|
loadEmojiKeywords({ language });
|
||||||
@ -91,21 +92,20 @@ const EmojiTooltip: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const getSelectedIndex = useCallback((newIndex: number) => {
|
const getSelectedIndex = useCallback((newIndex: number) => {
|
||||||
if (!emojis.length) {
|
if (!emojis.length) {
|
||||||
return -1;
|
return NO_EMOJI_SELECTED_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojisCount = emojis.length;
|
const emojisCount = emojis.length;
|
||||||
return cycleRestrict(emojisCount, newIndex);
|
return cycleRestrict(emojisCount, newIndex);
|
||||||
}, [emojis]);
|
}, [emojis]);
|
||||||
|
|
||||||
|
|
||||||
const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => {
|
const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSelectedIndex((index) => (getSelectedIndex(index + value)));
|
setSelectedIndex((index) => (getSelectedIndex(index + value)));
|
||||||
}, [setSelectedIndex, getSelectedIndex]);
|
}, [setSelectedIndex, getSelectedIndex]);
|
||||||
|
|
||||||
const handleSelectEmoji = useCallback((e: KeyboardEvent) => {
|
const handleSelectEmoji = useCallback((e: KeyboardEvent) => {
|
||||||
if (emojis.length && selectedIndex > -1) {
|
if (emojis.length && selectedIndex > NO_EMOJI_SELECTED_INDEX) {
|
||||||
const emoji = emojis[selectedIndex];
|
const emoji = emojis[selectedIndex];
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@ -17,8 +17,16 @@ let emojiDataPromise: Promise<EmojiModule>;
|
|||||||
let emojiRawData: EmojiRawData;
|
let emojiRawData: EmojiRawData;
|
||||||
let emojiData: EmojiData;
|
let emojiData: EmojiData;
|
||||||
|
|
||||||
const RE_NOT_EMOJI_SEARCH = /[^-_:\p{L}\p{N}]+/iu;
|
let RE_NOT_EMOJI_SEARCH: RegExp;
|
||||||
const EMOJIS_LIMIT = 36;
|
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(
|
export default function useEmojiTooltip(
|
||||||
isAllowed: boolean,
|
isAllowed: boolean,
|
||||||
@ -31,7 +39,9 @@ export default function useEmojiTooltip(
|
|||||||
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
||||||
|
|
||||||
const [byId, setById] = useState<Record<string, Emoji> | undefined>();
|
const [byId, setById] = useState<Record<string, Emoji> | undefined>();
|
||||||
|
const [keywords, setKeywords] = useState<string[]>();
|
||||||
const [byKeyword, setByKeyword] = useState<Record<string, Emoji[]>>({});
|
const [byKeyword, setByKeyword] = useState<Record<string, Emoji[]>>({});
|
||||||
|
const [names, setNames] = useState<string[]>();
|
||||||
const [byName, setByName] = useState<Record<string, Emoji[]>>({});
|
const [byName, setByName] = useState<Record<string, Emoji[]>>({});
|
||||||
|
|
||||||
const [filteredEmojis, setFilteredEmojis] = useState<Emoji[]>([]);
|
const [filteredEmojis, setFilteredEmojis] = useState<Emoji[]>([]);
|
||||||
@ -70,12 +80,14 @@ export default function useEmojiTooltip(
|
|||||||
|
|
||||||
if (emojiKeywords) {
|
if (emojiKeywords) {
|
||||||
const byNative = buildCollectionByKey(emojis, 'native');
|
const byNative = buildCollectionByKey(emojis, 'native');
|
||||||
setByKeyword(mapValues(emojiKeywords, (natives) => {
|
const emojisByKeyword = mapValues(emojiKeywords, (natives) => {
|
||||||
return Object.values(pickTruthy(byNative, 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) => {
|
emoji.names.forEach((name) => {
|
||||||
if (!result[name]) {
|
if (!result[name]) {
|
||||||
result[name] = [];
|
result[name] = [];
|
||||||
@ -85,7 +97,9 @@ export default function useEmojiTooltip(
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, {} as Record<string, Emoji[]>));
|
}, {} as Record<string, Emoji[]>);
|
||||||
|
setByName(emojisByName);
|
||||||
|
setNames(Object.keys(emojisByName));
|
||||||
}, [byId, emojiKeywords]);
|
}, [byId, emojiKeywords]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -106,12 +120,12 @@ export default function useEmojiTooltip(
|
|||||||
|
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
matched = recentEmojis;
|
matched = recentEmojis;
|
||||||
} else {
|
} else if (filter.length >= FILTER_MIN_LENGTH) {
|
||||||
const matchedKeywords = Object.keys(byKeyword).filter((keyword) => keyword.startsWith(filter));
|
const matchedKeywords = keywords.filter((keyword) => keyword.startsWith(filter)).sort();
|
||||||
matched = matched.concat(flatten(Object.values(pickTruthy(byKeyword, matchedKeywords))));
|
matched = matched.concat(flatten(Object.values(pickTruthy(byKeyword, matchedKeywords))));
|
||||||
|
|
||||||
// Also search by names, which is useful for non-English languages
|
// 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 = matched.concat(flatten(Object.values(pickTruthy(byName, matchedNames))));
|
||||||
|
|
||||||
matched = unique(matched);
|
matched = unique(matched);
|
||||||
@ -123,7 +137,10 @@ export default function useEmojiTooltip(
|
|||||||
} else {
|
} else {
|
||||||
unmarkIsOpen();
|
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 insertEmoji = useCallback((textEmoji: string) => {
|
||||||
const atIndex = html.lastIndexOf(':');
|
const atIndex = html.lastIndexOf(':');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user