diff --git a/src/components/middle/composer/MentionTooltip.tsx b/src/components/middle/composer/MentionTooltip.tsx index a566b02a7..7b65bb29e 100644 --- a/src/components/middle/composer/MentionTooltip.tsx +++ b/src/components/middle/composer/MentionTooltip.tsx @@ -45,6 +45,12 @@ const MentionTooltip: FC = ({ onInsertUserName(user, forceFocus); }, [onInsertUserName]); + const handleClick = useCallback((e: React.MouseEvent, id: string) => { + e.preventDefault(); + + handleUserSelect(id); + }, [handleUserSelect]); + const handleSelectMention = useCallback((member: ApiUser) => { handleUserSelect(member.id, true); }, [handleUserSelect]); @@ -93,8 +99,8 @@ const MentionTooltip: FC = ({ handleUserSelect(id)} + onClick={handleClick} + clickArg={id} focus={selectedMentionIndex === index} > ${getUserFirstOrLastName(user)}`; + >${userFirstOrLastName}`; const inputEl = inputRef.current!; const htmlBeforeSelection = getHtmlBeforeSelection(inputEl); const fixedHtmlBeforeSelection = cleanWebkitNewLines(htmlBeforeSelection); const atIndex = fixedHtmlBeforeSelection.lastIndexOf('@'); + const shiftCaretPosition = (mainUsername ? mainUsername.length + 1 : userFirstOrLastName.length) + - (fixedHtmlBeforeSelection.length - atIndex); if (atIndex !== -1) { const newHtml = `${fixedHtmlBeforeSelection.substr(0, atIndex)}${htmlToInsert} `; const htmlAfterSelection = cleanWebkitNewLines(inputEl.innerHTML).substring(fixedHtmlBeforeSelection.length); - + const caretPosition = getCaretPosition(inputEl); setHtml(`${newHtml}${htmlAfterSelection}`); requestAnimationFrame(() => { + const newCaretPosition = caretPosition + shiftCaretPosition + 1; focusEditableElement(inputEl, forceFocus); + if (newCaretPosition >= 0) { + setCaretPosition(inputEl, newCaretPosition); + } }); } diff --git a/src/util/selection.ts b/src/util/selection.ts index 5dbce7be8..9c9ae82b9 100644 --- a/src/util/selection.ts +++ b/src/util/selection.ts @@ -48,3 +48,47 @@ export function getHtmlBeforeSelection(container?: HTMLElement, useCommonAncesto return extractorEl.innerHTML; } + +// https://stackoverflow.com/a/3976125 +export function getCaretPosition(element: HTMLElement) { + let caretPosition = 0; + const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + return caretPosition; + } + + const range = selection.getRangeAt(0); + const caretRange = range.cloneRange(); + caretRange.selectNodeContents(element); + caretRange.setEnd(range.endContainer, range.endOffset); + caretPosition = caretRange.toString().length; + + return caretPosition; +} + +// https://stackoverflow.com/a/36953852 +export function setCaretPosition(element: Node, position: number) { + for (const node of element.childNodes) { + if (node.nodeType === Node.TEXT_NODE) { + if ((node as Text).length >= position) { + const range = document.createRange(); + const selection = window.getSelection()!; + range.setStart(node, position); + range.collapse(true); + selection.removeAllRanges(); + selection.addRange(range); + + return -1; + } else { + position -= 'length' in node ? node.length as number : 0; + } + } else { + position = setCaretPosition(node, position); + if (position === -1) { + return -1; + } + } + } + + return position; +}