diff --git a/ts/components/CompositionInput.tsx b/ts/components/CompositionInput.tsx index 08b2b0ff30..a1a5620908 100644 --- a/ts/components/CompositionInput.tsx +++ b/ts/components/CompositionInput.tsx @@ -8,7 +8,7 @@ import ReactQuill from 'react-quill'; import classNames from 'classnames'; import emojiRegex from 'emoji-regex'; import { Manager, Reference } from 'react-popper'; -import Quill, { KeyboardStatic } from 'quill'; +import Quill, { KeyboardStatic, RangeStatic } from 'quill'; import Op from 'quill-delta/dist/Op'; import { EmojiBlot, EmojiCompletion } from '../quill/emoji'; @@ -89,6 +89,10 @@ export const CompositionInput: React.ComponentType = props => { const [emojiCompletionElement, setEmojiCompletionElement] = React.useState< JSX.Element >(); + const [ + lastSelectionRange, + setLastSelectionRange, + ] = React.useState(null); const emojiCompletionRef = React.useRef(); const quillRef = React.useRef(); @@ -168,19 +172,20 @@ export const CompositionInput: React.ComponentType = props => { const range = quill.getSelection(); - if (range === null) { + const insertionRange = range || lastSelectionRange; + if (insertionRange === null) { return; } const emoji = convertShortName(e.shortName, e.skinTone); const delta = new Delta() - .retain(range.index) - .delete(range.length) + .retain(insertionRange.index) + .delete(insertionRange.length) .insert({ emoji }); quill.updateContents(delta, 'user'); - quill.setSelection(range.index + 1, 0, 'user'); + quill.setSelection(insertionRange.index + 1, 0, 'user'); }; const reset = () => { @@ -433,6 +438,15 @@ export const CompositionInput: React.ComponentType = props => { quill.setSelection(quill.getLength(), 0); }); + quill.on( + 'selection-change', + (newRange: RangeStatic, oldRange: RangeStatic) => { + // If we lose focus, store the last edit point for emoji insertion + if (newRange === null) { + setLastSelectionRange(oldRange); + } + } + ); quillRef.current = quill; emojiCompletionRef.current = quill.getModule('emojiCompletion'); } diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 527aac6ae2..2f04a04210 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -14544,15 +14544,6 @@ "rule": "React-useRef", "path": "ts/components/CompositionInput.js", "line": " const emojiCompletionRef = React.useRef();", - "lineNumber": 34, - "reasonCategory": "falseMatch", - "updated": "2020-10-26T19:12:24.410Z", - "reasonDetail": "Doesn't refer to a DOM element." - }, - { - "rule": "React-useRef", - "path": "ts/components/CompositionInput.js", - "line": " const quillRef = React.useRef();", "lineNumber": 35, "reasonCategory": "falseMatch", "updated": "2020-10-26T19:12:24.410Z", @@ -14561,8 +14552,17 @@ { "rule": "React-useRef", "path": "ts/components/CompositionInput.js", - "line": " const scrollerRef = React.useRef(null);", + "line": " const quillRef = React.useRef();", "lineNumber": 36, + "reasonCategory": "falseMatch", + "updated": "2020-10-26T19:12:24.410Z", + "reasonDetail": "Doesn't refer to a DOM element." + }, + { + "rule": "React-useRef", + "path": "ts/components/CompositionInput.js", + "line": " const scrollerRef = React.useRef(null);", + "lineNumber": 37, "reasonCategory": "usageTrusted", "updated": "2020-10-26T19:12:24.410Z", "reasonDetail": "Used with Quill for scrolling." @@ -14571,7 +14571,7 @@ "rule": "React-useRef", "path": "ts/components/CompositionInput.js", "line": " const propsRef = React.useRef(props);", - "lineNumber": 37, + "lineNumber": 38, "reasonCategory": "falseMatch", "updated": "2020-10-26T19:12:24.410Z", "reasonDetail": "Doesn't refer to a DOM element." @@ -15116,4 +15116,4 @@ "reasonCategory": "falseMatch", "updated": "2020-09-08T23:07:22.682Z" } -] +] \ No newline at end of file