diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index f4b14fe833..b6da536276 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -25,10 +25,12 @@ import { formatRangeAsQuote, formatRangeAsCode, formatInline, + replaceRangeAndMoveCaret, } from '../../../editor/operations'; import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom'; import Autocomplete from '../rooms/Autocomplete'; import {autoCompleteCreator} from '../../../editor/parts'; +import {parsePlainTextMessage} from '../../../editor/deserialize'; import {renderModel} from '../../../editor/render'; import {Room} from 'matrix-js-sdk'; import TypingStore from "../../../stores/TypingStore"; @@ -172,6 +174,18 @@ export default class BasicMessageEditor extends React.Component { this._onInput({inputType: "insertCompositionText"}); } + _onPaste = (event) => { + const {model} = this.props; + const {partCreator} = model; + const text = event.clipboardData.getData("text/plain"); + if (text) { + const range = getRangeForSelection(this._editorRef, model, document.getSelection()); + const parts = parsePlainTextMessage(text, partCreator); + replaceRangeAndMoveCaret(range, parts); + event.preventDefault(); + } + } + _onInput = (event) => { // ignore any input while doing IME compositions if (this._isIMEComposing) { @@ -495,6 +509,7 @@ export default class BasicMessageEditor extends React.Component { tabIndex="1" onBlur={this._onBlur} onFocus={this._onFocus} + onPaste={this._onPaste} onKeyDown={this._onKeyDown} ref={ref => this._editorRef = ref} aria-label={this.props.label} diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 08c66f592a..925d0d1ab3 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -232,7 +232,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { return parts; } -function parsePlainTextMessage(body, partCreator, isQuotedMessage) { +export function parsePlainTextMessage(body, partCreator, isQuotedMessage) { const lines = body.split("\n"); const parts = lines.reduce((parts, line, i) => { if (isQuotedMessage) { diff --git a/src/editor/operations.js b/src/editor/operations.js index e6ed2ba0e5..4645e7d805 100644 --- a/src/editor/operations.js +++ b/src/editor/operations.js @@ -18,7 +18,8 @@ limitations under the License. * Some common queries and transformations on the editor model */ -export function replaceRangeAndExpandSelection(model, range, newParts) { +export function replaceRangeAndExpandSelection(range, newParts) { + const {model} = range; model.transform(() => { const oldLen = range.length; const addedLen = range.replace(newParts); @@ -28,6 +29,17 @@ export function replaceRangeAndExpandSelection(model, range, newParts) { }); } +export function replaceRangeAndMoveCaret(range, newParts) { + const {model} = range; + model.transform(() => { + const oldLen = range.length; + const addedLen = range.replace(newParts); + const firstOffset = range.start.asOffset(model); + const lastOffset = firstOffset.add(oldLen + addedLen); + return lastOffset.asPosition(model); + }); +} + export function rangeStartsAtBeginningOfLine(range) { const {model} = range; const startsWithPartial = range.start.offset !== 0; @@ -63,7 +75,7 @@ export function formatRangeAsQuote(range) { } parts.push(partCreator.newline()); - replaceRangeAndExpandSelection(model, range, parts); + replaceRangeAndExpandSelection(range, parts); } export function formatRangeAsCode(range) { @@ -85,7 +97,7 @@ export function formatRangeAsCode(range) { parts.unshift(partCreator.plain("`")); parts.push(partCreator.plain("`")); } - replaceRangeAndExpandSelection(model, range, parts); + replaceRangeAndExpandSelection(range, parts); } export function formatInline(range, prefix, suffix = prefix) { @@ -93,5 +105,5 @@ export function formatInline(range, prefix, suffix = prefix) { const {partCreator} = model; parts.unshift(partCreator.plain(prefix)); parts.push(partCreator.plain(suffix)); - replaceRangeAndExpandSelection(model, range, parts); + replaceRangeAndExpandSelection(range, parts); }