diff --git a/lib/models/transform.js b/lib/models/transform.js index 7d5d823de..6a4437974 100644 --- a/lib/models/transform.js +++ b/lib/models/transform.js @@ -250,8 +250,7 @@ class Transform { Object.keys(Transforms).forEach((type) => { Transform.prototype[type] = function (...args) { this.operations.push({ type, args }) - this.state = Transforms[type](this.state, ...args) - return this + return Transforms[type](this, ...args) } }) diff --git a/lib/transforms/at-current-range.js b/lib/transforms/at-current-range.js index 778ec88cd..86f28f212 100644 --- a/lib/transforms/at-current-range.js +++ b/lib/transforms/at-current-range.js @@ -27,38 +27,41 @@ import { /** * Add a `mark` to the characters in the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Mark} mark - * @return {State} state + * @return {Transform} */ -export function addMark(state, mark) { +export function addMark(transform, mark) { mark = Normalize.mark(mark) + let { state } = transform let { cursorMarks, document, selection } = state // If the selection is collapsed, add the mark to the cursor instead. if (selection.isCollapsed) { const marks = document.getMarksAtRange(selection) state = state.merge({ cursorMarks: marks.add(mark) }) - return state + transform.state = state + return transform } - return addMarkAtRange(state, selection, mark) + return addMarkAtRange(transform, selection, mark) } /** * Delete at the current selection. * - * @param {State} state - * @return {State} + * @param {Transform} transform + * @return {Transform} */ -export function _delete(state) { +export function _delete(transform) { + let { state } = transform let { document, selection } = state let after // When collapsed, there's nothing to do. - if (selection.isCollapsed) return state + if (selection.isCollapsed) return transform // Determine what the selection will be after deleting. const { startText } = state @@ -95,20 +98,23 @@ export function _delete(state) { } // Delete and update the selection. - state = deleteAtRange(state, selection) + transform = deleteAtRange(transform, selection) + state = transform.state state = state.merge({ selection: after }) - return state + transform.state = state + return transform } /** * Delete backward `n` characters at the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Number} n (optional) - * @return {State} + * @return {Transform} */ -export function deleteBackward(state, n = 1) { +export function deleteBackward(transform, n = 1) { + let { state } = transform let { document, selection } = state let after = selection @@ -170,20 +176,23 @@ export function deleteBackward(state, n = 1) { } // Delete backward and then update the selection. - state = deleteBackwardAtRange(state, selection, n) + transform = deleteBackwardAtRange(transform, selection, n) + state = transform.state state = state.merge({ selection: after }) - return state + transform.state = state + return transform } /** * Delete forward `n` characters at the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Number} n (optional) - * @return {State} + * @return {Transform} */ -export function deleteForward(state, n = 1) { +export function deleteForward(transform, n = 1) { + let { state } = transform let { document, selection, startText } = state let { startKey, startOffset } = selection let after = selection @@ -226,25 +235,29 @@ export function deleteForward(state, n = 1) { } // Delete forward and then update the selection. - state = deleteForwardAtRange(state, selection, n) + transform = deleteForwardAtRange(transform, selection, n) + state = transform.state state = state.merge({ selection: after }) - return state + transform.state = state + return transform } /** * Insert a `block` at the current selection. * - * @param {State} state + * @param {Transform} transform * @param {String || Object || Block} block - * @return {State} + * @return {Transform} */ -export function insertBlock(state, block) { +export function insertBlock(transform, block) { + let { state } = transform let { document, selection } = state const keys = document.getTexts().map(text => text.key) // Insert the block - state = insertBlockAtRange(state, selection, block) + transform = insertBlockAtRange(transform, selection, block) + state = transform.state document = state.document selection = state.selection @@ -254,23 +267,25 @@ export function insertBlock(state, block) { // Update the document and selection. state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Insert a `fragment` at the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Document} fragment - * @return {State} + * @return {Transform} */ -export function insertFragment(state, fragment) { +export function insertFragment(transform, fragment) { + let { state } = transform let { document, selection } = state let after = selection // If there's nothing in the fragment, do nothing. - if (!fragment.length) return state + if (!fragment.length) return transform // Lookup some nodes for determining the selection next. const lastText = fragment.getTexts().last() @@ -278,7 +293,8 @@ export function insertFragment(state, fragment) { const beforeTexts = document.getTexts() // Insert the fragment. - state = insertFragmentAtRange(state, selection, fragment) + transform = insertFragmentAtRange(transform, selection, fragment) + state = transform.state document = state.document selection = state.selection @@ -310,24 +326,27 @@ export function insertFragment(state, fragment) { // Update the document and selection. selection = after state = state.merge({ document, selection }) - return state + transform.state = state + return transform } /** * Insert a `inline` at the current selection. * - * @param {State} state + * @param {Transform} transform * @param {String || Object || Block} inline - * @return {State} + * @return {Transform} */ -export function insertInline(state, inline) { +export function insertInline(transform, inline) { + let { state } = transform let { document, selection, startText } = state const hasVoid = document.hasVoidParent(startText) const keys = document.getTexts().map(text => text.key) // Insert the inline - state = insertInlineAtRange(state, selection, inline) + transform = insertInlineAtRange(transform, selection, inline) + state = transform.state document = state.document selection = state.selection @@ -343,19 +362,21 @@ export function insertInline(state, inline) { // Update the document and selection. state = state.merge({ document, selection }) - return state + transform.state = state + return transform } /** * Insert a `text` string at the current selection. * - * @param {State} state + * @param {Transform} transform * @param {String} text * @param {Set} marks (optional) - * @return {State} + * @return {Transform} */ -export function insertText(state, text, marks) { +export function insertText(transform, text, marks) { + let { state } = transform let { cursorMarks, document, selection } = state let after const isVoid = document.hasVoidParent(state.startText) @@ -374,46 +395,54 @@ export function insertText(state, text, marks) { } // Insert the text and update the selection. - state = insertTextAtRange(state, selection, text, marks || cursorMarks) + state = insertTextAtRange(transform, selection, text, marks || cursorMarks) + state = transform.state state = state.merge({ selection: after }) - return state + transform.state = state + return transform } /** * Set `properties` of the block nodes in the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Object} properties - * @return {State} + * @return {Transform} */ -export function setBlock(state, properties) { - return setBlockAtRange(state, state.selection, properties) +export function setBlock(transform, properties) { + const { state } = transform + const { selection } = state + return setBlockAtRange(transform, selection, properties) } /** * Set `properties` of the inline nodes in the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Object} properties - * @return {State} + * @return {Transform} */ -export function setInline(state, properties) { - return setInlineAtRange(state, state.selection, properties) +export function setInline(transform, properties) { + const { state } = transform + const { selection } = state + return setInlineAtRange(transform, selection, properties) } /** * Split the block node at the current selection, to optional `depth`. * - * @param {State} state + * @param {Transform} transform * @param {Number} depth (optional) - * @return {State} + * @return {Transform} */ -export function splitBlock(state, depth = 1) { - state = splitBlockAtRange(state, state.selection, depth) +export function splitBlock(transform, depth = 1) { + let { state } = transform + transform = splitBlockAtRange(transform, state.selection, depth) + state = transform.state let { document, selection } = state // Determine what the selection should be after splitting. @@ -422,22 +451,25 @@ export function splitBlock(state, depth = 1) { const nextNode = document.getNextText(startNode) selection = selection.collapseToStartOf(nextNode) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Split the inline nodes at the current selection, to optional `depth`. * - * @param {State} state + * @param {Transform} transform * @param {Number} depth (optional) - * @return {State} + * @return {Transform} */ -export function splitInline(state, depth = Infinity) { +export function splitInline(transform, depth = Infinity) { + let { state } = transform let { document, selection } = state // Split the document. - state = splitInlineAtRange(state, selection, depth) + transform = splitInlineAtRange(transform, selection, depth) + state = transform.state document = state.document selection = state.selection @@ -452,99 +484,111 @@ export function splitInline(state, depth = Infinity) { } state = state.merge({ document, selection }) - return state + transform.state = state + return transform } /** * Remove a `mark` from the characters in the current selection. * - * @param {State} state + * @param {Transform} transform * @param {Mark} mark - * @return {State} + * @return {Transform} */ -export function removeMark(state, mark) { +export function removeMark(transform, mark) { mark = Normalize.mark(mark) + let { state } = transform let { cursorMarks, document, selection } = state // If the selection is collapsed, remove the mark from the cursor instead. if (selection.isCollapsed) { const marks = document.getMarksAtRange(selection) state = state.merge({ cursorMarks: marks.remove(mark) }) - return state + transform.state = state + return transform } - return removeMarkAtRange(state, state.selection, mark) + return removeMarkAtRange(transform, state.selection, mark) } /** * Add or remove a `mark` from the characters in the current selection, * depending on whether it's already there. * - * @param {State} state + * @param {Transform} transform * @param {Mark} mark - * @return {State} + * @return {Transform} */ -export function toggleMark(state, mark) { +export function toggleMark(transform, mark) { mark = Normalize.mark(mark) + const { state } = transform const exists = state.marks.some(m => m.equals(mark)) return exists - ? removeMark(state, mark) - : addMark(state, mark) + ? removeMark(transform, mark) + : addMark(transform, mark) } /** * Unwrap the current selection from a block parent with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function unwrapBlock(state, properties) { - return unwrapBlockAtRange(state, state.selection, properties) +export function unwrapBlock(transform, properties) { + const { state } = transform + const { selection } = state + return unwrapBlockAtRange(transform, selection, properties) } /** * Unwrap the current selection from an inline parent with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function unwrapInline(state, properties) { - return unwrapInlineAtRange(state, state.selection, properties) +export function unwrapInline(transform, properties) { + const { state } = transform + const { selection } = state + return unwrapInlineAtRange(transform, selection, properties) } /** * Wrap the block nodes in the current selection with a new block node with * `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function wrapBlock(state, properties) { - return wrapBlockAtRange(state, state.selection, properties) +export function wrapBlock(transform, properties) { + const { state } = transform + const { selection } = state + return wrapBlockAtRange(transform, selection, properties) } /** * Wrap the current selection in new inline nodes with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function wrapInline(state, properties) { +export function wrapInline(transform, properties) { + let { state } = transform let { document, selection } = state const { startKey } = selection const previous = document.getPreviousText(startKey) - state = wrapInlineAtRange(state, selection, properties) + transform = wrapInlineAtRange(transform, selection, properties) + state = transform.state document = state.document selection = state.selection @@ -577,19 +621,21 @@ export function wrapInline(state, properties) { } state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Wrap the current selection with prefix/suffix. * - * @param {State} state + * @param {Transform} transform * @param {String} prefix * @param {String} suffix - * @return {State} + * @return {Transform} */ -export function wrapText(state, prefix, suffix = prefix) { +export function wrapText(transform, prefix, suffix = prefix) { + let { state } = transform let { document, selection } = state let { anchorOffset, anchorKey, focusOffset, focusKey, isBackward } = selection let after @@ -607,7 +653,9 @@ export function wrapText(state, prefix, suffix = prefix) { } // Wrap the text and update the state. - state = wrapTextAtRange(state, selection, prefix, suffix) + transform = wrapTextAtRange(transform, selection, prefix, suffix) + state = transform.state state = state.merge({ selection: after }) - return state + transform.state = state + return transform } diff --git a/lib/transforms/at-range.js b/lib/transforms/at-range.js index 5492b4427..f9084c525 100644 --- a/lib/transforms/at-range.js +++ b/lib/transforms/at-range.js @@ -11,20 +11,21 @@ import { Set } from 'immutable' /** * Add a new `mark` to the characters at `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Mark || String || Object} mark - * @return {State} + * @return {Transform} */ -export function addMarkAtRange(state, range, mark) { +export function addMarkAtRange(transform, range, mark) { + let { state } = transform let { document } = state // Normalize the mark. mark = Normalize.mark(mark) // When the range is collapsed, do nothing. - if (range.isCollapsed) return state + if (range.isCollapsed) return transform // Otherwise, find each of the text nodes within the range. const { startKey, startOffset, endKey, endOffset } = range @@ -49,22 +50,24 @@ export function addMarkAtRange(state, range, mark) { // Update the state. state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Delete everything in a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range - * @return {State} + * @return {Transform} */ -export function deleteAtRange(state, range) { +export function deleteAtRange(transform, range) { + let { state } = transform let { document } = state // If the range is collapsed, there's nothing to delete. - if (range.isCollapsed) return state + if (range.isCollapsed) return transform // Make sure the children exist. const { startKey, startOffset, endKey, endOffset } = range @@ -78,7 +81,8 @@ export function deleteAtRange(state, range) { document = document.updateDescendant(text) document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } // Split the blocks and determine the edge boundaries. @@ -93,8 +97,9 @@ export function deleteAtRange(state, range) { let isAncestor = ancestor == document const ancestorDepth = isAncestor ? 0 : document.getDepth(ancestor) - state = splitBlockAtRange(state, start, startDepth - ancestorDepth) - state = splitBlockAtRange(state, end, endDepth - ancestorDepth) + transform = splitBlockAtRange(transform, start, startDepth - ancestorDepth) + transform = splitBlockAtRange(transform, end, endDepth - ancestorDepth) + state = transform.state document = state.document ancestor = document.getCommonAncestor(startKey, endKey) @@ -142,41 +147,45 @@ export function deleteAtRange(state, range) { // Update the state. state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Delete backward `n` characters at a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Number} n (optional) - * @return {State} + * @return {Transform} */ -export function deleteBackwardAtRange(state, range, n = 1) { +export function deleteBackwardAtRange(transform, range, n = 1) { + let { state } = transform let { document } = state const { startKey, startOffset } = range // When the range is still expanded, just do a regular delete. - if (range.isExpanded) return deleteAtRange(state, range) + if (range.isExpanded) return deleteAtRange(transform, range) // When collapsed at the start of the node, there's nothing to do. - if (range.isAtStartOf(document)) return state + if (range.isAtStartOf(document)) return transform // When collapsed in a void node, remove that node. const block = document.getClosestBlock(startKey) if (block && block.isVoid) { document = document.removeDescendant(block) state = state.merge({ document }) - return state + transform.state = state + return transform } const inline = document.getClosestInline(startKey) if (inline && inline.isVoid) { document = document.removeDescendant(inline) state = state.merge({ document }) - return state + transform.state = state + return transform } // When at start of a text node, merge forwards into the next text node. @@ -190,59 +199,64 @@ export function deleteBackwardAtRange(state, range, n = 1) { if (prevBlock && prevBlock.isVoid) { document = document.removeDescendant(prevBlock) state = state.merge({ document }) - return state + transform.state = state + return transform } const prevInline = document.getClosestInline(previous) if (prevInline && prevInline.isVoid) { document = document.removeDescendant(prevInline) state = state.merge({ document }) - return state + transform.state = state + return transform } range = range.extendToEndOf(previous) range = range.normalize(document) - return deleteAtRange(state, range) + return deleteAtRange(transform, range) } // Otherwise, remove `n` characters behind of the cursor. range = range.extendBackward(n) range = range.normalize(document) - return deleteAtRange(state, range) + return deleteAtRange(transform, range) } /** * Delete forward `n` characters at a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Number} n (optional) - * @return {State} + * @return {Transform} */ -export function deleteForwardAtRange(state, range, n = 1) { +export function deleteForwardAtRange(transform, range, n = 1) { + let { state } = transform let { document } = state const { startKey } = range // When the range is still expanded, just do a regular delete. - if (range.isExpanded) return deleteAtRange(state, range) + if (range.isExpanded) return deleteAtRange(transform, range) // When collapsed at the end of the node, there's nothing to do. - if (range.isAtEndOf(document)) return state + if (range.isAtEndOf(document)) return transform // When collapsed in a void node, remove that node. const block = document.getClosestBlock(startKey) if (block && block.isVoid) { document = document.removeDescendant(block) state = state.merge({ document }) - return state + transform.state = state + return transform } const inline = document.getClosestInline(startKey) if (inline && inline.isVoid) { document = document.removeDescendant(inline) state = state.merge({ document }) - return state + transform.state = state + return transform } // When at end of a text node, merge forwards into the next text node. @@ -251,25 +265,26 @@ export function deleteForwardAtRange(state, range, n = 1) { const next = document.getNextText(startNode) range = range.extendToStartOf(next) range = range.normalize(document) - return deleteAtRange(state, range) + return deleteAtRange(transform, range) } // Otherwise, remove `n` characters ahead of the cursor. range = range.extendForward(n) range = range.normalize(document) - return deleteAtRange(state, range) + return deleteAtRange(transform, range) } /** * Insert a `block` node at `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Block or String or Object} block - * @return {State} + * @return {Transform} */ -export function insertBlockAtRange(state, range, block) { +export function insertBlockAtRange(transform, range, block) { + let { state } = transform let { document } = state // Normalize the block argument. @@ -277,7 +292,8 @@ export function insertBlockAtRange(state, range, block) { // If expanded, delete the range first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } @@ -311,7 +327,8 @@ export function insertBlockAtRange(state, range, block) { // Otherwise, split the block and insert between. else { - state = splitBlockAtRange(state, range) + transform = splitBlockAtRange(transform, range) + state = transform.state document = state.document parent = document.getParent(startBlock) startBlock = document.getClosestBlock(startKey) @@ -332,19 +349,21 @@ export function insertBlockAtRange(state, range, block) { // Return the updated state. state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Insert a `fragment` at a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Document} fragment - * @return {State} + * @return {Transform} */ -export function insertFragmentAtRange(state, range, fragment) { +export function insertFragmentAtRange(transform, range, fragment) { + let { state } = transform let { document } = state // Ensure that the selection is normalized. @@ -352,20 +371,22 @@ export function insertFragmentAtRange(state, range, fragment) { // If the range is expanded, delete first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } // If the fragment is empty, do nothing. - if (!fragment.length) return state + if (!fragment.length) return transform // Make sure each node in the fragment has a unique key. fragment = fragment.mapDescendants(child => child.set('key', uid())) // Split the inlines if need be. if (!document.isInlineSplitAtRange(range)) { - state = splitInlineAtRange(state, range) + transform = splitInlineAtRange(transform, range) + state = transform.state document = state.document } @@ -420,7 +441,8 @@ export function insertFragmentAtRange(state, range, fragment) { if (firstBlock == lastBlock) { document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } // Otherwise, remove the fragment's first block's highest solo parent... @@ -441,19 +463,21 @@ export function insertFragmentAtRange(state, range, fragment) { document = document.insertChildrenAfter(block, fragment.nodes) document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Insert an `inline` node at `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Inline or String or Object} inline - * @return {State} + * @return {Transform} */ -export function insertInlineAtRange(state, range, inline) { +export function insertInlineAtRange(transform, range, inline) { + let { state } = transform let { document } = state // Normalize the inline argument. @@ -461,7 +485,8 @@ export function insertInlineAtRange(state, range, inline) { // If expanded, delete the range first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } @@ -470,13 +495,14 @@ export function insertInlineAtRange(state, range, inline) { // If the range is inside a void, abort. const startBlock = document.getClosestBlock(startKey) - if (startBlock && startBlock.isVoid) return state + if (startBlock && startBlock.isVoid) return transform const startInline = document.getClosestInline(startKey) - if (startInline && startInline.isVoid) return state + if (startInline && startInline.isVoid) return transform // Split the text nodes at the cursor. - state = splitTextAtRange(state, range) + transform = splitTextAtRange(transform, range) + state = transform.state document = state.document // Insert the inline between the split text nodes. @@ -491,30 +517,33 @@ export function insertInlineAtRange(state, range, inline) { document = document.updateDescendant(parent) document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Insert text `string` at a `range`, with optional `marks`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {String} string * @param {Set} marks (optional) - * @return {State} + * @return {Transform} */ -export function insertTextAtRange(state, range, string, marks) { +export function insertTextAtRange(transform, range, string, marks) { + let { state } = transform let { document } = state const { startKey, startOffset } = range const isVoid = document.hasVoidParent(startKey) // If inside a void node, do nothing. - if (isVoid) return state + if (isVoid) return transform // Is the range is expanded, delete it first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } @@ -526,24 +555,26 @@ export function insertTextAtRange(state, range, string, marks) { // Return the updated selection. state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Remove an existing `mark` to the characters at `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Mark or String} mark (optional) - * @return {State} + * @return {Transform} */ -export function removeMarkAtRange(state, range, mark) { +export function removeMarkAtRange(transform, range, mark) { + let { state } = transform mark = Normalize.mark(mark) let { document } = state // When the range is collapsed, do nothing. - if (range.isCollapsed) return state + if (range.isCollapsed) return transform // Otherwise, find each of the text nodes within the range. let texts = document.getTextsAtRange(range) @@ -568,19 +599,21 @@ export function removeMarkAtRange(state, range, mark) { }) state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Set the `properties` of block nodes in a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function setBlockAtRange(state, range, properties = {}) { +export function setBlockAtRange(transform, range, properties = {}) { + let { state } = transform properties = Normalize.nodeProperties(properties) let { document } = state const blocks = document.getBlocksAtRange(range) @@ -592,19 +625,21 @@ export function setBlockAtRange(state, range, properties = {}) { document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Set the `properties` of inline nodes in a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function setInlineAtRange(state, range, properties = {}) { +export function setInlineAtRange(transform, range, properties = {}) { + let { state } = transform properties = Normalize.nodeProperties(properties) let { document } = state const inlines = document.getInlinesAtRange(range) @@ -616,30 +651,34 @@ export function setInlineAtRange(state, range, properties = {}) { document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Split the block nodes at a `range`, to optional `depth`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Number} depth (optional) - * @return {State} + * @return {Transform} */ -export function splitBlockAtRange(state, range, depth = 1) { +export function splitBlockAtRange(transform, range, depth = 1) { + let { state } = transform let { document } = state // If the range is expanded, remove it first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } // Split the inline nodes at the range. - state = splitInlineAtRange(state, range) + transform = splitInlineAtRange(transform, range) + state = transform.state document = state.document // Find the highest inline elements that were split. @@ -682,30 +721,34 @@ export function splitBlockAtRange(state, range, depth = 1) { } state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Split the inline nodes at a `range`, to optional `depth`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Number} depth (optiona) - * @return {State} + * @return {Transform} */ -export function splitInlineAtRange(state, range, depth = Infinity) { +export function splitInlineAtRange(transform, range, depth = Infinity) { + let { state } = transform let { document } = state // If the range is expanded, remove it first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } // First split the text nodes. - state = splitTextAtRange(state, range) + transform = splitTextAtRange(transform, range) + state = transform.state document = state.document // Find the children that were split. @@ -742,23 +785,26 @@ export function splitInlineAtRange(state, range, depth = Infinity) { } state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Split the text nodes at a `range`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range - * @return {State} + * @return {Transform} */ -export function splitTextAtRange(state, range) { +export function splitTextAtRange(transform, range) { + let { state } = transform let { document } = state // If the range is expanded, remove it first. if (range.isExpanded) { - state = deleteAtRange(state, range) + transform = deleteAtRange(transform, range) + state = transform.state document = state.document range = range.collapseToStart() } @@ -784,45 +830,48 @@ export function splitTextAtRange(state, range) { parent = parent.merge({ nodes }) document = document.updateDescendant(parent) state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Add or remove a `mark` from the characters at `range`, depending on whether * it's already there. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {Mark or String} mark (optional) - * @return {State} + * @return {Transform} */ -export function toggleMarkAtRange(state, range, mark) { +export function toggleMarkAtRange(transform, range, mark) { + let { state } = transform mark = Normalize.mark(mark) let { document } = state // When the range is collapsed, do nothing. - if (range.isCollapsed) return state + if (range.isCollapsed) return transform // Check if the mark exists in the range already. const marks = document.getMarksAtRange(range) const exists = marks.some(m => m.equals(mark)) return exists - ? removeMarkAtRange(state, range, mark) - : addMarkAtRange(state, range, mark) + ? removeMarkAtRange(transform, range, mark) + : addMarkAtRange(transform, range, mark) } /** * Unwrap all of the block nodes in a `range` from a block with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {String or Object} properties - * @return {State} + * @return {Transform} */ -export function unwrapBlockAtRange(state, range, properties) { +export function unwrapBlockAtRange(transform, range, properties) { + let { state } = transform properties = Normalize.nodeProperties(properties) let { document } = state @@ -905,19 +954,21 @@ export function unwrapBlockAtRange(state, range, properties) { document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Unwrap the inline nodes in a `range` from an inline with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {String or Object} properties - * @return {State} + * @return {Transform} */ -export function unwrapInlineAtRange(state, range, properties) { +export function unwrapInlineAtRange(transform, range, properties) { + let { state } = transform properties = Normalize.nodeProperties(properties) let { document } = state let blocks = document.getInlinesAtRange(range) @@ -953,19 +1004,21 @@ export function unwrapInlineAtRange(state, range, properties) { document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Wrap all of the blocks in a `range` in a new block with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {String or Object} properties - * @return {State} + * @return {Transform} */ -export function wrapBlockAtRange(state, range, properties) { +export function wrapBlockAtRange(transform, range, properties) { + let { state } = transform properties = Normalize.nodeProperties(properties) let { document } = state @@ -1012,28 +1065,31 @@ export function wrapBlockAtRange(state, range, properties) { : document.updateDescendant(parent.merge({ nodes })) state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Wrap the text and inlines in a `range` in a new inline with `properties`. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {String or Object} properties - * @return {State} + * @return {Transform} */ -export function wrapInlineAtRange(state, range, properties) { +export function wrapInlineAtRange(transform, range, properties) { + let { state } = transform properties = Normalize.nodeProperties(properties) let { document } = state // If collapsed, there's nothing to wrap. - if (range.isCollapsed) return state + if (range.isCollapsed) return transform // Split at the start of the range. const start = range.collapseToStart() - state = splitInlineAtRange(state, start) + transform = splitInlineAtRange(transform, start) + state = transform.state document = state.document // Determine the new end of the range, and split there. @@ -1049,7 +1105,8 @@ export function wrapInlineAtRange(state, range, properties) { focusOffset: endOffset - startOffset }) - state = splitInlineAtRange(state, end) + transform = splitInlineAtRange(transform, end) + state = transform.state document = state.document // Calculate the new range to wrap around. @@ -1087,28 +1144,30 @@ export function wrapInlineAtRange(state, range, properties) { document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Wrap the text in a `range` in a prefix/suffix. * - * @param {State} state + * @param {Transform} transform * @param {Selection} range * @param {String} prefix * @param {String} suffix - * @return {State} + * @return {Transform} */ -export function wrapTextAtRange(state, range, prefix, suffix = prefix) { +export function wrapTextAtRange(transform, range, prefix, suffix = prefix) { + let { state } = transform // Insert text at the starting edge. const { startKey, endKey } = range const start = range.collapseToStart() - state = insertTextAtRange(state, start, prefix) + transform = insertTextAtRange(transform, start, prefix) // Determine the new ending edge, and insert text there. let end = range.collapseToEnd() if (startKey == endKey) end = end.moveForward(prefix.length) - state = insertTextAtRange(state, end, suffix) - return state + transform = insertTextAtRange(transform, end, suffix) + return transform } diff --git a/lib/transforms/by-key.js b/lib/transforms/by-key.js index c0e1d6bf1..54cfae274 100644 --- a/lib/transforms/by-key.js +++ b/lib/transforms/by-key.js @@ -4,48 +4,53 @@ import Normalize from '../utils/normalize' /** * Remove a node by `key`. * - * @param {State} state + * @param {Transform} transform * @param {String} key - * @return {State} + * @return {Transform} */ -export function removeNodeByKey(state, key) { +export function removeNodeByKey(transform, key) { + let { state } = transform let { document } = state document = document.removeDescendant(key) document = document.normalize() state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Set `properties` on a node by `key`. * - * @param {State} state + * @param {Transform} transform * @param {String} key * @param {Object or String} properties - * @return {State} + * @return {Transform} */ -export function setNodeByKey(state, key, properties) { +export function setNodeByKey(transform, key, properties) { properties = Normalize.nodeProperties(properties) + let { state } = transform let { document } = state let descendant = document.assertDescendant(key) descendant = descendant.merge(properties) document = document.updateDescendant(descendant) state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Insert a `node` after a node by `key`. * - * @param {State} state + * @param {Transform} transform * @param {String} key * @param {Node} node - * @return {State} + * @return {Transform} */ -export function insertNodeAfterNodeByKey(state, key, node) { +export function insertNodeAfterNodeByKey(transform, key, node) { + let { state } = transform let { document } = state let descendant = document.assertDescendant(key) let parent = document.getParent(key) @@ -54,19 +59,21 @@ export function insertNodeAfterNodeByKey(state, key, node) { parent = parent.merge({ nodes }) document = document.updateDescendant(parent) state = state.merge({ document }) - return state + transform.state = state + return transform } /** * Insert a `node` before a node by `key`. * - * @param {State} state + * @param {Transform} transform * @param {String} key * @param {Node} node - * @return {State} + * @return {Transform} */ -export function insertNodeBeforeNodeByKey(state, key, node) { +export function insertNodeBeforeNodeByKey(transform, key, node) { + let { state } = transform let { document } = state let descendant = document.assertDescendant(key) let parent = document.getParent(key) @@ -75,5 +82,6 @@ export function insertNodeBeforeNodeByKey(state, key, node) { parent = parent.merge({ nodes }) document = document.updateDescendant(parent) state = state.merge({ document }) - return state + transform.state = state + return transform } diff --git a/lib/transforms/on-selection.js b/lib/transforms/on-selection.js index 0355d9814..e1d2d3d6d 100644 --- a/lib/transforms/on-selection.js +++ b/lib/transforms/on-selection.js @@ -4,57 +4,66 @@ import Selection from '../models/selection' /** * Blur the selection. * - * @return {Selection} selection + * @param {Transform} transform + * @return {Transform} */ -export function blur(state, ...args) { +export function blur(transform) { + let { state } = transform let { document, selection } = state - selection = selection.blur(...args) + selection = selection.blur() selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Move the focus point to the anchor point. * - * @return {Selection} selection + * @return {Transform} */ -export function collapseToAnchor(state, ...args) { +export function collapseToAnchor(transform) { + let { state } = transform let { document, selection } = state - selection = selection.collapseToAnchor(...args) + selection = selection.collapseToAnchor() selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Move the anchor point to the focus point. * - * @return {Selection} selection + * @return {Transform} */ -export function collapseToFocus(state, ...args) { +export function collapseToFocus(transform) { + let { state } = transform let { document, selection } = state - selection = selection.collapseToFocus(...args) + selection = selection.collapseToFocus() selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Move to the end of a `node`. * - * @return {Selection} selection + * @return {Transform} */ -export function collapseToEndOf(state, ...args) { +export function collapseToEndOf(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.collapseToEndOf(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -64,19 +73,21 @@ export function collapseToEndOf(state, ...args) { * @return {State} */ -export function collapseToEndOfNextBlock(state) { +export function collapseToEndOfNextBlock(transform) { + let { state } = transform let { document, selection } = state let blocks = document.getBlocksAtRange(selection) let block = blocks.last() - if (!block) return state + if (!block) return transform let next = document.getNextBlock(block) - if (!next) return state + if (!next) return transform selection = selection.collapseToEndOf(next) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -86,19 +97,21 @@ export function collapseToEndOfNextBlock(state) { * @return {State} */ -export function collapseToEndOfNextText(state) { +export function collapseToEndOfNextText(transform) { + let { state } = transform let { document, selection } = state let texts = document.getTextsAtRange(selection) let text = texts.last() - if (!text) return state + if (!text) return transform let next = document.getNextText(text) - if (!next) return state + if (!next) return transform selection = selection.collapseToEndOf(next) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -108,19 +121,21 @@ export function collapseToEndOfNextText(state) { * @return {State} */ -export function collapseToEndOfPreviousBlock(state) { +export function collapseToEndOfPreviousBlock(transform) { + let { state } = transform let { document, selection } = state let blocks = document.getBlocksAtRange(selection) let block = blocks.first() - if (!block) return state + if (!block) return transform let previous = document.getPreviousBlock(block) - if (!previous) return state + if (!previous) return transform selection = selection.collapseToEndOf(previous) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -130,33 +145,37 @@ export function collapseToEndOfPreviousBlock(state) { * @return {State} */ -export function collapseToEndOfPreviousText(state) { +export function collapseToEndOfPreviousText(transform) { + let { state } = transform let { document, selection } = state let texts = document.getTextsAtRange(selection) let text = texts.first() - if (!text) return state + if (!text) return transform let previous = document.getPreviousText(text) - if (!previous) return state + if (!previous) return transform selection = selection.collapseToEndOf(previous) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Move to the start of a `node`. * - * @return {Selection} selection + * @return {Transform} */ -export function collapseToStartOf(state, ...args) { +export function collapseToStartOf(transform, node) { + let { state } = transform let { document, selection } = state - selection = selection.collapseToStartOf(...args) + selection = selection.collapseToStartOf(node) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -166,19 +185,21 @@ export function collapseToStartOf(state, ...args) { * @return {State} */ -export function collapseToStartOfNextBlock(state) { +export function collapseToStartOfNextBlock(transform) { + let { state } = transform let { document, selection } = state let blocks = document.getBlocksAtRange(selection) let block = blocks.last() - if (!block) return state + if (!block) return transform let next = document.getNextBlock(block) - if (!next) return state + if (!next) return transform selection = selection.collapseToStartOf(next) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -188,19 +209,21 @@ export function collapseToStartOfNextBlock(state) { * @return {State} */ -export function collapseToStartOfNextText(state) { +export function collapseToStartOfNextText(transform) { + let { state } = transform let { document, selection } = state let texts = document.getTextsAtRange(selection) let text = texts.last() - if (!text) return state + if (!text) return transform let next = document.getNextText(text) - if (!next) return state + if (!next) return transform selection = selection.collapseToStartOf(next) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -210,19 +233,21 @@ export function collapseToStartOfNextText(state) { * @return {State} */ -export function collapseToStartOfPreviousBlock(state) { +export function collapseToStartOfPreviousBlock(transform) { + let { state } = transform let { document, selection } = state let blocks = document.getBlocksAtRange(selection) let block = blocks.first() - if (!block) return state + if (!block) return transform let previous = document.getPreviousBlock(block) - if (!previous) return state + if (!previous) return transform selection = selection.collapseToStartOf(previous) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -232,123 +257,139 @@ export function collapseToStartOfPreviousBlock(state) { * @return {State} */ -export function collapseToStartOfPreviousText(state) { +export function collapseToStartOfPreviousText(transform) { + let { state } = transform let { document, selection } = state let texts = document.getTextsAtRange(selection) let text = texts.first() - if (!text) return state + if (!text) return transform let previous = document.getPreviousText(text) - if (!previous) return state + if (!previous) return transform selection = selection.collapseToStartOf(previous) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Extend the focus point backward `n` characters. * * @param {Number} n (optional) - * @return {Selection} selection + * @return {Transform} */ -export function extendBackward(state, ...args) { +export function extendBackward(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.extendBackward(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Extend the focus point forward `n` characters. * * @param {Number} n (optional) - * @return {Selection} selection + * @return {Transform} */ -export function extendForward(state, ...args) { +export function extendForward(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.extendForward(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Extend the focus point to the end of a `node`. * * @param {Node} node - * @return {Selection} selection + * @return {Transform} */ -export function extendToEndOf(state, ...args) { +export function extendToEndOf(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.extendToEndOf(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Extend the focus point to the start of a `node`. * * @param {Node} node - * @return {Selection} selection + * @return {Transform} */ -export function extendToStartOf(state, ...args) { +export function extendToStartOf(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.extendToStartOf(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Focus the selection. * - * @return {Selection} selection + * @return {Transform} */ -export function focus(state, ...args) { +export function focus(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.focus(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Move the selection backward `n` characters. * * @param {Number} n (optional) - * @return {Selection} selection + * @return {Transform} */ -export function moveBackward(state, ...args) { +export function moveBackward(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.moveBackward(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** * Move the selection forward `n` characters. * * @param {Number} n (optional) - * @return {Selection} selection + * @return {Transform} */ -export function moveForward(state, ...args) { +export function moveForward(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.moveForward(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -359,7 +400,8 @@ export function moveForward(state, ...args) { * @return {State} */ -export function moveTo(state, properties) { +export function moveTo(transform, properties) { + let { state } = transform let { document, selection } = state // Allow for passing a `Selection` object. @@ -381,7 +423,8 @@ export function moveTo(state, properties) { selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -389,15 +432,17 @@ export function moveTo(state, properties) { * * @param {Number} anchor * @param {Number} focus (optional) - * @return {Selection} selection + * @return {Transform} */ -export function moveToOffsets(state, ...args) { +export function moveToOffsets(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.moveToOffsets(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform } /** @@ -405,13 +450,15 @@ export function moveToOffsets(state, ...args) { * * @param {Node} start * @param {Node} end (optional) - * @return {Selection} selection + * @return {Transform} */ -export function moveToRangeOf(state, ...args) { +export function moveToRangeOf(transform, ...args) { + let { state } = transform let { document, selection } = state selection = selection.moveToRangeOf(...args) selection = selection.normalize(document) state = state.merge({ selection }) - return state + transform.state = state + return transform }