diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index cf306d80bb..f51348ce04 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -280,6 +280,7 @@ export default class MessageEditor extends React.Component { } componentWillUnmount() { + this._editorRef.removeEventListener("input", this._onInput, true); const sel = document.getSelection(); const {caret} = getCaretOffsetAndText(this._editorRef, sel); const parts = this.model.serializeParts(); @@ -292,6 +293,9 @@ export default class MessageEditor extends React.Component { this._updateEditorState(); // initial caret position this._initializeCaret(); + // attach input listener by hand so React doesn't proxy the events, + // as the proxied event doesn't support inputType, which we need. + this._editorRef.addEventListener("input", this._onInput, true); this._editorRef.focus(); } @@ -359,7 +363,6 @@ export default class MessageEditor extends React.Component { className="mx_MessageEditor_editor" contentEditable="true" tabIndex="1" - onInput={this._onInput} onKeyDown={this._onKeyDown} ref={ref => this._editorRef = ref} aria-label={_t("Edit message")} diff --git a/src/editor/model.js b/src/editor/model.js index 1080df67ba..530aeef0b2 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -97,24 +97,26 @@ export default class EditorModel { if (diff.removed) { removedOffsetDecrease = this.removeText(position, diff.removed.length); } + const canOpenAutoComplete = inputType !== "insertFromPaste" && inputType !== "insertFromDrop"; let addedLen = 0; if (diff.added) { - addedLen = this._addText(position, diff.added); + // these shouldn't trigger auto-complete, you just want to append a piece of text + addedLen = this._addText(position, diff.added, {validate: canOpenAutoComplete}); } this._mergeAdjacentParts(); const caretOffset = diff.at - removedOffsetDecrease + addedLen; const newPosition = this.positionForOffset(caretOffset, true); - this._setActivePart(newPosition); + this._setActivePart(newPosition, canOpenAutoComplete); this._updateCallback(newPosition); } - _setActivePart(pos) { + _setActivePart(pos, canOpenAutoComplete) { const {index} = pos; const part = this._parts[index]; if (part) { if (index !== this._activePartIdx) { this._activePartIdx = index; - if (this._activePartIdx !== this._autoCompletePartIdx) { + if (canOpenAutoComplete && this._activePartIdx !== this._autoCompletePartIdx) { // else try to create one const ac = part.createAutoComplete(this._onAutoComplete); if (ac) { @@ -217,17 +219,22 @@ export default class EditorModel { * inserts `str` into the model at `pos`. * @param {Object} pos * @param {string} str + * @param {Object} options + * @param {bool} options.validate Whether characters will be validated by the part. + * Validating allows the inserted text to be parsed according to the part rules. * @return {Number} how far from position (in characters) the insertion ended. * This can be more than the length of `str` when crossing non-editable parts, which are skipped. */ - _addText(pos, str) { + _addText(pos, str, {validate=true}) { let {index} = pos; const {offset} = pos; let addLen = str.length; const part = this._parts[index]; if (part) { if (part.canEdit) { - if (part.insertAll(offset, str)) { + if (validate && part.validateAndInsert(offset, str)) { + str = null; + } else if (!validate && part.insert(offset, str)) { str = null; } else { const splitPart = part.split(offset); @@ -240,10 +247,19 @@ export default class EditorModel { addLen += part.text.length - offset; index += 1; } + } else if (index < 0) { + // if position was not found (index: -1, as happens for empty editor) + // reset it to insert as first part + index = 0; } while (str) { const newPart = this._partCreator.createPartForInput(str); - str = newPart.appendUntilRejected(str); + if (validate) { + str = newPart.appendUntilRejected(str); + } else { + newPart.insert(0, str); + str = null; + } this._insertPart(index, newPart); index += 1; } diff --git a/src/editor/parts.js b/src/editor/parts.js index dc2c1e69a2..572a861024 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -69,7 +69,7 @@ class BasePart { // inserts str at offset if all the characters in str were accepted, otherwise don't do anything // return whether the str was accepted or not. - insertAll(offset, str) { + validateAndInsert(offset, str) { for (let i = 0; i < str.length; ++i) { const chr = str.charAt(i); if (!this.acceptsInsertion(chr)) { @@ -82,6 +82,16 @@ class BasePart { return true; } + insert(offset, str) { + if (this.canEdit) { + const beforeInsert = this._text.substr(0, offset); + const afterInsert = this._text.substr(offset); + this._text = beforeInsert + str + afterInsert; + return true; + } + return false; + } + createAutoComplete() {} trim(len) {