diff --git a/src/models/selection.js b/src/models/selection.js index 7e186318f..38e2c9f4a 100644 --- a/src/models/selection.js +++ b/src/models/selection.js @@ -595,6 +595,32 @@ class Selection extends new Record(DEFAULTS) { : this.merge({ focusOffset: this.focusOffset + n }) } + /** + * Move the start key, while preserving the direction + * + * @param {String} key + * @return {Selection} selection + */ + + moveStartTo(key, offset = 0) { + return this.isBackward + ? this.merge({ focusKey: key, focusOffset: offset }) + : this.merge({ anchorKey: key, anchorOffset: offset }) + } + + /** + * Move the end key, while preserving the direction + * + * @param {String} key + * @return {Selection} selection + */ + + moveEndTo(key, offset = 0) { + return this.isBackward + ? this.merge({ anchorKey: key, anchorOffset: offset }) + : this.merge({ focusKey: key, focusOffset: offset }) + } + /** * Extend the focus point to the start of a `node`. * @@ -625,6 +651,23 @@ class Selection extends new Record(DEFAULTS) { }) } + /** + * Unset the selection + * + * @return {Selection} selection + */ + + unset() { + return this.merge({ + anchorKey: null, + anchorOffset: 0, + focusKey: null, + focusOffset: 0, + isFocused: false, + isBackward: false + }) + } + } /** diff --git a/src/transforms/apply-operation.js b/src/transforms/apply-operation.js index 32720c0c4..a459b8301 100644 --- a/src/transforms/apply-operation.js +++ b/src/transforms/apply-operation.js @@ -178,12 +178,14 @@ function moveNode(state, operation) { let { document } = state const node = document.assertPath(path) + // Remove the node from its current parent let parent = document.getParent(node) const isParent = document == parent const index = parent.nodes.indexOf(node) parent = parent.removeNode(index) document = isParent ? parent : document.updateDescendant(parent) + // Insert the new node to its new parent let target = document.assertPath(newPath) const isTarget = document == target target = target.insertNode(newIndex, node) @@ -221,14 +223,56 @@ function removeMark(state, operation) { function removeNode(state, operation) { const { path } = operation - let { document } = state + let { document, selection } = state + const { startKey, endKey } = selection + + // Preserve previous document + const prevDocument = document + + // Update the document const node = document.assertPath(path) let parent = document.getParent(node) const index = parent.nodes.indexOf(node) const isParent = document == parent parent = parent.removeNode(index) document = isParent ? parent : document.updateDescendant(parent) - state = state.merge({ document }) + + function getRemoved(key) { + if (key === node.key) return node + if (node.kind == 'text') return null + return node.getDescendant(key) + } + + // Update the selection, if one of the anchor/focus has been removed + const startDesc = startKey ? getRemoved(startKey) : null + const endDesc = endKey ? getRemoved(endKey) : null + + if (startDesc) { + const prevText = prevDocument.getTexts() + .takeUntil(text => text.key == startKey) + .filter(text => !getRemoved(text.key)) + .last() + + if (!prevText) selection = selection.unset() + else selection = selection.moveStartTo(prevText.key, prevText.length) + } + if (endDesc) { + // The whole selection is inside the node, we collapse to the previous text node + if (startKey == endKey) { + selection = selection.collapseToStart() + } else { + const nextText = prevDocument.getTexts() + .skipUntil(text => text.key == startKey) + .slice(1) + .filter(text => !getRemoved(text.key)) + .first() + + if (!nextText) selection = selection.unset() + else selection = selection.moveEndTo(nextText.key, 0) + } + } + + state = state.merge({ document, selection }) return state }