mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-22 23:12:52 +02:00
lots of work on refactoring for history
This commit is contained in:
@@ -60,7 +60,6 @@ class Links extends React.Component {
|
||||
|
||||
onChange = (state) => {
|
||||
this.setState({ state })
|
||||
console.log(state.document.toJS())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,7 +1,5 @@
|
||||
|
||||
import Operations from '../transforms/operations'
|
||||
import Transforms from '../transforms'
|
||||
import { List, Record } from 'immutable'
|
||||
|
||||
/**
|
||||
* Transform.
|
||||
@@ -18,10 +16,13 @@ class Transform {
|
||||
*/
|
||||
|
||||
constructor(properties) {
|
||||
const { state } = properties
|
||||
this.initialState = state
|
||||
const { state, operations = [] } = properties
|
||||
this.state = state
|
||||
this.operations = []
|
||||
|
||||
operations.forEach(op => {
|
||||
this.applyOperation(op)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,26 +35,6 @@ class Transform {
|
||||
return 'transform'
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an `operation` to the transform that resulted in `state`.
|
||||
*
|
||||
* @param {Object} operation
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the transform and return the new state.
|
||||
*
|
||||
@@ -64,38 +45,38 @@ class Transform {
|
||||
*/
|
||||
|
||||
apply(options = {}) {
|
||||
let { initialState, state, operations } = this
|
||||
let { history, selection } = state
|
||||
let { marks } = selection
|
||||
let { state, operations } = this
|
||||
let { history } = state
|
||||
let { undos, redos } = history
|
||||
|
||||
// If there are no operations, abort early.
|
||||
if (!operations.length) return state
|
||||
|
||||
// The `isNative` flag allows for natively-handled changes to skip
|
||||
// rerendering the editor for improved performance.
|
||||
const isNative = !!options.isNative
|
||||
|
||||
// Determine whether we need to create a new snapshot.
|
||||
const shouldSnapshot = options.snapshot == null
|
||||
? this.shouldSnapshot()
|
||||
: options.snapshot
|
||||
|
||||
// If we should, save a snapshot into the history before transforming.
|
||||
// Either create a new snapshot, or push the operations into the previous.
|
||||
if (shouldSnapshot) {
|
||||
const snapshot = this.snapshot()
|
||||
const snapshot = { operations }
|
||||
undos = undos.push(snapshot)
|
||||
if (undos.size > 100) undos = undos.take(100)
|
||||
redos = redos.clear()
|
||||
history = history.merge({ undos, redos })
|
||||
state = state.merge({ history })
|
||||
} else {
|
||||
const snapshot = undos.peek()
|
||||
snapshot.operations = snapshot.operations.concat(operations)
|
||||
}
|
||||
|
||||
// Check whether to remove the cursor marks.
|
||||
// if (options.snapshot !== false && initialState.selection.marks) {
|
||||
// selection = selection.merge({ marks: null })
|
||||
// state = state.merge({ selection })
|
||||
// }
|
||||
|
||||
// Apply the "isNative" flag, which is used to allow for natively-handled
|
||||
// content changes to skip rerendering the editor for performance.
|
||||
state = state.merge({
|
||||
isNative: !!options.isNative
|
||||
})
|
||||
// Clear the redo stack and constrain the undos stack.
|
||||
if (undos.size > 100) undos = undos.take(100)
|
||||
redos = redos.clear()
|
||||
|
||||
// Update the state.
|
||||
history = history.merge({ undos, redos })
|
||||
state = state.merge({ history, isNative })
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -114,69 +95,32 @@ class Transform {
|
||||
// If there isn't a previous state, snapshot.
|
||||
if (!previous) return true
|
||||
|
||||
// If the only operations applied are selection operations, don't snapshot.
|
||||
const onlySelections = operations.every(op => op.type == 'set_selection')
|
||||
if (onlySelections) return false
|
||||
const types = operations.map(op => op.type)
|
||||
const prevTypes = previous.operations.map(op => op.type)
|
||||
const edits = types.filter(type => type != 'set_selection')
|
||||
const prevEdits = prevTypes.filter(type => type != 'set_selection')
|
||||
|
||||
// If the current operations aren't one of the "combinable" types, snapshot.
|
||||
const onlyInsert = operations.every(op => op.type == 'insert_text')
|
||||
const onlyRemove = operations.every(op => op.type == 'remove_text')
|
||||
const onlyPrevInsert = previous.operations.every(op => op.type == 'insert_text')
|
||||
const onlyPrevRemove = previous.operations.every(op => op.type == 'remove_text')
|
||||
if (onlyInsert && onlyPrevInsert) return false
|
||||
if (onlyRemove && onlyPrevRemove) return false
|
||||
const onlySelections = types.every(type => type == 'set_selection')
|
||||
const onlyInsert = edits.every(type => type == 'insert_text')
|
||||
const onlyRemove = edits.every(type => type == 'remove_text')
|
||||
const prevOnlySelections = prevTypes.every(type => type == 'set_selection')
|
||||
const prevOnlyInsert = prevEdits.every(type => type == 'insert_text')
|
||||
const prevOnlyRemove = prevEdits.every(type => type == 'remove_text')
|
||||
|
||||
// If the only operations applied are selection operations, or if the
|
||||
// current operations are all text editing, don't snapshot.
|
||||
if (
|
||||
(onlySelections) ||
|
||||
(!prevOnlySelections && onlyInsert && prevOnlyInsert) ||
|
||||
(!prevOnlySelections && onlyRemove && prevOnlyRemove)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, snapshot.
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a history-ready snapshot of the current state.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
snapshot() {
|
||||
let { initialState, operations } = this
|
||||
let { document, selection } = initialState
|
||||
return { document, selection, operations }
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo to the previous state in the history.
|
||||
*
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
undo() {
|
||||
let { state } = this
|
||||
let { history } = state
|
||||
let { undos, redos } = history
|
||||
|
||||
// If there's no previous snapshot, return the current state.
|
||||
let previous = undos.peek()
|
||||
if (!previous) return state
|
||||
|
||||
// Remove the previous snapshot from the undo stack.
|
||||
undos = undos.pop()
|
||||
|
||||
// Snapshot the current state, and move it into the redos stack.
|
||||
let snapshot = this.snapshot()
|
||||
redos = redos.push(snapshot)
|
||||
|
||||
// Return the previous state, with the updated history.
|
||||
let { document, selection } = previous
|
||||
history = history.merge({ undos, redos })
|
||||
state = state.merge({
|
||||
document,
|
||||
selection,
|
||||
history,
|
||||
isNative: false
|
||||
})
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Redo to the next state in the history.
|
||||
*
|
||||
@@ -192,21 +136,58 @@ class Transform {
|
||||
let next = redos.peek()
|
||||
if (!next) return state
|
||||
|
||||
// Remove the next history from the redo stack.
|
||||
// Shift the next state into the undo stack.
|
||||
redos = redos.pop()
|
||||
undos = undos.push(next)
|
||||
|
||||
// Snapshot the current state, and move it into the undos stack.
|
||||
let snapshot = this.snapshot()
|
||||
undos = undos.push(snapshot)
|
||||
// Replay the next operations.
|
||||
const { operations } = next
|
||||
operations.forEach(op => {
|
||||
this.applyOperation(op)
|
||||
})
|
||||
|
||||
// Return the next state, with the updated history.
|
||||
let { document, selection } = next
|
||||
// Update the state's history and force `isNative` to false.
|
||||
history = history.merge({ undos, redos })
|
||||
state = state.merge({
|
||||
document,
|
||||
selection,
|
||||
state = this.state.merge({
|
||||
history,
|
||||
isNative: false
|
||||
isNative: false,
|
||||
})
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the previous operations in the history.
|
||||
*
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
undo() {
|
||||
let { state } = this
|
||||
let { history } = state
|
||||
let { undos, redos } = history
|
||||
|
||||
// If there's no previous snapshot, return the current state.
|
||||
let previous = undos.peek()
|
||||
if (!previous) return state
|
||||
|
||||
// Shift the previous operations into the redo stack.
|
||||
undos = undos.pop()
|
||||
redos = redos.push(previous)
|
||||
|
||||
// Replay the inverse of the previous operations.
|
||||
const operations = previous.operations.slice().reverse()
|
||||
operations.forEach(op => {
|
||||
op.inverse.forEach(inv => {
|
||||
this.applyOperation(inv)
|
||||
})
|
||||
})
|
||||
|
||||
// Update the state's history and force `isNative` to false.
|
||||
history = history.merge({ undos, redos })
|
||||
state = this.state.merge({
|
||||
history,
|
||||
isNative: false,
|
||||
})
|
||||
|
||||
return state
|
||||
|
@@ -568,7 +568,6 @@ function Plugin(options = {}) {
|
||||
return state
|
||||
.transform()
|
||||
.moveTo(selection)
|
||||
.focus()
|
||||
.apply()
|
||||
}
|
||||
|
||||
|
297
lib/transforms/apply-operation.js
Normal file
297
lib/transforms/apply-operation.js
Normal file
@@ -0,0 +1,297 @@
|
||||
|
||||
import uid from '../utils/uid'
|
||||
|
||||
/**
|
||||
* Operations.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const OPERATIONS = {
|
||||
// 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,
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an `operation` to the current state.
|
||||
*
|
||||
* @param {Transform} transform
|
||||
* @param {Object} operation
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
export function applyOperation(transform, operation) {
|
||||
let { state, operations } = transform
|
||||
const { type } = operation
|
||||
const fn = OPERATIONS[type]
|
||||
|
||||
if (!fn) {
|
||||
throw new Error(`Unknown operation type: "${type}".`)
|
||||
}
|
||||
|
||||
transform.state = fn(state, operation)
|
||||
transform.operations = operations.concat([operation])
|
||||
return transform
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mark to text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @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 `path`.
|
||||
*
|
||||
* @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 `path`.
|
||||
*
|
||||
* @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 `path` to a new parent by `path` 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 `path`.
|
||||
*
|
||||
* @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 `path`.
|
||||
*
|
||||
* @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 `path`.
|
||||
*
|
||||
* @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 `path`.
|
||||
*
|
||||
* @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 `path`.
|
||||
*
|
||||
* @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 `properties` on the selection.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function setSelection(state, operation) {
|
||||
let { properties } = operation
|
||||
let { selection } = state
|
||||
selection = selection.merge(properties)
|
||||
state = state.merge({ selection })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a node by `path` 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
|
||||
}
|
@@ -22,13 +22,13 @@ export function addMark(transform, mark) {
|
||||
else if (selection.marks) {
|
||||
const marks = selection.marks.add(mark)
|
||||
const sel = selection.merge({ marks })
|
||||
return transform.setSelection(sel)
|
||||
return transform.moveTo(sel)
|
||||
}
|
||||
|
||||
else {
|
||||
const marks = document.getMarksAtRange(selection).add(mark)
|
||||
const sel = selection.merge({ marks })
|
||||
return transform.setSelection(sel)
|
||||
return transform.moveTo(sel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export function _delete(transform) {
|
||||
|
||||
return transform
|
||||
.deleteAtRange(selection)
|
||||
.setSelection(after)
|
||||
.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +155,7 @@ export function deleteBackward(transform, n = 1) {
|
||||
|
||||
return transform
|
||||
.deleteBackwardAtRange(selection, n)
|
||||
.setSelection(after)
|
||||
.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +214,7 @@ export function deleteForward(transform, n = 1) {
|
||||
|
||||
return transform
|
||||
.deleteForwardAtRange(selection, n)
|
||||
.setSelection(after)
|
||||
.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,7 +237,7 @@ export function insertBlock(transform, block) {
|
||||
const text = document.getTexts().find(n => !keys.includes(n.key))
|
||||
const after = selection.collapseToEndOf(text)
|
||||
|
||||
return transform.setSelection(after)
|
||||
return transform.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +287,7 @@ export function insertFragment(transform, fragment) {
|
||||
.moveForward(lastText.length)
|
||||
}
|
||||
|
||||
return transform.setSelection(after)
|
||||
return transform.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,7 +319,7 @@ export function insertInline(transform, inline) {
|
||||
after = selection.collapseToEndOf(text)
|
||||
}
|
||||
|
||||
return transform.setSelection(after)
|
||||
return transform.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +354,7 @@ export function insertText(transform, text, marks) {
|
||||
|
||||
return transform
|
||||
.insertTextAtRange(selection, text, marks)
|
||||
.setSelection(after)
|
||||
.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,7 +407,7 @@ export function splitBlock(transform, depth = 1) {
|
||||
const nextNode = document.getNextText(startNode)
|
||||
const after = selection.collapseToStartOf(nextNode)
|
||||
|
||||
return transform.setSelection(after)
|
||||
return transform.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,7 +436,7 @@ export function splitInline(transform, depth = Infinity) {
|
||||
after = selection.collapseToStartOf(nextNode)
|
||||
}
|
||||
|
||||
return transform.setSelection(after)
|
||||
return transform.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,13 +460,13 @@ export function removeMark(transform, mark) {
|
||||
else if (selection.marks) {
|
||||
const marks = selection.marks.remove(mark)
|
||||
const sel = selection.merge({ marks })
|
||||
return transform.setSelection(sel)
|
||||
return transform.moveTo(sel)
|
||||
}
|
||||
|
||||
else {
|
||||
const marks = document.getMarksAtRange(selection).remove(mark)
|
||||
const sel = selection.merge({ marks })
|
||||
return transform.setSelection(sel)
|
||||
return transform.moveTo(sel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ export function wrapInline(transform, properties) {
|
||||
}
|
||||
|
||||
after = after.normalize(document)
|
||||
return transform.setSelection(after)
|
||||
return transform.moveTo(after)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,5 +613,5 @@ export function wrapText(transform, prefix, suffix = prefix) {
|
||||
|
||||
return transform
|
||||
.wrapTextAtRange(selection, prefix, suffix)
|
||||
.setSelection(after)
|
||||
.moveTo(after)
|
||||
}
|
||||
|
@@ -15,29 +15,10 @@ import uid from '../utils/uid'
|
||||
|
||||
export function addMarkByKey(transform, key, offset, length, mark) {
|
||||
mark = Normalize.mark(mark)
|
||||
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
|
||||
const inverse = {
|
||||
type: 'remove_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'add_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.addMarkOperation(path, offset, length, mark)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,21 +36,7 @@ export function insertNodeByKey(transform, key, index, node) {
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
const newPath = path.slice().push(index)
|
||||
|
||||
const inverse = {
|
||||
type: 'remove_node',
|
||||
path: newPath,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'insert_node',
|
||||
path,
|
||||
index,
|
||||
node,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.insertNodeOperation(path, index, node)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,23 +54,7 @@ export function insertTextByKey(transform, key, offset, text, marks) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
|
||||
const inverse = {
|
||||
type: 'remove_text',
|
||||
path,
|
||||
offset,
|
||||
length: text.length,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'insert_text',
|
||||
path,
|
||||
offset,
|
||||
text,
|
||||
marks,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.insertTextOperation(path, offset, text, marks)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,30 +70,9 @@ export function insertTextByKey(transform, key, offset, text, marks) {
|
||||
export function moveNodeByKey(transform, key, newKey, newIndex) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const node = document.assertDescendant(key)
|
||||
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)
|
||||
const nodePath = newPath.slice().concat([newIndex])
|
||||
|
||||
const inverse = {
|
||||
type: 'move_node',
|
||||
path: nodePath,
|
||||
newPath: parentPath,
|
||||
newIndex: parentIndex,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'move_node',
|
||||
path,
|
||||
newPath,
|
||||
newIndex,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.moveNodeOperation(path, newPath, newIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,29 +88,10 @@ export function moveNodeByKey(transform, key, newKey, newIndex) {
|
||||
|
||||
export function removeMarkByKey(transform, key, offset, length, mark) {
|
||||
mark = Normalize.mark(mark)
|
||||
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
|
||||
const inverse = {
|
||||
type: 'add_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'remove_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.removeMarkOperation(path, offset, length, mark)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,25 +105,8 @@ export function removeMarkByKey(transform, key, offset, length, mark) {
|
||||
export function removeNodeByKey(transform, key) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const node = document.assertDescendant(key)
|
||||
const path = document.getPath(key)
|
||||
const parentPath = path.slice(0, -1)
|
||||
const parentIndex = path.slice(-1)
|
||||
|
||||
const inverse = {
|
||||
type: 'insert_node',
|
||||
path: parentPath,
|
||||
index: parentIndex,
|
||||
node,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'remove_node',
|
||||
path,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.removeNodeOperation(path)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,19 +123,7 @@ export function removeTextByKey(transform, key, offset, length) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
|
||||
// TODO!
|
||||
const inverse = {}
|
||||
|
||||
const operation = {
|
||||
type: 'remove_text',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.removeTextOperation(path, offset, length)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,36 +140,10 @@ export function removeTextByKey(transform, key, offset, length) {
|
||||
export function setMarkByKey(transform, key, offset, length, mark, properties) {
|
||||
mark = Normalize.mark(mark)
|
||||
properties = Normalize.markProperties(properties)
|
||||
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
const prevProps = {}
|
||||
|
||||
for (const k in properties) {
|
||||
prevProps[k] = mark[k]
|
||||
}
|
||||
|
||||
const inverse = {
|
||||
type: 'set_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
properties: prevProps,
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'set_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
properties,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.setMarkOperation(path, offset, length, mark, properties)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,31 +157,11 @@ export function setMarkByKey(transform, key, offset, length, mark, properties) {
|
||||
|
||||
export function setNodeByKey(transform, key, properties) {
|
||||
properties = Normalize.nodeProperties(properties)
|
||||
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const node = document.assertDescendant(key)
|
||||
const path = document.getPath(key)
|
||||
const prevProps = {}
|
||||
|
||||
for (const k in properties) {
|
||||
prevProps[k] = node[k]
|
||||
}
|
||||
|
||||
const inverse = {
|
||||
type: 'set_node',
|
||||
path,
|
||||
properties: prevProps
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'set_node',
|
||||
path,
|
||||
properties,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.setNodeOperation(path, properties)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,17 +177,5 @@ export function splitNodeByKey(transform, key, offset) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
|
||||
// TODO!
|
||||
const inverse = {}
|
||||
|
||||
const operation = {
|
||||
type: 'split_node',
|
||||
path,
|
||||
offset,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.splitNodeOperation(path, offset)
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,30 @@
|
||||
|
||||
/**
|
||||
* Apply operation.
|
||||
*/
|
||||
|
||||
import {
|
||||
applyOperation,
|
||||
} from './apply-operation'
|
||||
|
||||
/**
|
||||
* Operations.
|
||||
*/
|
||||
|
||||
import {
|
||||
addMarkOperation,
|
||||
insertNodeOperation,
|
||||
insertTextOperation,
|
||||
moveNodeOperation,
|
||||
removeMarkOperation,
|
||||
removeNodeOperation,
|
||||
removeTextOperation,
|
||||
setMarkOperation,
|
||||
setNodeOperation,
|
||||
setSelectionOperation,
|
||||
splitNodeOperation,
|
||||
} from './operations'
|
||||
|
||||
/**
|
||||
* At range.
|
||||
*/
|
||||
@@ -98,7 +124,6 @@ import {
|
||||
moveTo,
|
||||
moveToOffsets,
|
||||
moveToRangeOf,
|
||||
setSelection,
|
||||
} from './on-selection'
|
||||
|
||||
/**
|
||||
@@ -118,6 +143,28 @@ import {
|
||||
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Apply operation.
|
||||
*/
|
||||
|
||||
applyOperation,
|
||||
|
||||
/**
|
||||
* Operations.
|
||||
*/
|
||||
|
||||
addMarkOperation,
|
||||
insertNodeOperation,
|
||||
insertTextOperation,
|
||||
moveNodeOperation,
|
||||
removeMarkOperation,
|
||||
removeNodeOperation,
|
||||
removeTextOperation,
|
||||
setMarkOperation,
|
||||
setNodeOperation,
|
||||
setSelectionOperation,
|
||||
splitNodeOperation,
|
||||
|
||||
/**
|
||||
* At range.
|
||||
*/
|
||||
@@ -210,7 +257,6 @@ export default {
|
||||
moveTo,
|
||||
moveToOffsets,
|
||||
moveToRangeOf,
|
||||
setSelection,
|
||||
|
||||
/**
|
||||
* Normalize.
|
||||
|
@@ -1,7 +1,4 @@
|
||||
|
||||
import Normalize from '../utils/normalize'
|
||||
import Selection from '../models/selection'
|
||||
|
||||
/**
|
||||
* Blur the selection.
|
||||
*
|
||||
@@ -13,7 +10,7 @@ export function blur(transform) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.blur()
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,7 +24,7 @@ export function collapseToAnchor(transform) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.collapseToAnchor()
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +38,7 @@ export function collapseToEnd(transform) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.collapseToEnd()
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +52,7 @@ export function collapseToFocus(transform) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.collapseToFocus()
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +66,7 @@ export function collapseToStart(transform) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.collapseToStart()
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +81,7 @@ export function collapseToEndOf(transform, node) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.collapseToEndOf(node)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +100,7 @@ export function collapseToEndOfNextBlock(transform) {
|
||||
if (!next) return transform
|
||||
|
||||
const sel = selection.collapseToEndOf(next)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +119,7 @@ export function collapseToEndOfNextText(transform) {
|
||||
if (!next) return transform
|
||||
|
||||
const sel = selection.collapseToEndOf(next)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +138,7 @@ export function collapseToEndOfPreviousBlock(transform) {
|
||||
if (!previous) return transform
|
||||
|
||||
const sel = selection.collapseToEndOf(previous)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +157,7 @@ export function collapseToEndOfPreviousText(transform) {
|
||||
if (!previous) return transform
|
||||
|
||||
const sel = selection.collapseToEndOf(previous)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +172,7 @@ export function collapseToStartOf(transform, node) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.collapseToStartOf(node)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +191,7 @@ export function collapseToStartOfNextBlock(transform) {
|
||||
if (!next) return transform
|
||||
|
||||
const sel = selection.collapseToStartOf(next)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +210,7 @@ export function collapseToStartOfNextText(transform) {
|
||||
if (!next) return transform
|
||||
|
||||
const sel = selection.collapseToStartOf(next)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +229,7 @@ export function collapseToStartOfPreviousBlock(transform) {
|
||||
if (!previous) return transform
|
||||
|
||||
const sel = selection.collapseToStartOf(previous)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +248,7 @@ export function collapseToStartOfPreviousText(transform) {
|
||||
if (!previous) return transform
|
||||
|
||||
const sel = selection.collapseToStartOf(previous)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +263,7 @@ export function extendBackward(transform, n) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.extendBackward(n).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,7 +278,7 @@ export function extendForward(transform, n) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.extendForward(n).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +293,7 @@ export function extendToEndOf(transform, node) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.extendToEndOf(node).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,7 +308,7 @@ export function extendToStartOf(transform, node) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.extendToStartOf(node).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,7 +322,7 @@ export function focus(transform) {
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const sel = selection.focus()
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,7 +337,7 @@ export function moveBackward(transform, n) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.moveBackward(n).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,7 +352,7 @@ export function moveForward(transform, n) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.moveForward(n).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,11 +364,7 @@ export function moveForward(transform, n) {
|
||||
*/
|
||||
|
||||
export function moveTo(transform, properties) {
|
||||
properties = Normalize.selection(properties)
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.merge(properties).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,7 +380,7 @@ export function moveToOffsets(transform, anchor, fokus) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.moveToOffsets(anchor, fokus)
|
||||
return transform.setSelection(sel)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,42 +396,5 @@ export function moveToRangeOf(transform, start, end) {
|
||||
const { state } = transform
|
||||
const { document, selection } = state
|
||||
const sel = selection.moveToRangeOf(start, end).normalize(document)
|
||||
return transform.setSelection(sel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selection to a new `selection`.
|
||||
*
|
||||
* @param {Transform} transform
|
||||
* @param {Mixed} selection
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
export function setSelection(transform, properties) {
|
||||
properties = Normalize.selectionProperties(properties)
|
||||
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const prevProps = {}
|
||||
|
||||
if (properties.marks == selection.marks) {
|
||||
properties.marks = null
|
||||
}
|
||||
|
||||
for (const k in properties) {
|
||||
prevProps[k] = selection[k]
|
||||
}
|
||||
|
||||
const inverse = {
|
||||
type: 'set_selection',
|
||||
properties: prevProps
|
||||
}
|
||||
|
||||
const operation = {
|
||||
type: 'set_selection',
|
||||
properties,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.operate(operation)
|
||||
return transform.setSelectionOperation(sel)
|
||||
}
|
||||
|
@@ -1,300 +1,375 @@
|
||||
|
||||
import Debug from 'debug'
|
||||
import Normalize from '../utils/normalize'
|
||||
import uid from '../utils/uid'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const debug = Debug('slate:operation')
|
||||
|
||||
/**
|
||||
* Add mark to text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mixed} mark
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function addMark(state, operation) {
|
||||
debug('add_mark', 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
|
||||
export function addMarkOperation(transform, path, offset, length, mark) {
|
||||
const inverse = [{
|
||||
type: 'remove_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'add_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `node` at `index` in a node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} index
|
||||
* @param {Node} node
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function insertNode(state, operation) {
|
||||
debug('insert_node', 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
|
||||
export function insertNodeOperation(transform, path, index, node) {
|
||||
const inversePath = path.slice().concat([index])
|
||||
const inverse = [{
|
||||
type: 'remove_node',
|
||||
path: inversePath,
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'insert_node',
|
||||
path,
|
||||
index,
|
||||
node,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {String} text
|
||||
* @param {Set} marks (optional)
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function insertText(state, operation) {
|
||||
debug('insert_text', 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
|
||||
export function insertTextOperation(transform, path, offset, text, marks) {
|
||||
const inverseLength = text.length
|
||||
const inverse = [{
|
||||
type: 'remove_text',
|
||||
path,
|
||||
offset,
|
||||
length: inverseLength,
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'insert_text',
|
||||
path,
|
||||
offset,
|
||||
text,
|
||||
marks,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a node by `path` to a new parent by `path` and `index`.
|
||||
* Move a node by `path` to a `newPath` and `newIndex`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Array} newPath
|
||||
* @param {Number} newIndex
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function moveNode(state, operation) {
|
||||
debug('move_node', operation)
|
||||
const { path, newPath, newIndex } = operation
|
||||
let { document } = state
|
||||
const node = document.assertPath(path)
|
||||
export function moveNodeOperation(transform, path, newPath, newIndex) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const parentPath = path.slice(0, -1)
|
||||
const parentIndex = path[path.length - 1]
|
||||
const inversePath = newPath.slice().concat([newIndex])
|
||||
|
||||
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)
|
||||
const inverse = [{
|
||||
type: 'move_node',
|
||||
path: inversePath,
|
||||
newPath: parentPath,
|
||||
newIndex: parentIndex,
|
||||
}]
|
||||
|
||||
let target = document.assertPath(newPath)
|
||||
const isTarget = document == target
|
||||
target = target.insertNode(newIndex, node)
|
||||
document = isTarget ? target : document.updateDescendant(target)
|
||||
const operation = {
|
||||
type: 'move_node',
|
||||
path,
|
||||
newPath,
|
||||
newIndex,
|
||||
inverse,
|
||||
}
|
||||
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove mark from text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function removeMark(state, operation) {
|
||||
debug('remove_mark', 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
|
||||
export function removeMarkOperation(transform, path, offset, length, mark) {
|
||||
const inverse = [{
|
||||
type: 'add_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'remove_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function removeNode(state, operation) {
|
||||
debug('remove_node', operation)
|
||||
const { path } = operation
|
||||
let { document } = state
|
||||
export function removeNodeOperation(transform, path) {
|
||||
const { state } = transform
|
||||
const { 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
|
||||
const inversePath = path.slice(0, -1)
|
||||
const inverseIndex = path.slice(-1)
|
||||
|
||||
const inverse = [{
|
||||
type: 'insert_node',
|
||||
path: inversePath,
|
||||
index: inverseIndex,
|
||||
node,
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'remove_node',
|
||||
path,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function removeText(state, operation) {
|
||||
debug('remove_text', 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
|
||||
export function removeTextOperation(transform, path, offset, length) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const node = document.assertPath(path)
|
||||
const ranges = node.getRanges()
|
||||
const inverse = []
|
||||
|
||||
ranges.reduce((start, range) => {
|
||||
const { text, marks } = range
|
||||
const end = start + text.length
|
||||
if (start > offset + length) return
|
||||
if (end <= offset) return
|
||||
|
||||
const endOffset = Math.min(end, offset + length)
|
||||
const string = text.slice(offset, endOffset)
|
||||
|
||||
inverse.push({
|
||||
type: 'insert_text',
|
||||
path,
|
||||
offset,
|
||||
text: string,
|
||||
marks,
|
||||
})
|
||||
|
||||
return end
|
||||
}, 0)
|
||||
|
||||
const operation = {
|
||||
type: 'remove_text',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function setMark(state, operation) {
|
||||
debug('set_mark', 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
|
||||
export function setMarkOperation(transform, path, offset, length, mark, properties) {
|
||||
const inverseProps = {}
|
||||
|
||||
for (const k in properties) {
|
||||
inverseProps[k] = mark[k]
|
||||
}
|
||||
|
||||
const inverse = [{
|
||||
type: 'set_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
properties: inverseProps,
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'set_mark',
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
properties,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on a node by `path`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Object || String} properties
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function setNode(state, operation) {
|
||||
debug('set_node', 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
|
||||
export function setNodeOperation(transform, path, properties) {
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const node = document.assertPath(path)
|
||||
const inverseProps = {}
|
||||
|
||||
for (const k in properties) {
|
||||
inverseProps[k] = node[k]
|
||||
}
|
||||
|
||||
const inverse = [{
|
||||
type: 'set_node',
|
||||
path,
|
||||
properties: inverseProps
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'set_node',
|
||||
path,
|
||||
properties,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on the selection.
|
||||
* Set the selection to a new `selection`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Mixed} selection
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function setSelection(state, operation) {
|
||||
debug('set_selection', operation)
|
||||
let { properties } = operation
|
||||
let { selection } = state
|
||||
selection = selection.merge(properties)
|
||||
state = state.merge({ selection })
|
||||
return state
|
||||
export function setSelectionOperation(transform, properties) {
|
||||
properties = Normalize.selectionProperties(properties)
|
||||
|
||||
const { state } = transform
|
||||
const { selection } = state
|
||||
const prevProps = {}
|
||||
|
||||
if (properties.marks == selection.marks) {
|
||||
properties.marks = null
|
||||
}
|
||||
|
||||
for (const k in properties) {
|
||||
prevProps[k] = selection[k]
|
||||
}
|
||||
|
||||
const inverse = [{
|
||||
type: 'set_selection',
|
||||
properties: prevProps
|
||||
}]
|
||||
|
||||
const operation = {
|
||||
type: 'set_selection',
|
||||
properties,
|
||||
inverse,
|
||||
}
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a node by `path` at `offset`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
* @param {Transform} transform
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @return {Transform}
|
||||
*/
|
||||
|
||||
function splitNode(state, operation) {
|
||||
debug('split_node', 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)
|
||||
export function splitNodeOperation(transform, path, offset) {
|
||||
const inverse = []
|
||||
|
||||
let child = node
|
||||
let one
|
||||
let two
|
||||
|
||||
if (node.kind != 'text') {
|
||||
child = node.getTextAtOffset(offset)
|
||||
const operation = {
|
||||
type: 'split_node',
|
||||
path,
|
||||
offset,
|
||||
inverse,
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
return transform.applyOperation(operation)
|
||||
}
|
||||
|
Reference in New Issue
Block a user