diff --git a/lib/models/selection.js b/lib/models/selection.js index ceb2de400..c16e1be79 100644 --- a/lib/models/selection.js +++ b/lib/models/selection.js @@ -99,6 +99,16 @@ class Selection extends new Record(DEFAULTS) { return this.isBackward == null ? null : !this.isBackward } + /** + * Check whether the selection's keys are not set. + * + * @return {Boolean} + */ + + get isUnset() { + return this.anchorKey == null || this.focusKey == null + } + /** * Get the start key. * @@ -281,19 +291,18 @@ class Selection extends new Record(DEFAULTS) { const { isCollapsed } = selection let { anchorKey, anchorOffset, focusKey, focusOffset, isBackward } = selection - // If the selection isn't formed yet or is malformed, set it to the - // beginning of the node. + // If the selection isn't formed yet or is malformed, ensure that it is + // properly zeroed out. if ( anchorKey == null || focusKey == null || !node.hasDescendant(anchorKey) || !node.hasDescendant(focusKey) ) { - const first = node.getTexts().first() return selection.merge({ - anchorKey: first.key, + anchorKey: null, anchorOffset: 0, - focusKey: first.key, + focusKey: null, focusOffset: 0, isBackward: false }) diff --git a/lib/models/state.js b/lib/models/state.js index 83bd2809f..22760453c 100644 --- a/lib/models/state.js +++ b/lib/models/state.js @@ -42,9 +42,16 @@ class State extends new Record(DEFAULTS) { static create(properties = {}) { if (properties instanceof State) return properties - properties.document = Document.create(properties.document) - properties.selection = Selection.create(properties.selection).normalize(properties.document) - return new State(properties) + + let document = Document.create(properties.document) + let selection = Selection.create(properties.selection) + + if (selection.isUnset) { + const text = document.getTexts().first() + selection = selection.collapseToStartOf(text) + } + + return new State({ document, selection }) } /** diff --git a/lib/transforms/apply-operation.js b/lib/transforms/apply-operation.js index f72894658..74fb31469 100644 --- a/lib/transforms/apply-operation.js +++ b/lib/transforms/apply-operation.js @@ -281,15 +281,17 @@ function setSelection(state, operation) { let properties = { ...operation.properties } let { document, selection } = state - if (properties.anchorPath) { - const anchorNode = document.assertPath(properties.anchorPath) - properties.anchorKey = anchorNode.key + if (properties.anchorPath !== undefined) { + properties.anchorKey = properties.anchorPath === null + ? null + : document.assertPath(properties.anchorPath).key delete properties.anchorPath } - if (properties.focusPath) { - const focusNode = document.assertPath(properties.focusPath) - properties.focusKey = focusNode.key + if (properties.focusPath !== undefined) { + properties.focusKey = properties.focusPath === null + ? null + : document.assertPath(properties.focusPath).key delete properties.focusPath } diff --git a/lib/transforms/at-current-range.js b/lib/transforms/at-current-range.js index 31eb0c7e7..385b47f35 100644 --- a/lib/transforms/at-current-range.js +++ b/lib/transforms/at-current-range.js @@ -80,6 +80,7 @@ export function _delete(transform) { } return transform + .unsetSelection() .deleteAtRange(selection) .moveTo(after) } @@ -154,6 +155,7 @@ export function deleteBackward(transform, n = 1) { } return transform + .unsetSelection() .deleteBackwardAtRange(selection, n) .moveTo(after) } @@ -213,6 +215,7 @@ export function deleteForward(transform, n = 1) { } return transform + .unsetSelection() .deleteForwardAtRange(selection, n) .moveTo(after) } @@ -230,6 +233,7 @@ export function insertBlock(transform, block) { let { document, selection } = state const keys = document.getTexts().map(text => text.key) + transform.unsetSelection() transform.insertBlockAtRange(selection, block) state = transform.state document = state.document @@ -258,6 +262,7 @@ export function insertFragment(transform, fragment) { const lastInline = fragment.getClosestInline(lastText) const beforeTexts = document.getTexts() + transform.unsetSelection() transform.insertFragmentAtRange(selection, fragment) state = transform.state document = state.document @@ -306,6 +311,7 @@ export function insertInline(transform, inline) { const hasVoid = document.hasVoidParent(startText) const keys = document.getTexts().map(text => text.key) + transform.unsetSelection() transform.insertInlineAtRange(selection, inline) state = transform.state document = state.document @@ -353,6 +359,7 @@ export function insertText(transform, text, marks) { marks = marks || selection.marks return transform + .unsetSelection() .insertTextAtRange(selection, text, marks) .moveTo(after) } @@ -397,6 +404,7 @@ export function splitBlock(transform, depth = 1) { let { state } = transform let { document, selection } = state + transform.unsetSelection() transform.splitBlockAtRange(selection, depth) state = transform.state @@ -423,6 +431,7 @@ export function splitInline(transform, depth = Infinity) { let { document, selection } = state let after = selection + transform.unsetSelection() transform.splitInlineAtRange(selection, depth) state = transform.state document = state.document @@ -551,6 +560,7 @@ export function wrapInline(transform, properties) { const { startKey } = selection const previous = document.getPreviousText(startKey) + transform.unsetSelection() transform.wrapInlineAtRange(selection, properties) state = transform.state document = state.document @@ -612,6 +622,7 @@ export function wrapText(transform, prefix, suffix = prefix) { } return transform + .unsetSelection() .wrapTextAtRange(selection, prefix, suffix) .moveTo(after) } diff --git a/lib/transforms/index.js b/lib/transforms/index.js index 83c6977c1..8d750bd1e 100644 --- a/lib/transforms/index.js +++ b/lib/transforms/index.js @@ -124,6 +124,7 @@ import { moveTo, moveToOffsets, moveToRangeOf, + unsetSelection, } from './on-selection' /** @@ -267,6 +268,7 @@ export default { moveTo, moveToOffsets, moveToRangeOf, + unsetSelection, /** * History. diff --git a/lib/transforms/on-selection.js b/lib/transforms/on-selection.js index 18332c194..bd322a26e 100644 --- a/lib/transforms/on-selection.js +++ b/lib/transforms/on-selection.js @@ -364,6 +364,7 @@ export function moveForward(transform, n) { */ export function moveTo(transform, properties) { + debugger return transform.setSelectionOperation(properties) } @@ -398,3 +399,21 @@ export function moveToRangeOf(transform, start, end) { const sel = selection.moveToRangeOf(start, end).normalize(document) return transform.setSelectionOperation(sel) } + +/** + * Unset the selection, removing an association to a node. + * + * @param {Transform} transform + * @return {Transform} + */ + +export function unsetSelection(transform) { + return transform.setSelectionOperation({ + anchorKey: null, + anchorOffset: 0, + focusKey: null, + focusOffset: 0, + isFocused: false, + isBackward: false + }) +} diff --git a/lib/transforms/operations.js b/lib/transforms/operations.js index 0a34bbc6f..f5d7cc5ff 100644 --- a/lib/transforms/operations.js +++ b/lib/transforms/operations.js @@ -361,39 +361,53 @@ export function setSelectionOperation(transform, properties) { const { document, selection } = state const prevProps = {} - if (properties.marks == selection.marks) { + // If the current selection has marks, and the new selection doesn't change + // them in some way, they are old and should be removed. + if (selection.marks && properties.marks == selection.marks) { properties.marks = null } + // Create a dictionary of the previous values for all of the properties that + // are being changed, for the inverse operation. for (const k in properties) { prevProps[k] = selection[k] } + // Resolve the selection keys into paths. if (properties.anchorKey) { properties.anchorPath = document.getPath(properties.anchorKey) - prevProps.anchorPath = document.getPath(prevProps.anchorKey) delete properties.anchorKey + } + + if (prevProps.anchorKey) { + prevProps.anchorPath = document.getPath(prevProps.anchorKey) delete prevProps.anchorKey } if (properties.focusKey) { properties.focusPath = document.getPath(properties.focusKey) - prevProps.focusPath = document.getPath(prevProps.focusKey) delete properties.focusKey + } + + if (prevProps.focusKey) { + prevProps.focusPath = document.getPath(prevProps.focusKey) delete prevProps.focusKey } + // Define an inverse of the operation for undoing. const inverse = [{ type: 'set_selection', properties: prevProps }] + // Define the operation. const operation = { type: 'set_selection', properties, inverse, } + // Apply the operation. return transform.applyOperation(operation) } diff --git a/lib/utils/normalize.js b/lib/utils/normalize.js index a4b83e344..641738626 100644 --- a/lib/utils/normalize.js +++ b/lib/utils/normalize.js @@ -107,7 +107,7 @@ function markProperties(value = {}) { case 'object': { for (const k in value) { if (k == 'data') { - if (value[k] != null) ret[k] = Data.create(value[k]) + if (value[k] !== undefined) ret[k] = Data.create(value[k]) } else { ret[k] = value[k] } @@ -138,10 +138,10 @@ function nodeProperties(value = {}) { break } case 'object': { - if (value.isVoid != null) ret.isVoid = !!value.isVoid + if (value.isVoid !== undefined) ret.isVoid = !!value.isVoid for (const k in value) { if (k == 'data') { - if (value[k] != null) ret[k] = Data.create(value[k]) + if (value[k] !== undefined) ret[k] = Data.create(value[k]) } else { ret[k] = value[k] } @@ -188,12 +188,12 @@ function selectionProperties(value = {}) { switch (typeOf(value)) { case 'object': { - if (value.anchorKey != null) ret.anchorKey = value.anchorKey - if (value.anchorOffset != null) ret.anchorOffset = value.anchorOffset - if (value.focusKey != null) ret.focusKey = value.focusKey - if (value.focusOffset != null) ret.focusOffset = value.focusOffset - if (value.isBackward != null) ret.isBackward = !!value.isBackward - if (value.isFocused != null) ret.isFocused = !!value.isFocused + if (value.anchorKey !== undefined) ret.anchorKey = value.anchorKey + if (value.anchorOffset !== undefined) ret.anchorOffset = value.anchorOffset + if (value.focusKey !== undefined) ret.focusKey = value.focusKey + if (value.focusOffset !== undefined) ret.focusOffset = value.focusOffset + if (value.isBackward !== undefined) ret.isBackward = !!value.isBackward + if (value.isFocused !== undefined) ret.isFocused = !!value.isFocused if (value.marks !== undefined) ret.marks = value.marks break }