Composer: Support 👍 emoji format (#1264)

This commit is contained in:
Alexander Zinchuk 2021-07-14 14:48:34 +03:00
parent 1c6f3fe06a
commit 791f6a30d5
4 changed files with 27 additions and 32 deletions

View File

@ -3,7 +3,6 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { ApiAttachment, ApiChatMember, ApiUser } from '../../../api/types'; import { ApiAttachment, ApiChatMember, ApiUser } from '../../../api/types';
import { LangCode } from '../../../types';
import { CONTENT_TYPES_FOR_QUICK_UPLOAD, EDITABLE_INPUT_MODAL_ID } from '../../../config'; import { CONTENT_TYPES_FOR_QUICK_UPLOAD, EDITABLE_INPUT_MODAL_ID } from '../../../config';
import { getFileExtension } from '../../common/helpers/documentInfo'; import { getFileExtension } from '../../common/helpers/documentInfo';
@ -32,10 +31,8 @@ export type OwnProps = {
groupChatMembers?: ApiChatMember[]; groupChatMembers?: ApiChatMember[];
usersById?: Record<number, ApiUser>; usersById?: Record<number, ApiUser>;
recentEmojis: string[]; recentEmojis: string[];
language: LangCode;
emojiKeywords?: Record<string, string[]>; emojiKeywords?: Record<string, string[]>;
addRecentEmoji: AnyToVoidFunction; addRecentEmoji: AnyToVoidFunction;
loadEmojiKeywords: AnyToVoidFunction;
onCaptionUpdate: (html: string) => void; onCaptionUpdate: (html: string) => void;
onSend: () => void; onSend: () => void;
onFileAppend: (files: File[], isQuick: boolean) => void; onFileAppend: (files: File[], isQuick: boolean) => void;
@ -52,11 +49,9 @@ const AttachmentModal: FC<OwnProps> = ({
currentUserId, currentUserId,
usersById, usersById,
recentEmojis, recentEmojis,
language,
emojiKeywords, emojiKeywords,
onCaptionUpdate, onCaptionUpdate,
addRecentEmoji, addRecentEmoji,
loadEmojiKeywords,
onSend, onSend,
onFileAppend, onFileAppend,
onClear, onClear,
@ -237,10 +232,8 @@ const AttachmentModal: FC<OwnProps> = ({
isOpen={isEmojiTooltipOpen} isOpen={isEmojiTooltipOpen}
emojis={filteredEmojis} emojis={filteredEmojis}
onClose={closeEmojiTooltip} onClose={closeEmojiTooltip}
language={language}
onEmojiSelect={insertEmoji} onEmojiSelect={insertEmoji}
addRecentEmoji={addRecentEmoji} addRecentEmoji={addRecentEmoji}
loadEmojiKeywords={loadEmojiKeywords}
/> />
<MessageInput <MessageInput
id="caption-input-text" id="caption-input-text"

View File

@ -232,6 +232,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
} }
}, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]); }, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]);
useEffect(() => {
loadEmojiKeywords({ language: 'en' });
if (language !== 'en') {
loadEmojiKeywords({ language });
}
}, [loadEmojiKeywords, language]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (!appendixRef.current) return; if (!appendixRef.current) return;
@ -710,10 +717,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
usersById={usersById} usersById={usersById}
recentEmojis={recentEmojis} recentEmojis={recentEmojis}
onCaptionUpdate={setHtml} onCaptionUpdate={setHtml}
language={language}
emojiKeywords={emojiKeywords} emojiKeywords={emojiKeywords}
addRecentEmoji={addRecentEmoji} addRecentEmoji={addRecentEmoji}
loadEmojiKeywords={loadEmojiKeywords}
onSend={shouldSchedule ? openCalendar : handleSend} onSend={shouldSchedule ? openCalendar : handleSend}
onFileAppend={handleAppendFiles} onFileAppend={handleAppendFiles}
onClear={handleClearAttachment} onClear={handleClearAttachment}
@ -845,8 +850,6 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
onClose={closeEmojiTooltip} onClose={closeEmojiTooltip}
onEmojiSelect={insertEmoji} onEmojiSelect={insertEmoji}
addRecentEmoji={addRecentEmoji} addRecentEmoji={addRecentEmoji}
loadEmojiKeywords={loadEmojiKeywords}
language={language}
/> />
<AttachMenu <AttachMenu
isOpen={isAttachMenuOpen} isOpen={isAttachMenuOpen}

View File

@ -2,8 +2,6 @@ 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';
@ -54,22 +52,18 @@ function setItemVisible(index: number, containerRef: Record<string, any>) {
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
language: LangCode;
onEmojiSelect: (text: string) => void; onEmojiSelect: (text: string) => void;
onClose: NoneToVoidFunction; onClose: NoneToVoidFunction;
addRecentEmoji: AnyToVoidFunction; addRecentEmoji: AnyToVoidFunction;
loadEmojiKeywords: AnyToVoidFunction;
emojis: Emoji[]; emojis: Emoji[];
}; };
const EmojiTooltip: FC<OwnProps> = ({ const EmojiTooltip: FC<OwnProps> = ({
isOpen, isOpen,
language,
emojis, emojis,
onClose, onClose,
onEmojiSelect, onEmojiSelect,
addRecentEmoji, addRecentEmoji,
loadEmojiKeywords,
}) => { }) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -78,13 +72,6 @@ const EmojiTooltip: FC<OwnProps> = ({
const [selectedIndex, setSelectedIndex] = useState(NO_EMOJI_SELECTED_INDEX); const [selectedIndex, setSelectedIndex] = useState(NO_EMOJI_SELECTED_INDEX);
useEffect(() => {
loadEmojiKeywords({ language: 'en' });
if (language !== 'en') {
loadEmojiKeywords({ language });
}
}, [loadEmojiKeywords, language]);
useEffect(() => { useEffect(() => {
setSelectedIndex(0); setSelectedIndex(0);
}, [emojis]); }, [emojis]);

View File

@ -22,10 +22,10 @@ const EMOJIS_LIMIT = 36;
const FILTER_MIN_LENGTH = 2; const FILTER_MIN_LENGTH = 2;
try { try {
RE_NOT_EMOJI_SEARCH = new RegExp('[^-_:\\p{L}\\p{N}]+', 'iu'); RE_NOT_EMOJI_SEARCH = new RegExp('[^-+_:\\p{L}\\p{N}]+', 'iu');
} catch (e) { } catch (e) {
// Support for older versions of firefox // Support for older versions of firefox
RE_NOT_EMOJI_SEARCH = new RegExp('[^-_:\\d\\wа-яё]+', 'i'); RE_NOT_EMOJI_SEARCH = new RegExp('[^-+_:\\d\\wа-яё]+', 'i');
} }
export default function useEmojiTooltip( export default function useEmojiTooltip(
@ -45,6 +45,7 @@ export default function useEmojiTooltip(
const [byKeyword, setByKeyword] = useState<Record<string, Emoji[]>>({}); const [byKeyword, setByKeyword] = useState<Record<string, Emoji[]>>({});
const [names, setNames] = useState<string[]>(); const [names, setNames] = useState<string[]>();
const [byName, setByName] = useState<Record<string, Emoji[]>>({}); const [byName, setByName] = useState<Record<string, Emoji[]>>({});
const [shouldForceInsertEmoji, setShouldForceInsertEmoji] = useState(false);
const [filteredEmojis, setFilteredEmojis] = useState<Emoji[]>([]); const [filteredEmojis, setFilteredEmojis] = useState<Emoji[]>([]);
@ -124,9 +125,12 @@ export default function useEmojiTooltip(
return; return;
} }
const filter = code.substr(1); const forceSend = code.length > 2 && code.endsWith(':');
const filter = code.substr(1, forceSend ? code.length - 2 : undefined);
let matched: Emoji[] = []; let matched: Emoji[] = [];
setShouldForceInsertEmoji(forceSend);
if (!filter) { if (!filter) {
matched = recentEmojis; matched = recentEmojis;
} else if (filter.length >= FILTER_MIN_LENGTH) { } else if (filter.length >= FILTER_MIN_LENGTH) {
@ -141,18 +145,20 @@ export default function useEmojiTooltip(
} }
if (matched.length) { if (matched.length) {
markIsOpen(); if (!forceSend) {
markIsOpen();
}
setFilteredEmojis(matched.slice(0, EMOJIS_LIMIT)); setFilteredEmojis(matched.slice(0, EMOJIS_LIMIT));
} else { } else {
unmarkIsOpen(); unmarkIsOpen();
} }
}, [ }, [
byId, byKeyword, keywords, byName, names, byId, byKeyword, keywords, byName, names, html, isAllowed, markIsOpen,
html, isAllowed, markIsOpen, recentEmojis, unmarkIsOpen, recentEmojis, unmarkIsOpen, setShouldForceInsertEmoji,
]); ]);
const insertEmoji = useCallback((textEmoji: string) => { const insertEmoji = useCallback((textEmoji: string, isForce?: boolean) => {
const atIndex = html.lastIndexOf(':'); const atIndex = html.lastIndexOf(':', isForce ? -1 : undefined);
if (atIndex !== -1) { if (atIndex !== -1) {
onUpdateHtml(`${html.substr(0, atIndex)}${textEmoji}`); onUpdateHtml(`${html.substr(0, atIndex)}${textEmoji}`);
const messageInput = document.getElementById(inputId)!; const messageInput = document.getElementById(inputId)!;
@ -166,6 +172,12 @@ export default function useEmojiTooltip(
unmarkIsOpen(); unmarkIsOpen();
}, [html, inputId, onUpdateHtml, unmarkIsOpen]); }, [html, inputId, onUpdateHtml, unmarkIsOpen]);
useEffect(() => {
if (isOpen && shouldForceInsertEmoji && filteredEmojis.length) {
insertEmoji(filteredEmojis[0].native, true);
}
}, [filteredEmojis, insertEmoji, isOpen, shouldForceInsertEmoji]);
return { return {
isEmojiTooltipOpen: isOpen, isEmojiTooltipOpen: isOpen,
closeEmojiTooltip: unmarkIsOpen, closeEmojiTooltip: unmarkIsOpen,