1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-31 02:49:56 +02:00

refactor transforms to take transform

This commit is contained in:
Ian Storm Taylor
2016-08-16 18:21:54 -07:00
parent 18d567672c
commit 9ae245ddab
5 changed files with 473 additions and 312 deletions

View File

@@ -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)
}
})

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}