add converted prototype code
This commit is contained in:
parent
6599d605cd
commit
9f98a6c0e6
4 changed files with 499 additions and 0 deletions
169
src/editor/model.js
Normal file
169
src/editor/model.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {PlainPart, RoomPillPart, UserPillPart} from "./parts";
|
||||
import {diffAtCaret, diffDeletion} from "./diff";
|
||||
|
||||
export default class EditorModel {
|
||||
constructor(parts = []) {
|
||||
this._parts = parts;
|
||||
this.actions = null;
|
||||
this._previousValue = parts.reduce((text, p) => text + p.text, "");
|
||||
}
|
||||
|
||||
_insertPart(index, part) {
|
||||
this._parts.splice(index, 0, part);
|
||||
}
|
||||
|
||||
_removePart(index) {
|
||||
this._parts.splice(index, 1);
|
||||
}
|
||||
|
||||
_replacePart(index, part) {
|
||||
this._parts.splice(index, 1, part);
|
||||
}
|
||||
|
||||
get parts() {
|
||||
return this._parts;
|
||||
}
|
||||
|
||||
_diff(newValue, inputType, caret) {
|
||||
if (inputType === "deleteByDrag") {
|
||||
return diffDeletion(this._previousValue, newValue);
|
||||
} else {
|
||||
return diffAtCaret(this._previousValue, newValue, caret.position);
|
||||
}
|
||||
}
|
||||
|
||||
update(newValue, inputType, caret) {
|
||||
const diff = this._diff(newValue, inputType, caret);
|
||||
const position = this._positionForOffset(diff.at, caret.atNodeEnd);
|
||||
console.log("update at", {position, diff});
|
||||
if (diff.removed) {
|
||||
this._removeText(position, diff.removed.length);
|
||||
}
|
||||
if (diff.added) {
|
||||
this._addText(position, diff.added);
|
||||
}
|
||||
this._mergeAdjacentParts();
|
||||
this._previousValue = newValue;
|
||||
const caretOffset = diff.at + (diff.added ? diff.added.length : 0);
|
||||
return this._positionForOffset(caretOffset, true);
|
||||
}
|
||||
|
||||
_mergeAdjacentParts(docPos) {
|
||||
let prevPart = this._parts[0];
|
||||
for (let i = 1; i < this._parts.length; ++i) {
|
||||
let part = this._parts[i];
|
||||
const isEmpty = !part.text.length;
|
||||
const isMerged = !isEmpty && prevPart.merge(part);
|
||||
if (isEmpty || isMerged) {
|
||||
// remove empty or merged part
|
||||
part = prevPart;
|
||||
this._removePart(i);
|
||||
//repeat this index, as it's removed now
|
||||
--i;
|
||||
}
|
||||
prevPart = part;
|
||||
}
|
||||
}
|
||||
|
||||
_removeText(pos, len) {
|
||||
let {index, offset} = pos;
|
||||
while (len !== 0) {
|
||||
// part might be undefined here
|
||||
let part = this._parts[index];
|
||||
const amount = Math.min(len, part.text.length - offset);
|
||||
const replaceWith = part.remove(offset, amount);
|
||||
if (typeof replaceWith === "string") {
|
||||
this._replacePart(index, new PlainPart(replaceWith));
|
||||
}
|
||||
part = this._parts[index];
|
||||
// remove empty part
|
||||
if (!part.text.length) {
|
||||
this._removePart(index);
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
len -= amount;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_addText(pos, str, actions) {
|
||||
let {index, offset} = pos;
|
||||
const part = this._parts[index];
|
||||
if (part) {
|
||||
if (part.insertAll(offset, str)) {
|
||||
str = null;
|
||||
} else {
|
||||
// console.log("splitting", offset, [part.text]);
|
||||
const splitPart = part.split(offset);
|
||||
// console.log("splitted", [part.text, splitPart.text]);
|
||||
index += 1;
|
||||
this._insertPart(index, splitPart);
|
||||
}
|
||||
}
|
||||
while (str) {
|
||||
let newPart;
|
||||
switch (str[0]) {
|
||||
case "#":
|
||||
newPart = new RoomPillPart();
|
||||
break;
|
||||
case "@":
|
||||
newPart = new UserPillPart();
|
||||
break;
|
||||
default:
|
||||
newPart = new PlainPart();
|
||||
}
|
||||
str = newPart.appendUntilRejected(str);
|
||||
this._insertPart(index, newPart);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
_positionForOffset(totalOffset, atPartEnd) {
|
||||
let currentOffset = 0;
|
||||
const index = this._parts.findIndex(part => {
|
||||
const partLen = part.text.length;
|
||||
if (
|
||||
(atPartEnd && (currentOffset + partLen) >= totalOffset) ||
|
||||
(!atPartEnd && (currentOffset + partLen) > totalOffset)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
currentOffset += partLen;
|
||||
return false;
|
||||
});
|
||||
|
||||
return new DocumentPosition(index, totalOffset - currentOffset);
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentPosition {
|
||||
constructor(index, offset) {
|
||||
this._index = index;
|
||||
this._offset = offset;
|
||||
}
|
||||
|
||||
get index() {
|
||||
return this._index;
|
||||
}
|
||||
|
||||
get offset() {
|
||||
return this._offset;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue