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(user, forceFocus);
|
||||||
}, [onInsertUserName]);
|
}, [onInsertUserName]);
|
||||||
|
|
||||||
|
const handleClick = useCallback((e: React.MouseEvent, id: string) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
handleUserSelect(id);
|
||||||
|
}, [handleUserSelect]);
|
||||||
|
|
||||||
const handleSelectMention = useCallback((member: ApiUser) => {
|
const handleSelectMention = useCallback((member: ApiUser) => {
|
||||||
handleUserSelect(member.id, true);
|
handleUserSelect(member.id, true);
|
||||||
}, [handleUserSelect]);
|
}, [handleUserSelect]);
|
||||||
@ -93,8 +99,8 @@ const MentionTooltip: FC<OwnProps> = ({
|
|||||||
<ListItem
|
<ListItem
|
||||||
key={id}
|
key={id}
|
||||||
className="chat-item-clickable scroll-item"
|
className="chat-item-clickable scroll-item"
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
onClick={handleClick}
|
||||||
onClick={() => handleUserSelect(id)}
|
clickArg={id}
|
||||||
focus={selectedMentionIndex === index}
|
focus={selectedMentionIndex === index}
|
||||||
>
|
>
|
||||||
<PrivateChatInfo
|
<PrivateChatInfo
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { filterUsersByName, getMainUsername, getUserFirstOrLastName } from '../.
|
|||||||
import { prepareForRegExp } from '../helpers/prepareForRegExp';
|
import { prepareForRegExp } from '../helpers/prepareForRegExp';
|
||||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||||
import { pickTruthy, unique } from '../../../../util/iteratees';
|
import { pickTruthy, unique } from '../../../../util/iteratees';
|
||||||
import { getHtmlBeforeSelection } from '../../../../util/selection';
|
import { getCaretPosition, getHtmlBeforeSelection, setCaretPosition } from '../../../../util/selection';
|
||||||
|
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
||||||
@ -96,6 +96,7 @@ export default function useMentionTooltip(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mainUsername = getMainUsername(user);
|
const mainUsername = getMainUsername(user);
|
||||||
|
const userFirstOrLastName = getUserFirstOrLastName(user) || '';
|
||||||
const htmlToInsert = mainUsername
|
const htmlToInsert = mainUsername
|
||||||
? `@${mainUsername}`
|
? `@${mainUsername}`
|
||||||
: `<a
|
: `<a
|
||||||
@ -104,21 +105,27 @@ export default function useMentionTooltip(
|
|||||||
data-user-id="${user.id}"
|
data-user-id="${user.id}"
|
||||||
contenteditable="false"
|
contenteditable="false"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>${getUserFirstOrLastName(user)}</a>`;
|
>${userFirstOrLastName}</a>`;
|
||||||
|
|
||||||
const inputEl = inputRef.current!;
|
const inputEl = inputRef.current!;
|
||||||
const htmlBeforeSelection = getHtmlBeforeSelection(inputEl);
|
const htmlBeforeSelection = getHtmlBeforeSelection(inputEl);
|
||||||
const fixedHtmlBeforeSelection = cleanWebkitNewLines(htmlBeforeSelection);
|
const fixedHtmlBeforeSelection = cleanWebkitNewLines(htmlBeforeSelection);
|
||||||
const atIndex = fixedHtmlBeforeSelection.lastIndexOf('@');
|
const atIndex = fixedHtmlBeforeSelection.lastIndexOf('@');
|
||||||
|
const shiftCaretPosition = (mainUsername ? mainUsername.length + 1 : userFirstOrLastName.length)
|
||||||
|
- (fixedHtmlBeforeSelection.length - atIndex);
|
||||||
|
|
||||||
if (atIndex !== -1) {
|
if (atIndex !== -1) {
|
||||||
const newHtml = `${fixedHtmlBeforeSelection.substr(0, atIndex)}${htmlToInsert} `;
|
const newHtml = `${fixedHtmlBeforeSelection.substr(0, atIndex)}${htmlToInsert} `;
|
||||||
const htmlAfterSelection = cleanWebkitNewLines(inputEl.innerHTML).substring(fixedHtmlBeforeSelection.length);
|
const htmlAfterSelection = cleanWebkitNewLines(inputEl.innerHTML).substring(fixedHtmlBeforeSelection.length);
|
||||||
|
const caretPosition = getCaretPosition(inputEl);
|
||||||
setHtml(`${newHtml}${htmlAfterSelection}`);
|
setHtml(`${newHtml}${htmlAfterSelection}`);
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
const newCaretPosition = caretPosition + shiftCaretPosition + 1;
|
||||||
focusEditableElement(inputEl, forceFocus);
|
focusEditableElement(inputEl, forceFocus);
|
||||||
|
if (newCaretPosition >= 0) {
|
||||||
|
setCaretPosition(inputEl, newCaretPosition);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,3 +48,47 @@ export function getHtmlBeforeSelection(container?: HTMLElement, useCommonAncesto
|
|||||||
|
|
||||||
return extractorEl.innerHTML;
|
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