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 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);
const RE_BR = /(
|
)/g;
const RE_SPACE = / /g;
const RE_CLEAN_HTML = /(
|<\/div>)/gi;
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 = html
.replace(RE_SPACE, ' ')
.replace(RE_BR, '\n')
.replace(RE_CLEAN_HTML, '')
.replace(/\n$/i, '')
.match(RE_USERNAME_SEARCH);
return username ? username[0].trim() : undefined;
}
function canSuggestInlineBots(html: string) {
return html.startsWith('@');
}