Composer: Preserve caret position on mention insert (#2849)
This commit is contained in:
parent
4cbe9c1112
commit
2c8b22b964
@ -45,6 +45,12 @@ const MentionTooltip: FC<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
<ListItem
|
||||
key={id}
|
||||
className="chat-item-clickable scroll-item"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleUserSelect(id)}
|
||||
onClick={handleClick}
|
||||
clickArg={id}
|
||||
focus={selectedMentionIndex === index}
|
||||
>
|
||||
<PrivateChatInfo
|
||||
|
||||
@ -12,7 +12,7 @@ import { filterUsersByName, getMainUsername, getUserFirstOrLastName } from '../.
|
||||
import { prepareForRegExp } from '../helpers/prepareForRegExp';
|
||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||
import { pickTruthy, unique } from '../../../../util/iteratees';
|
||||
import { getHtmlBeforeSelection } from '../../../../util/selection';
|
||||
import { getCaretPosition, getHtmlBeforeSelection, setCaretPosition } from '../../../../util/selection';
|
||||
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
||||
@ -96,6 +96,7 @@ export default function useMentionTooltip(
|
||||
}
|
||||
|
||||
const mainUsername = getMainUsername(user);
|
||||
const userFirstOrLastName = getUserFirstOrLastName(user) || '';
|
||||
const htmlToInsert = mainUsername
|
||||
? `@${mainUsername}`
|
||||
: `<a
|
||||
@ -104,21 +105,27 @@ export default function useMentionTooltip(
|
||||
data-user-id="${user.id}"
|
||||
contenteditable="false"
|
||||
dir="auto"
|
||||
>${getUserFirstOrLastName(user)}</a>`;
|
||||
>${userFirstOrLastName}</a>`;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user