1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-24 09:13:24 +01:00

refactor operations

This commit is contained in:
Ian Storm Taylor 2016-08-29 14:34:54 -07:00
parent 52820af7ae
commit b9e083d55f
7 changed files with 512 additions and 216 deletions

View File

@ -1,4 +1,5 @@
import Operations from '../transforms/operations'
import Transforms from '../transforms'
import includes from 'lodash/includes'
import xor from 'lodash/xor'
@ -70,13 +71,19 @@ class Transform {
/**
* Add an `operation` to the transform that resulted in `state`.
*
* @param {State} state
* @param {Object} operation
* @return {Transform}
*/
add(state, operation) {
this.state = state
operate(operation) {
const { type } = operation
const fn = Operations[type]
if (!fn) {
throw new Error(`Unknown operation type: "${type}".`)
}
this.state = fn(this.state, operation)
this.operations.push(operation)
return this
}

View File

@ -18,7 +18,7 @@ export function addMark(transform, mark) {
if (selection.isCollapsed) {
const marks = document.getMarksAtRange(selection).add(mark)
const sel = selection.merge({ marks })
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
return transform.addMarkAtRange(selection, mark)
@ -73,7 +73,7 @@ export function _delete(transform) {
return transform
.deleteAtRange(selection)
.updateSelection(after)
.setSelection(after)
}
/**
@ -147,7 +147,7 @@ export function deleteBackward(transform, n = 1) {
return transform
.deleteBackwardAtRange(selection, n)
.updateSelection(after)
.setSelection(after)
}
/**
@ -206,7 +206,7 @@ export function deleteForward(transform, n = 1) {
return transform
.deleteForwardAtRange(selection, n)
.updateSelection(after)
.setSelection(after)
}
/**
@ -229,7 +229,7 @@ export function insertBlock(transform, block) {
const text = document.getTexts().find(n => !keys.includes(n.key))
const after = selection.collapseToEndOf(text)
return transform.updateSelection(after)
return transform.setSelection(after)
}
/**
@ -279,7 +279,7 @@ export function insertFragment(transform, fragment) {
.moveForward(lastText.length)
}
return transform.updateSelection(after)
return transform.setSelection(after)
}
/**
@ -311,7 +311,7 @@ export function insertInline(transform, inline) {
after = selection.collapseToEndOf(text)
}
return transform.updateSelection(after)
return transform.setSelection(after)
}
/**
@ -346,7 +346,7 @@ export function insertText(transform, text, marks) {
return transform
.insertTextAtRange(selection, text, marks)
.updateSelection(after)
.setSelection(after)
}
/**
@ -399,7 +399,7 @@ export function splitBlock(transform, depth = 1) {
const nextNode = document.getNextText(startNode)
const after = selection.collapseToStartOf(nextNode)
return transform.updateSelection(after)
return transform.setSelection(after)
}
/**
@ -428,7 +428,7 @@ export function splitInline(transform, depth = Infinity) {
after = selection.collapseToStartOf(nextNode)
}
return transform.updateSelection(after)
return transform.setSelection(after)
}
/**
@ -448,7 +448,7 @@ export function removeMark(transform, mark) {
if (selection.isCollapsed) {
const marks = document.getMarksAtRange(selection).remove(mark)
const sel = selection.merge({ marks })
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
return transform.removeMarkAtRange(selection, mark)
@ -566,7 +566,7 @@ export function wrapInline(transform, properties) {
}
after = after.normalize(document)
return transform.updateSelection(after)
return transform.setSelection(after)
}
/**
@ -597,5 +597,5 @@ export function wrapText(transform, prefix, suffix = prefix) {
return transform
.wrapTextAtRange(selection, prefix, suffix)
.updateSelection(after)
.setSelection(after)
}

View File

@ -323,7 +323,7 @@ export function insertFragmentAtRange(transform, range, fragment) {
const lastIndex = lastBlock.nodes.size
nextNodes.forEach((node, i) => {
const newIndex = lastIndex + i + 1
const newIndex = lastIndex + i
transform.moveNodeByKey(node.key, lastBlock.key, newIndex)
})
}
@ -737,7 +737,7 @@ export function wrapBlockAtRange(transform, range, block) {
const parent = document.getParent(first)
const index = parent.nodes.indexOf(first)
transform.insertNodeByKey(parent.key, index + 1, block)
transform.insertNodeByKey(parent.key, index, block)
siblings.forEach((node, i) => {
transform.moveNodeByKey(node.key, block.key, i)
@ -818,8 +818,8 @@ export function wrapInlineAtRange(transform, range, inline) {
const startNode = inline.merge({ key: uid() })
const endNode = inline.merge({ key: uid() })
transform.insertNodeByKey(startBlock.key, startIndex + 1, startNode)
transform.insertNodeByKey(endBlock.key, endIndex + 1, endNode)
transform.insertNodeByKey(startBlock.key, startIndex - 1, startNode)
transform.insertNodeByKey(endBlock.key, endIndex, endNode)
startInlines.forEach((child, i) => {
transform.moveNodeByKey(child.key, startNode.key, i)

View File

@ -16,22 +16,28 @@ import uid from '../utils/uid'
export function addMarkByKey(transform, key, offset, length, mark) {
mark = Normalize.mark(mark)
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
node = node.addMark(offset, length, mark)
document = document.updateDescendant(node)
state = state.merge({ document })
return transform.add(state, {
type: 'add-mark',
const inverse = {
type: 'remove_mark',
path,
offset,
length,
mark,
offset,
}
const operation = {
type: 'add_mark',
path,
})
offset,
length,
mark,
inverse,
}
return transform.operate(operation)
}
/**
@ -45,23 +51,25 @@ export function addMarkByKey(transform, key, offset, length, mark) {
*/
export function insertNodeByKey(transform, key, index, node) {
let { state } = transform
let { document } = state
let parent = document.key == key ? document : document.assertDescendant(key)
const isParent = document == parent
const path = document.getPath(parent)
const nodes = parent.nodes.splice(index, 0, node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
const newPath = path.slice().push(index)
parent = parent.merge({ nodes })
document = isParent ? parent : document.updateDescendant(parent)
state = state.merge({ document })
const inverse = {
type: 'remove_node',
path: newPath,
}
return transform.add(state, {
type: 'insert-node',
const operation = {
type: 'insert_node',
path,
index,
node,
path,
})
inverse,
}
return transform.operate(operation)
}
/**
@ -76,22 +84,26 @@ export function insertNodeByKey(transform, key, index, node) {
*/
export function insertTextByKey(transform, key, offset, text, marks) {
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
node = node.insertText(offset, text, marks)
document = document.updateDescendant(node)
state = state.merge({ document })
return transform.add(state, {
type: 'insert-text',
offset,
marks,
const inverse = {
type: 'remove_text',
path,
offset,
length: text.length,
}
const operation = {
type: 'insert_text',
path,
offset,
text,
})
marks,
}
return transform.operate(operation)
}
/**
@ -105,32 +117,32 @@ export function insertTextByKey(transform, key, offset, text, marks) {
*/
export function moveNodeByKey(transform, key, newKey, newIndex) {
let { state } = transform
let { document } = state
const { state } = transform
const { document } = state
const node = document.assertDescendant(key)
const path = document.getPath(node)
const path = document.getPath(key)
const parent = document.getParent(key)
const parentPath = path.slice(0, -1)
const parentIndex = path[path.length - 1]
const newPath = document.getPath(newKey)
let parent = document.getParent(node)
const isParent = document == parent
const index = parent.nodes.indexOf(node)
const nodePath = newPath.slice().concat([newIndex])
parent = parent.removeNode(index)
document = isParent ? parent : document.updateDescendant(parent)
const inverse = {
type: 'move_node',
path: nodePath,
newPath: parentPath,
newIndex: parentIndex,
}
let target = document.key == newKey ? document : document.assertDescendant(newKey)
const isTarget = document == target
target = target.insertNode(newIndex, node)
document = isTarget ? target : document.updateDescendant(target)
// document = document.normalize()
state = state.merge({ document })
return transform.add(state, {
type: 'move-node',
const operation = {
type: 'move_node',
path,
newPath,
newIndex,
})
inverse,
}
return transform.operate(operation)
}
/**
@ -147,22 +159,28 @@ export function moveNodeByKey(transform, key, newKey, newIndex) {
export function removeMarkByKey(transform, key, offset, length, mark) {
mark = Normalize.mark(mark)
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
node = node.removeMark(offset, length, mark)
document = document.updateDescendant(node)
state = state.merge({ document })
return transform.add(state, {
type: 'remove-mark',
const inverse = {
type: 'add_mark',
path,
offset,
length,
mark,
}
const operation = {
type: 'remove_mark',
path,
})
offset,
length,
mark,
inverse,
}
return transform.operate(operation)
}
/**
@ -174,23 +192,27 @@ export function removeMarkByKey(transform, key, offset, length, mark) {
*/
export function removeNodeByKey(transform, key) {
let { state } = transform
let { document } = state
const { state } = transform
const { document } = state
const node = document.assertDescendant(key)
const path = document.getPath(node)
let parent = document.getParent(node)
const index = parent.nodes.indexOf(node)
const isParent = document == parent
const path = document.getPath(key)
const parentPath = path.slice(0, -1)
const parentIndex = path.slice(-1)
parent = parent.removeNode(index)
document = isParent ? parent : document.updateDescendant(parent)
document = document.normalize()
state = state.merge({ document })
const inverse = {
type: 'insert_node',
path: parentPath,
index: parentIndex,
node,
}
return transform.add(state, {
type: 'remove-node',
const operation = {
type: 'remove_node',
path,
})
inverse,
}
return transform.operate(operation)
}
/**
@ -204,22 +226,22 @@ export function removeNodeByKey(transform, key) {
*/
export function removeTextByKey(transform, key, offset, length) {
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
node = node.removeText(offset, length)
document = document.updateDescendant(node)
document = document.normalize()
state = state.merge({ document })
// TODO!
const inverse = {}
return transform.add(state, {
type: 'remove-text',
const operation = {
type: 'remove_text',
path,
offset,
length,
path,
})
inverse,
}
return transform.operate(operation)
}
/**
@ -237,23 +259,23 @@ export function setMarkByKey(transform, key, offset, length, mark, properties) {
mark = Normalize.mark(mark)
properties = Normalize.markProperties(properties)
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
node = node.updateMark(offset, length, mark, properties)
document = document.updateDescendant(node)
state = state.merge({ document })
// TODO!
const inverse = {}
return transform.add(state, {
type: 'set-mark',
const operation = {
type: 'set_mark',
path,
offset,
length,
mark,
path,
properties,
})
}
return transform.operate(operation)
}
/**
@ -268,21 +290,30 @@ export function setMarkByKey(transform, key, offset, length, mark, properties) {
export function setNodeByKey(transform, key, properties) {
properties = Normalize.nodeProperties(properties)
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
const { state } = transform
const { document } = state
const node = document.assertDescendant(key)
const path = document.getPath(key)
const prevProps = {}
node = node.merge(properties)
document = document.updateDescendant(node)
document = document.normalize()
state = state.merge({ document })
for (const k in properties) {
prevProps[k] = node[k]
}
return transform.add(state, {
type: 'set-node',
const inverse = {
type: 'set_node',
path,
properties: prevProps
}
const operation = {
type: 'set_node',
path,
properties,
})
inverse,
}
return transform.operate(operation)
}
/**
@ -295,53 +326,20 @@ export function setNodeByKey(transform, key, properties) {
*/
export function splitNodeByKey(transform, key, offset) {
let { state } = transform
let { document } = state
let node = document.assertDescendant(key)
const path = document.getPath(node)
let parent = document.getParent(node)
const isParent = document == parent
const index = parent.nodes.indexOf(node)
const { state } = transform
const { document } = state
const path = document.getPath(key)
let child = node
let one
let two
// TODO!
const inverse = {}
if (node.kind != 'text') {
child = node.getTextAtOffset(offset)
}
while (child && child != parent) {
if (child.kind == 'text') {
const i = node.kind == 'text' ? offset : offset - node.getOffset(child)
const { characters } = child
const oneChars = characters.take(i)
const twoChars = characters.skip(i)
one = child.merge({ characters: oneChars })
two = child.merge({ characters: twoChars, key: uid() })
}
else {
const { nodes } = child
const oneNodes = nodes.takeUntil(n => n.key == one.key).push(one)
const twoNodes = nodes.skipUntil(n => n.key == one.key).rest().unshift(two)
one = child.merge({ nodes: oneNodes })
two = child.merge({ nodes: twoNodes, key: uid() })
}
child = document.getParent(child)
}
parent = parent.removeNode(index)
parent = parent.insertNode(index, two)
parent = parent.insertNode(index, one)
document = isParent ? parent : document.updateDescendant(parent)
state = state.merge({ document })
return transform.add(state, {
type: 'split-node',
offset,
const operation = {
type: 'split_node',
path,
})
offset,
inverse,
}
return transform.operate(operation)
}

View File

@ -98,7 +98,7 @@ import {
moveTo,
moveToOffsets,
moveToRangeOf,
updateSelection,
setSelection,
} from './on-selection'
/**
@ -210,7 +210,7 @@ export default {
moveTo,
moveToOffsets,
moveToRangeOf,
updateSelection,
setSelection,
/**
* Normalize.

View File

@ -13,7 +13,15 @@ export function blur(transform) {
const { state } = transform
const { selection } = state
const sel = selection.blur()
return transform.updateSelection(sel)
return transform.operate({
type: 'set_selection',
selection: sel,
inverse: {
type: 'set_selection',
selection,
}
})
}
/**
@ -27,7 +35,7 @@ export function collapseToAnchor(transform) {
const { state } = transform
const { selection } = state
const sel = selection.collapseToAnchor()
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -41,7 +49,7 @@ export function collapseToFocus(transform) {
const { state } = transform
const { selection } = state
const sel = selection.collapseToFocus()
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -56,7 +64,7 @@ export function collapseToEndOf(transform, node) {
const { state } = transform
const { selection } = state
const sel = selection.collapseToEndOf(node)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -75,7 +83,7 @@ export function collapseToEndOfNextBlock(transform) {
if (!next) return transform
const sel = selection.collapseToEndOf(next)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -94,7 +102,7 @@ export function collapseToEndOfNextText(transform) {
if (!next) return transform
const sel = selection.collapseToEndOf(next)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -113,7 +121,7 @@ export function collapseToEndOfPreviousBlock(transform) {
if (!previous) return transform
const sel = selection.collapseToEndOf(previous)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -132,7 +140,7 @@ export function collapseToEndOfPreviousText(transform) {
if (!previous) return transform
const sel = selection.collapseToEndOf(previous)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -147,7 +155,7 @@ export function collapseToStartOf(transform, node) {
const { state } = transform
const { selection } = state
const sel = selection.collapseToStartOf(node)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -166,7 +174,7 @@ export function collapseToStartOfNextBlock(transform) {
if (!next) return transform
const sel = selection.collapseToStartOf(next)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -185,7 +193,7 @@ export function collapseToStartOfNextText(transform) {
if (!next) return transform
const sel = selection.collapseToStartOf(next)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -204,7 +212,7 @@ export function collapseToStartOfPreviousBlock(transform) {
if (!previous) return transform
const sel = selection.collapseToStartOf(previous)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -223,7 +231,7 @@ export function collapseToStartOfPreviousText(transform) {
if (!previous) return transform
const sel = selection.collapseToStartOf(previous)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -238,7 +246,7 @@ export function extendBackward(transform, n) {
const { state } = transform
const { document, selection } = state
const sel = selection.extendBackward(n).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -253,7 +261,7 @@ export function extendForward(transform, n) {
const { state } = transform
const { document, selection } = state
const sel = selection.extendForward(n).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -268,7 +276,7 @@ export function extendToEndOf(transform, node) {
const { state } = transform
const { document, selection } = state
const sel = selection.extendToEndOf(node).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -283,7 +291,7 @@ export function extendToStartOf(transform, node) {
const { state } = transform
const { document, selection } = state
const sel = selection.extendToStartOf(node).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -297,7 +305,7 @@ export function focus(transform) {
const { state } = transform
const { selection } = state
const sel = selection.focus()
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -312,7 +320,7 @@ export function moveBackward(transform, n) {
const { state } = transform
const { document, selection } = state
const sel = selection.moveBackward(n).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -327,7 +335,7 @@ export function moveForward(transform, n) {
const { state } = transform
const { document, selection } = state
const sel = selection.moveForward(n).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -343,7 +351,7 @@ export function moveTo(transform, properties) {
const { state } = transform
const { document, selection } = state
const sel = selection.merge(properties).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -359,7 +367,7 @@ export function moveToOffsets(transform, anchor, fokus) {
const { state } = transform
const { document, selection } = state
const sel = selection.moveToOffsets(anchor, fokus)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
@ -375,27 +383,31 @@ export function moveToRangeOf(transform, start, end) {
const { state } = transform
const { document, selection } = state
const sel = selection.moveToRangeOf(start, end).normalize(document)
return transform.updateSelection(sel)
return transform.setSelection(sel)
}
/**
* Update the selection with a new `selection`.
* Set the selection to a new `selection`.
*
* @param {Transform} transform
* @param {Mixed} sel
* @param {Mixed} selection
* @return {Transform}
*/
export function updateSelection(transform, sel) {
sel = Normalize.selection(sel)
export function setSelection(transform, selection) {
selection = Normalize.selection(selection)
const { state } = transform
let { state } = transform
let { selection } = state
selection = selection.merge(sel)
state = state.merge({ selection })
const inverse = {
type: 'set_selection',
selection: state.selection,
}
return transform.add(state, {
type: 'update-selection',
const operation = {
type: 'set_selection',
selection,
})
inverse,
}
return transform.operate(operation)
}

View File

@ -0,0 +1,279 @@
import uid from '../utils/uid'
/**
* Add mark to text at `offset` and `length` in node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function addMark(state, operation) {
const { path, offset, length, mark } = operation
let { document } = state
let node = document.assertPath(path)
node = node.addMark(offset, length, mark)
document = document.updateDescendant(node)
state = state.merge({ document })
return state
}
/**
* Insert a `node` at `index` in a node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function insertNode(state, operation) {
const { path, index, node } = operation
let { document } = state
let parent = document.assertPath(path)
const isParent = document == parent
const nodes = parent.nodes.splice(index, 0, node)
parent = parent.merge({ nodes })
document = isParent ? parent : document.updateDescendant(parent)
state = state.merge({ document })
return state
}
/**
* Insert `text` at `offset` in node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function insertText(state, operation) {
const { path, offset, text, marks } = operation
let { document } = state
let node = document.assertPath(path)
node = node.insertText(offset, text, marks)
document = document.updateDescendant(node)
state = state.merge({ document })
return state
}
/**
* Move a node by `key` to a new parent by `key` and `index`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function moveNode(state, operation) {
const { path, newPath, newIndex } = operation
let { document } = state
const node = document.assertPath(path)
let parent = document.getParent(node)
const isParent = document == parent
const index = parent.nodes.indexOf(node)
parent = parent.removeNode(index)
document = isParent ? parent : document.updateDescendant(parent)
let target = document.assertPath(newPath)
const isTarget = document == target
target = target.insertNode(newIndex, node)
document = isTarget ? target : document.updateDescendant(target)
state = state.merge({ document })
return state
}
/**
* Remove mark from text at `offset` and `length` in node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function removeMark(state, operation) {
const { path, offset, length, mark } = operation
let { document } = state
let node = document.assertPath(path)
node = node.removeMark(offset, length, mark)
document = document.updateDescendant(node)
state = state.merge({ document })
return state
}
/**
* Remove a node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function removeNode(state, operation) {
const { path } = operation
let { document } = state
const node = document.assertPath(path)
let parent = document.getParent(node)
const index = parent.nodes.indexOf(node)
const isParent = document == parent
parent = parent.removeNode(index)
document = isParent ? parent : document.updateDescendant(parent)
document = document.normalize()
state = state.merge({ document })
return state
}
/**
* Remove text at `offset` and `length` in node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function removeText(state, operation) {
const { path, offset, length } = operation
let { document } = state
let node = document.assertPath(path)
node = node.removeText(offset, length)
document = document.updateDescendant(node)
document = document.normalize()
state = state.merge({ document })
return state
}
/**
* Set `properties` on mark on text at `offset` and `length` in node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function setMark(state, operation) {
const { path, offset, length, mark, properties } = operation
let { document } = state
let node = document.assertPath(path)
node = node.updateMark(offset, length, mark, properties)
document = document.updateDescendant(node)
state = state.merge({ document })
return state
}
/**
* Set `properties` on a node by `key`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function setNode(state, operation) {
const { path, properties } = operation
let { document } = state
let node = document.assertPath(path)
node = node.merge(properties)
document = document.updateDescendant(node)
document = document.normalize()
state = state.merge({ document })
return state
}
/**
* Set the selection to a new `selection`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function setSelection(state, operation) {
let { selection } = operation
selection = state.selection.merge(selection)
state = state.merge({ selection })
return state
}
/**
* Split a node by `key` at `offset`.
*
* @param {State} state
* @param {Object} operation
* @return {State}
*/
function splitNode(state, operation) {
const { path, offset } = operation
let { document } = state
let node = document.assertPath(path)
let parent = document.getParent(node)
const isParent = document == parent
const index = parent.nodes.indexOf(node)
let child = node
let one
let two
if (node.kind != 'text') {
child = node.getTextAtOffset(offset)
}
while (child && child != parent) {
if (child.kind == 'text') {
const i = node.kind == 'text' ? offset : offset - node.getOffset(child)
const { characters } = child
const oneChars = characters.take(i)
const twoChars = characters.skip(i)
one = child.merge({ characters: oneChars })
two = child.merge({ characters: twoChars, key: uid() })
}
else {
const { nodes } = child
const oneNodes = nodes.takeUntil(n => n.key == one.key).push(one)
const twoNodes = nodes.skipUntil(n => n.key == one.key).rest().unshift(two)
one = child.merge({ nodes: oneNodes })
two = child.merge({ nodes: twoNodes, key: uid() })
}
child = document.getParent(child)
}
parent = parent.removeNode(index)
parent = parent.insertNode(index, two)
parent = parent.insertNode(index, one)
document = isParent ? parent : document.updateDescendant(parent)
state = state.merge({ document })
return state
}
/**
* Export.
*
* @type {Object}
*/
export default {
// Text operations.
insert_text: insertText,
remove_text: removeText,
// Mark operations.
add_mark: addMark,
remove_mark: removeMark,
set_mark: setMark,
// Node operations.
insert_node: insertNode,
move_node: moveNode,
remove_node: removeNode,
set_node: setNode,
split_node: splitNode,
// Selection operations.
set_selection: setSelection,
}