From a2a0161cc98172e4759fd19319386e37071d2555 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 13 Nov 2022 17:06:21 +0400 Subject: [PATCH] Mention Tooltip: Support catching "@" in the middle of input (#2131) --- .../middle/composer/AttachmentModal.tsx | 3 +- src/components/middle/composer/Composer.tsx | 3 +- .../composer/hooks/useMentionTooltip.ts | 47 +++++++++++++------ 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 4c074f5aa..34e005c1f 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -104,9 +104,8 @@ const AttachmentModal: FC = ({ isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers, } = useMentionTooltip( isOpen, - captionRef, + `#${EDITABLE_INPUT_MODAL_ID}`, onCaptionUpdate, - EDITABLE_INPUT_MODAL_ID, groupChatMembers, undefined, currentUserId, diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 1bbd2f0ac..89b8d9f87 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -406,9 +406,8 @@ const Composer: FC = ({ isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers, } = useMentionTooltip( !attachments.length, - htmlRef, + EDITABLE_INPUT_CSS_SELECTOR, setHtml, - undefined, groupChatMembers, topInlineBotIds, currentUserId, diff --git a/src/components/middle/composer/hooks/useMentionTooltip.ts b/src/components/middle/composer/hooks/useMentionTooltip.ts index 3ba660015..d697f852c 100644 --- a/src/components/middle/composer/hooks/useMentionTooltip.ts +++ b/src/components/middle/composer/hooks/useMentionTooltip.ts @@ -5,14 +5,17 @@ import { getGlobal } from '../../../../global'; import type { ApiChatMember, ApiUser } from '../../../../api/types'; import { ApiMessageEntityTypes } from '../../../../api/types'; -import { EDITABLE_INPUT_ID } from '../../../../config'; + import { filterUsersByName, getUserFirstOrLastName } from '../../../../global/helpers'; import { prepareForRegExp } from '../helpers/prepareForRegExp'; import focusEditableElement from '../../../../util/focusEditableElement'; import { pickTruthy, unique } from '../../../../util/iteratees'; import { throttle } from '../../../../util/schedulers'; +import { getHtmlBeforeSelection } from '../../../../util/selection'; import useFlag from '../../../../hooks/useFlag'; +import useCacheBuster from '../../../../hooks/useCacheBuster'; +import useOnSelectionChange from '../../../../hooks/useOnSelectionChange'; const runThrottled = throttle((cb) => cb(), 500, true); let RE_USERNAME_SEARCH: RegExp; @@ -26,14 +29,14 @@ try { export default function useMentionTooltip( canSuggestMembers: boolean | undefined, - htmlRef: { current: string }, + inputSelector: string, onUpdateHtml: (html: string) => void, - inputId: string = EDITABLE_INPUT_ID, groupChatMembers?: ApiChatMember[], topInlineBotIds?: string[], currentUserId?: string, ) { const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); + const [htmlBeforeSelection, setHtmlBeforeSelection] = useState(''); const [usersToMention, setUsersToMention] = useState(); const updateFilteredUsers = useCallback((filter, withInlineBots: boolean) => { @@ -64,22 +67,35 @@ export default function useMentionTooltip( }); }, [currentUserId, groupChatMembers, topInlineBotIds]); - const html = htmlRef.current; + const [cacheBuster, updateCacheBuster] = useCacheBuster(); + + const handleSelectionChange = useCallback((range: Range) => { + if (range.collapsed) { + updateCacheBuster(); // Update tooltip on cursor move + } + }, [updateCacheBuster]); + + useOnSelectionChange(document.querySelector(inputSelector), handleSelectionChange); + useEffect(() => { - if (!canSuggestMembers || !html.length) { + setHtmlBeforeSelection(getHtmlBeforeSelection(document.querySelector(inputSelector)!)); + }, [inputSelector, cacheBuster]); + + useEffect(() => { + if (!canSuggestMembers || !htmlBeforeSelection.length) { unmarkIsOpen(); return; } - const usernameFilter = html.includes('@') && getUsernameFilter(html); + const usernameFilter = htmlBeforeSelection.includes('@') && getUsernameFilter(htmlBeforeSelection); if (usernameFilter) { const filter = usernameFilter ? usernameFilter.substr(1) : ''; - updateFilteredUsers(filter, canSuggestInlineBots(html)); + updateFilteredUsers(filter, canSuggestInlineBots(htmlBeforeSelection)); } else { unmarkIsOpen(); } - }, [canSuggestMembers, updateFilteredUsers, markIsOpen, unmarkIsOpen, html]); + }, [canSuggestMembers, updateFilteredUsers, markIsOpen, unmarkIsOpen, htmlBeforeSelection]); useEffect(() => { if (usersToMention?.length) { @@ -104,18 +120,21 @@ export default function useMentionTooltip( dir="auto" >${getUserFirstOrLastName(user)}`; - const currentHtml = htmlRef.current; - const atIndex = currentHtml.lastIndexOf('@'); + const containerEl = document.querySelector(inputSelector)!; + + const atIndex = htmlBeforeSelection.lastIndexOf('@'); if (atIndex !== -1) { - onUpdateHtml(`${currentHtml.substr(0, atIndex)}${insertedHtml} `); - const messageInput = document.getElementById(inputId)!; + const newHtml = `${htmlBeforeSelection.substr(0, atIndex)}${insertedHtml} `; + const htmlAfterSelection = containerEl.innerHTML.substring(htmlBeforeSelection.length); + onUpdateHtml(`${newHtml}${htmlAfterSelection}`); + requestAnimationFrame(() => { - focusEditableElement(messageInput, forceFocus); + focusEditableElement(containerEl, forceFocus); }); } unmarkIsOpen(); - }, [htmlRef, inputId, onUpdateHtml, unmarkIsOpen]); + }, [htmlBeforeSelection, inputSelector, onUpdateHtml, unmarkIsOpen]); return { isMentionTooltipOpen: isOpen,