import { useCallback, useEffect, useState, useMemo, } from '../../../../lib/teact/teact'; import { ApiMessageEntityTypes, ApiChatMember, ApiUser } from '../../../../api/types'; import { EDITABLE_INPUT_ID } from '../../../../config'; import { getUserFirstOrLastName } from '../../../../modules/helpers'; import searchUserName from '../helpers/searchUserName'; import { prepareForRegExp } from '../helpers/prepareForRegExp'; import focusEditableElement from '../../../../util/focusEditableElement'; import useFlag from '../../../../hooks/useFlag'; import { unique } from '../../../../util/iteratees'; import { throttle } from '../../../../util/schedulers'; const runThrottled = throttle((cb) => cb(), 500, true); let RE_USERNAME_SEARCH: RegExp; try { RE_USERNAME_SEARCH = new RegExp('(^|\\s)@[-_\\p{L}\\p{M}\\p{N}]*$', 'gui'); } catch (e) { // Support for older versions of firefox RE_USERNAME_SEARCH = new RegExp('(^|\\s)@[-_\\d\\wа-яё]*$', 'gi'); } export default function useMentionTooltip( canSuggestMembers: boolean | undefined, html: string, onUpdateHtml: (html: string) => void, inputId: string = EDITABLE_INPUT_ID, groupChatMembers?: ApiChatMember[], topInlineBotIds?: number[], currentUserId?: number, usersById?: Record, ) { const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); const [usersToMention, setUsersToMention] = useState(); const topInlineBots = useMemo(() => { return (topInlineBotIds || []).map((id) => usersById?.[id]).filter(Boolean as any); }, [topInlineBotIds, usersById]); const getFilteredUsers = useCallback((filter, withInlineBots: boolean) => { if (!(groupChatMembers || topInlineBotIds) || !usersById) { setUsersToMention(undefined); return; } runThrottled(() => { const inlineBots = (withInlineBots ? topInlineBots : []).filter((inlineBot) => { return !filter || searchUserName(filter, inlineBot); }); const chatMembers = (groupChatMembers || []) .map(({ userId }) => usersById[userId]) .filter((user) => { if (!user || user.id === currentUserId) { return false; } return !filter || searchUserName(filter, user); }); setUsersToMention(unique(inlineBots.concat(chatMembers))); }); }, [currentUserId, groupChatMembers, topInlineBotIds, topInlineBots, usersById]); useEffect(() => { if (!canSuggestMembers || !html.length) { unmarkIsOpen(); return; } const usernameFilter = html.includes('@') && getUsernameFilter(html); if (usernameFilter) { const filter = usernameFilter ? usernameFilter.substr(1) : ''; getFilteredUsers(filter, canSuggestInlineBots(html)); } else { unmarkIsOpen(); } }, [canSuggestMembers, html, getFilteredUsers, markIsOpen, unmarkIsOpen]); useEffect(() => { if (usersToMention?.length) { markIsOpen(); } else { unmarkIsOpen(); } }, [markIsOpen, unmarkIsOpen, usersToMention]); const insertMention = useCallback((user: ApiUser, forceFocus = false) => { if (!user.username && !getUserFirstOrLastName(user)) { return; } const insertedHtml = user.username ? `@${user.username}` : `${getUserFirstOrLastName(user)}`; const atIndex = html.lastIndexOf('@'); if (atIndex !== -1) { onUpdateHtml(`${html.substr(0, atIndex)}${insertedHtml} `); const messageInput = document.getElementById(inputId)!; requestAnimationFrame(() => { focusEditableElement(messageInput, forceFocus); }); } unmarkIsOpen(); }, [html, inputId, onUpdateHtml, unmarkIsOpen]); return { isMentionTooltipOpen: isOpen, closeMentionTooltip: unmarkIsOpen, insertMention, mentionFilteredUsers: usersToMention, }; } function getUsernameFilter(html: string) { const username = prepareForRegExp(html).match(RE_USERNAME_SEARCH); return username ? username[0].trim() : undefined; } function canSuggestInlineBots(html: string) { return html.startsWith('@'); }