add converted prototype code
							parent
							
								
									6599d605cd
								
							
						
					
					
						commit
						9f98a6c0e6
					
				| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getCaretPosition(editor) {
 | 
				
			||||||
 | 
					    const sel = document.getSelection();
 | 
				
			||||||
 | 
					    const atNodeEnd = sel.focusOffset === sel.focusNode.textContent.length;
 | 
				
			||||||
 | 
					    let position = sel.focusOffset;
 | 
				
			||||||
 | 
					    let node = sel.focusNode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // when deleting the last character of a node,
 | 
				
			||||||
 | 
					    // the caret gets reported as being after the focusOffset-th node,
 | 
				
			||||||
 | 
					    // with the focusNode being the editor
 | 
				
			||||||
 | 
					    if (node === editor) {
 | 
				
			||||||
 | 
					        let position = 0;
 | 
				
			||||||
 | 
					        for (let i = 0; i < sel.focusOffset; ++i) {
 | 
				
			||||||
 | 
					            position += editor.childNodes[i].textContent.length;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {position, atNodeEnd: false};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // first make sure we're at the level of a direct child of editor
 | 
				
			||||||
 | 
					    if (node.parentElement !== editor) {
 | 
				
			||||||
 | 
					        // include all preceding siblings of the non-direct editor children
 | 
				
			||||||
 | 
					        while (node.previousSibling) {
 | 
				
			||||||
 | 
					            node = node.previousSibling;
 | 
				
			||||||
 | 
					            position += node.textContent.length;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // then move up
 | 
				
			||||||
 | 
					        // I guess technically there could be preceding text nodes in the parents here as well,
 | 
				
			||||||
 | 
					        // but we're assuming there are no mixed text and element nodes
 | 
				
			||||||
 | 
					        while (node.parentElement !== editor) {
 | 
				
			||||||
 | 
					            node = node.parentElement;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // now include the text length of all preceding direct editor children
 | 
				
			||||||
 | 
					    while (node.previousSibling) {
 | 
				
			||||||
 | 
					        node = node.previousSibling;
 | 
				
			||||||
 | 
					        position += node.textContent.length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        const {focusOffset, focusNode} = sel;
 | 
				
			||||||
 | 
					        console.log("selection", {focusOffset, focusNode, position, atNodeEnd});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {position, atNodeEnd};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setCaretPosition(editor, caretPosition) {
 | 
				
			||||||
 | 
					    if (caretPosition) {
 | 
				
			||||||
 | 
					        let focusNode = editor.childNodes[caretPosition.index];
 | 
				
			||||||
 | 
					        if (!focusNode) {
 | 
				
			||||||
 | 
					            focusNode = editor;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // make sure we have a text node
 | 
				
			||||||
 | 
					            if (focusNode.nodeType === Node.ELEMENT_NODE) {
 | 
				
			||||||
 | 
					                focusNode = focusNode.childNodes[0];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const sel = document.getSelection();
 | 
				
			||||||
 | 
					        sel.removeAllRanges();
 | 
				
			||||||
 | 
					        const range = document.createRange();
 | 
				
			||||||
 | 
					        range.setStart(focusNode, caretPosition.offset);
 | 
				
			||||||
 | 
					        range.collapse(true);
 | 
				
			||||||
 | 
					        sel.addRange(range);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function firstDiff(a, b) {
 | 
				
			||||||
 | 
					    const compareLen = Math.min(a.length, b.length);
 | 
				
			||||||
 | 
					    for (let i = 0; i < compareLen; ++i) {
 | 
				
			||||||
 | 
					        if (a[i] !== b[i]) {
 | 
				
			||||||
 | 
					            return i;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return compareLen;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function lastDiff(a, b) {
 | 
				
			||||||
 | 
					    const compareLen = Math.min(a.length, b.length);
 | 
				
			||||||
 | 
					    for (let i = 0; i < compareLen; ++i) {
 | 
				
			||||||
 | 
					        if (a[a.length - i] !== b[b.length - i]) {
 | 
				
			||||||
 | 
					            return i;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return compareLen;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function diffStringsAtEnd(oldStr, newStr) {
 | 
				
			||||||
 | 
					    const len = Math.min(oldStr.length, newStr.length);
 | 
				
			||||||
 | 
					    const startInCommon = oldStr.substr(0, len) === newStr.substr(0, len);
 | 
				
			||||||
 | 
					    if (startInCommon && oldStr.length > newStr.length) {
 | 
				
			||||||
 | 
					        return {removed: oldStr.substr(len), at: len};
 | 
				
			||||||
 | 
					    } else if (startInCommon && oldStr.length < newStr.length) {
 | 
				
			||||||
 | 
					        return {added: newStr.substr(len), at: len};
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const commonStartLen = firstDiff(oldStr, newStr);
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            removed: oldStr.substr(commonStartLen),
 | 
				
			||||||
 | 
					            added: newStr.substr(commonStartLen),
 | 
				
			||||||
 | 
					            at: commonStartLen,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function diffDeletion(oldStr, newStr) {
 | 
				
			||||||
 | 
					    if (oldStr === newStr) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const firstDiffIdx = firstDiff(oldStr, newStr);
 | 
				
			||||||
 | 
					    const lastDiffIdx = oldStr.length - lastDiff(oldStr, newStr) + 1;
 | 
				
			||||||
 | 
					    return {at: firstDiffIdx, removed: oldStr.substring(firstDiffIdx, lastDiffIdx)};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function diffInsertion(oldStr, newStr) {
 | 
				
			||||||
 | 
					    const diff = diffDeletion(newStr, oldStr);
 | 
				
			||||||
 | 
					    if (diff.removed) {
 | 
				
			||||||
 | 
					        return {at: diff.at, added: diff.removed};
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return diff;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function diffAtCaret(oldValue, newValue, caretPosition) {
 | 
				
			||||||
 | 
					    const diffLen = newValue.length - oldValue.length;
 | 
				
			||||||
 | 
					    const caretPositionBeforeInput = caretPosition - diffLen;
 | 
				
			||||||
 | 
					    const oldValueBeforeCaret = oldValue.substr(0, caretPositionBeforeInput);
 | 
				
			||||||
 | 
					    const newValueBeforeCaret = newValue.substr(0, caretPosition);
 | 
				
			||||||
 | 
					    return diffStringsAtEnd(oldValueBeforeCaret, newValueBeforeCaret);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,174 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BasePart {
 | 
				
			||||||
 | 
					    constructor(text = "") {
 | 
				
			||||||
 | 
					        this._text = text;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acceptsInsertion(chr) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acceptsRemoval(position, chr) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    merge(part) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    split(offset) {
 | 
				
			||||||
 | 
					        const splitText = this.text.substr(offset);
 | 
				
			||||||
 | 
					        this._text = this.text.substr(0, offset);
 | 
				
			||||||
 | 
					        return new PlainPart(splitText);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // removes len chars, or returns the plain text this part should be replaced with
 | 
				
			||||||
 | 
					    // if the part would become invalid if it removed everything.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: this should probably return the Part and caret position within this should be replaced with
 | 
				
			||||||
 | 
					    remove(offset, len) {
 | 
				
			||||||
 | 
					        // validate
 | 
				
			||||||
 | 
					        const strWithRemoval = this.text.substr(0, offset) + this.text.substr(offset + len);
 | 
				
			||||||
 | 
					        for(let i = offset; i < (len + offset); ++i) {
 | 
				
			||||||
 | 
					            const chr = this.text.charAt(i);
 | 
				
			||||||
 | 
					            if (!this.acceptsRemoval(i, chr)) {
 | 
				
			||||||
 | 
					                return strWithRemoval;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this._text = strWithRemoval;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // append str, returns the remaining string if a character was rejected.
 | 
				
			||||||
 | 
					    appendUntilRejected(str) {
 | 
				
			||||||
 | 
					        for(let i = 0; i < str.length; ++i) {
 | 
				
			||||||
 | 
					            const chr = str.charAt(i);
 | 
				
			||||||
 | 
					            if (!this.acceptsInsertion(chr)) {
 | 
				
			||||||
 | 
					                this._text = this._text + str.substr(0, i);
 | 
				
			||||||
 | 
					                return str.substr(i);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this._text = this._text + str;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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) {
 | 
				
			||||||
 | 
					        for(let i = 0; i < str.length; ++i) {
 | 
				
			||||||
 | 
					            const chr = str.charAt(i);
 | 
				
			||||||
 | 
					            if (!this.acceptsInsertion(chr)) {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const beforeInsert = this._text.substr(0, offset);
 | 
				
			||||||
 | 
					        const afterInsert = this._text.substr(offset);
 | 
				
			||||||
 | 
					        this._text = beforeInsert + str + afterInsert;
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    trim(len) {
 | 
				
			||||||
 | 
					        const remaining = this._text.substr(len);
 | 
				
			||||||
 | 
					        this._text = this._text.substr(0, len);
 | 
				
			||||||
 | 
					        return remaining;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get text() {
 | 
				
			||||||
 | 
					        return this._text;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PlainPart extends BasePart {
 | 
				
			||||||
 | 
					    acceptsInsertion(chr) {
 | 
				
			||||||
 | 
					        return chr !== "@" && chr !== "#";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    toDOMNode() {
 | 
				
			||||||
 | 
					        return document.createTextNode(this.text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    merge(part) {
 | 
				
			||||||
 | 
					        if (part.type === this.type) {
 | 
				
			||||||
 | 
					            this._text = this.text + part.text;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get type() {
 | 
				
			||||||
 | 
					        return "plain";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateDOMNode(node) {
 | 
				
			||||||
 | 
					        if (node.textContent !== this.text) {
 | 
				
			||||||
 | 
					            // console.log("changing plain text from", node.textContent, "to", this.text);
 | 
				
			||||||
 | 
					            node.textContent = this.text;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canUpdateDOMNode(node) {
 | 
				
			||||||
 | 
					        return node.nodeType === Node.TEXT_NODE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PillPart extends BasePart {
 | 
				
			||||||
 | 
					    acceptsInsertion(chr) {
 | 
				
			||||||
 | 
					        return chr !== " ";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acceptsRemoval(position, chr) {
 | 
				
			||||||
 | 
					        return position !== 0;  //if you remove initial # or @, pill should become plain
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    toDOMNode() {
 | 
				
			||||||
 | 
					        const container = document.createElement("span");
 | 
				
			||||||
 | 
					        container.className = this.type;
 | 
				
			||||||
 | 
					        container.appendChild(document.createTextNode(this.text));
 | 
				
			||||||
 | 
					        return container;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateDOMNode(node) {
 | 
				
			||||||
 | 
					        const textNode = node.childNodes[0];
 | 
				
			||||||
 | 
					        if (textNode.textContent !== this.text) {
 | 
				
			||||||
 | 
					            // console.log("changing pill text from", textNode.textContent, "to", this.text);
 | 
				
			||||||
 | 
					            textNode.textContent = this.text;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (node.className !== this.type) {
 | 
				
			||||||
 | 
					            // console.log("turning", node.className, "into", this.type);
 | 
				
			||||||
 | 
					            node.className = this.type;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canUpdateDOMNode(node) {
 | 
				
			||||||
 | 
					        return node.nodeType === Node.ELEMENT_NODE &&
 | 
				
			||||||
 | 
					               node.nodeName === "SPAN" &&
 | 
				
			||||||
 | 
					               node.childNodes.length === 1 &&
 | 
				
			||||||
 | 
					               node.childNodes[0].nodeType === Node.TEXT_NODE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class RoomPillPart extends PillPart {
 | 
				
			||||||
 | 
					    get type() {
 | 
				
			||||||
 | 
					        return "room-pill";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserPillPart extends PillPart {
 | 
				
			||||||
 | 
					    get type() {
 | 
				
			||||||
 | 
					        return "user-pill";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue