Merge pull request #3356 from matrix-org/bwindels/cider-paste-newlines

New composer: handle newlines properly when pasting
This commit is contained in:
Bruno Windels 2019-08-29 16:24:27 +00:00 committed by GitHub
commit c1d7ad40c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 38 deletions

View file

@ -84,6 +84,14 @@ function getTextAndFocusNodeOffset(editor, focusNode, focusOffset) {
foundCaret = true; foundCaret = true;
} }
} }
// usually newlines are entered as new DIV elements,
// but for example while pasting in some browsers, they are still
// converted to BRs, so also take these into account when they
// are not the last element in the DIV.
if (node.tagName === "BR" && node.nextSibling) {
text += "\n";
focusNodeOffset += 1;
}
const nodeText = node.nodeType === Node.TEXT_NODE && getTextNodeValue(node); const nodeText = node.nodeType === Node.TEXT_NODE && getTextNodeValue(node);
if (nodeText) { if (nodeText) {
if (!foundCaret) { if (!foundCaret) {

View file

@ -183,15 +183,14 @@ export default class EditorModel {
if (diff.removed) { if (diff.removed) {
removedOffsetDecrease = this.removeText(position, diff.removed.length); removedOffsetDecrease = this.removeText(position, diff.removed.length);
} }
const canOpenAutoComplete = inputType !== "insertFromPaste" && inputType !== "insertFromDrop";
let addedLen = 0; let addedLen = 0;
if (diff.added) { if (diff.added) {
// these shouldn't trigger auto-complete, you just want to append a piece of text addedLen = this._addText(position, diff.added, inputType);
addedLen = this._addText(position, diff.added, {validate: canOpenAutoComplete});
} }
this._mergeAdjacentParts(); this._mergeAdjacentParts();
const caretOffset = diff.at - removedOffsetDecrease + addedLen; const caretOffset = diff.at - removedOffsetDecrease + addedLen;
let newPosition = this.positionForOffset(caretOffset, true); let newPosition = this.positionForOffset(caretOffset, true);
const canOpenAutoComplete = inputType !== "insertFromPaste" && inputType !== "insertFromDrop";
const acPromise = this._setActivePart(newPosition, canOpenAutoComplete); const acPromise = this._setActivePart(newPosition, canOpenAutoComplete);
if (this._transformCallback) { if (this._transformCallback) {
const transformAddedLen = this._transform(newPosition, inputType, diff); const transformAddedLen = this._transform(newPosition, inputType, diff);
@ -333,22 +332,20 @@ export default class EditorModel {
* inserts `str` into the model at `pos`. * inserts `str` into the model at `pos`.
* @param {Object} pos * @param {Object} pos
* @param {string} str * @param {string} str
* @param {Object} options * @param {string} inputType the source of the input, see html InputEvent.inputType
* @param {bool} options.validate Whether characters will be validated by the part. * @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. * Validating allows the inserted text to be parsed according to the part rules.
* @return {Number} how far from position (in characters) the insertion ended. * @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. * This can be more than the length of `str` when crossing non-editable parts, which are skipped.
*/ */
_addText(pos, str, {validate=true}) { _addText(pos, str, inputType) {
let {index} = pos; let {index} = pos;
const {offset} = pos; const {offset} = pos;
let addLen = str.length; let addLen = str.length;
const part = this._parts[index]; const part = this._parts[index];
if (part) { if (part) {
if (part.canEdit) { if (part.canEdit) {
if (validate && part.validateAndInsert(offset, str)) { if (part.validateAndInsert(offset, str, inputType)) {
str = null;
} else if (!validate && part.insert(offset, str)) {
str = null; str = null;
} else { } else {
const splitPart = part.split(offset); const splitPart = part.split(offset);
@ -367,13 +364,8 @@ export default class EditorModel {
index = 0; index = 0;
} }
while (str) { while (str) {
const newPart = this._partCreator.createPartForInput(str, index); const newPart = this._partCreator.createPartForInput(str, index, inputType);
if (validate) { str = newPart.appendUntilRejected(str, inputType);
str = newPart.appendUntilRejected(str);
} else {
newPart.insert(0, str);
str = null;
}
this._insertPart(index, newPart); this._insertPart(index, newPart);
index += 1; index += 1;
} }

View file

@ -23,7 +23,7 @@ class BasePart {
this._text = text; this._text = text;
} }
acceptsInsertion(chr) { acceptsInsertion(chr, offset, inputType) {
return true; return true;
} }
@ -56,10 +56,11 @@ class BasePart {
} }
// append str, returns the remaining string if a character was rejected. // append str, returns the remaining string if a character was rejected.
appendUntilRejected(str) { appendUntilRejected(str, inputType) {
const offset = this.text.length;
for (let i = 0; i < str.length; ++i) { for (let i = 0; i < str.length; ++i) {
const chr = str.charAt(i); const chr = str.charAt(i);
if (!this.acceptsInsertion(chr, i)) { if (!this.acceptsInsertion(chr, offset + i, inputType)) {
this._text = this._text + str.substr(0, i); this._text = this._text + str.substr(0, i);
return str.substr(i); return str.substr(i);
} }
@ -69,10 +70,10 @@ class BasePart {
// inserts str at offset if all the characters in str were accepted, otherwise don't do anything // 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. // return whether the str was accepted or not.
validateAndInsert(offset, str) { validateAndInsert(offset, str, inputType) {
for (let i = 0; i < str.length; ++i) { for (let i = 0; i < str.length; ++i) {
const chr = str.charAt(i); const chr = str.charAt(i);
if (!this.acceptsInsertion(chr)) { if (!this.acceptsInsertion(chr, offset + i, inputType)) {
return false; return false;
} }
} }
@ -82,16 +83,6 @@ class BasePart {
return true; 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() {} createAutoComplete() {}
trim(len) { trim(len) {
@ -119,8 +110,15 @@ class BasePart {
// exported for unit tests, should otherwise only be used through PartCreator // exported for unit tests, should otherwise only be used through PartCreator
export class PlainPart extends BasePart { export class PlainPart extends BasePart {
acceptsInsertion(chr) { acceptsInsertion(chr, offset, inputType) {
return chr !== "@" && chr !== "#" && chr !== ":" && chr !== "\n"; if (chr === "\n") {
return false;
}
// when not pasting or dropping text, reject characters that should start a pill candidate
if (inputType !== "insertFromPaste" && inputType !== "insertFromDrop") {
return chr !== "@" && chr !== "#" && chr !== ":";
}
return true;
} }
toDOMNode() { toDOMNode() {
@ -141,7 +139,6 @@ export class PlainPart extends BasePart {
updateDOMNode(node) { updateDOMNode(node) {
if (node.textContent !== this.text) { if (node.textContent !== this.text) {
// console.log("changing plain text from", node.textContent, "to", this.text);
node.textContent = this.text; node.textContent = this.text;
} }
} }
@ -211,8 +208,8 @@ class PillPart extends BasePart {
} }
class NewlinePart extends BasePart { class NewlinePart extends BasePart {
acceptsInsertion(chr, i) { acceptsInsertion(chr, offset) {
return (this.text.length + i) === 0 && chr === "\n"; return offset === 0 && chr === "\n";
} }
acceptsRemoval(position, chr) { acceptsRemoval(position, chr) {
@ -331,11 +328,11 @@ class PillCandidatePart extends PlainPart {
return this._autoCompleteCreator.create(updateCallback); return this._autoCompleteCreator.create(updateCallback);
} }
acceptsInsertion(chr, i) { acceptsInsertion(chr, offset, inputType) {
if ((this.text.length + i) === 0) { if (offset === 0) {
return true; return true;
} else { } else {
return super.acceptsInsertion(chr, i); return super.acceptsInsertion(chr, offset, inputType);
} }
} }