mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-23 15:32:59 +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) => {
|
onChange = (state) => {
|
||||||
this.setState({ state })
|
this.setState({ state })
|
||||||
console.log(state.document.toJS())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
import Operations from '../transforms/operations'
|
|
||||||
import Transforms from '../transforms'
|
import Transforms from '../transforms'
|
||||||
import { List, Record } from 'immutable'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform.
|
* Transform.
|
||||||
@@ -18,10 +16,13 @@ class Transform {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
constructor(properties) {
|
constructor(properties) {
|
||||||
const { state } = properties
|
const { state, operations = [] } = properties
|
||||||
this.initialState = state
|
|
||||||
this.state = state
|
this.state = state
|
||||||
this.operations = []
|
this.operations = []
|
||||||
|
|
||||||
|
operations.forEach(op => {
|
||||||
|
this.applyOperation(op)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,26 +35,6 @@ class Transform {
|
|||||||
return '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.
|
* Apply the transform and return the new state.
|
||||||
*
|
*
|
||||||
@@ -64,38 +45,38 @@ class Transform {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
apply(options = {}) {
|
apply(options = {}) {
|
||||||
let { initialState, state, operations } = this
|
let { state, operations } = this
|
||||||
let { history, selection } = state
|
let { history } = state
|
||||||
let { marks } = selection
|
|
||||||
let { undos, redos } = history
|
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.
|
// Determine whether we need to create a new snapshot.
|
||||||
const shouldSnapshot = options.snapshot == null
|
const shouldSnapshot = options.snapshot == null
|
||||||
? this.shouldSnapshot()
|
? this.shouldSnapshot()
|
||||||
: options.snapshot
|
: 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) {
|
if (shouldSnapshot) {
|
||||||
const snapshot = this.snapshot()
|
const snapshot = { operations }
|
||||||
undos = undos.push(snapshot)
|
undos = undos.push(snapshot)
|
||||||
if (undos.size > 100) undos = undos.take(100)
|
} else {
|
||||||
redos = redos.clear()
|
const snapshot = undos.peek()
|
||||||
history = history.merge({ undos, redos })
|
snapshot.operations = snapshot.operations.concat(operations)
|
||||||
state = state.merge({ history })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether to remove the cursor marks.
|
// Clear the redo stack and constrain the undos stack.
|
||||||
// if (options.snapshot !== false && initialState.selection.marks) {
|
if (undos.size > 100) undos = undos.take(100)
|
||||||
// selection = selection.merge({ marks: null })
|
redos = redos.clear()
|
||||||
// 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
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// Update the state.
|
||||||
|
history = history.merge({ undos, redos })
|
||||||
|
state = state.merge({ history, isNative })
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,69 +95,32 @@ class Transform {
|
|||||||
// If there isn't a previous state, snapshot.
|
// If there isn't a previous state, snapshot.
|
||||||
if (!previous) return true
|
if (!previous) return true
|
||||||
|
|
||||||
// If the only operations applied are selection operations, don't snapshot.
|
const types = operations.map(op => op.type)
|
||||||
const onlySelections = operations.every(op => op.type == 'set_selection')
|
const prevTypes = previous.operations.map(op => op.type)
|
||||||
if (onlySelections) return false
|
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 onlySelections = types.every(type => type == 'set_selection')
|
||||||
const onlyInsert = operations.every(op => op.type == 'insert_text')
|
const onlyInsert = edits.every(type => type == 'insert_text')
|
||||||
const onlyRemove = operations.every(op => op.type == 'remove_text')
|
const onlyRemove = edits.every(type => type == 'remove_text')
|
||||||
const onlyPrevInsert = previous.operations.every(op => op.type == 'insert_text')
|
const prevOnlySelections = prevTypes.every(type => type == 'set_selection')
|
||||||
const onlyPrevRemove = previous.operations.every(op => op.type == 'remove_text')
|
const prevOnlyInsert = prevEdits.every(type => type == 'insert_text')
|
||||||
if (onlyInsert && onlyPrevInsert) return false
|
const prevOnlyRemove = prevEdits.every(type => type == 'remove_text')
|
||||||
if (onlyRemove && onlyPrevRemove) return false
|
|
||||||
|
// 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.
|
// Otherwise, snapshot.
|
||||||
return true
|
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.
|
* Redo to the next state in the history.
|
||||||
*
|
*
|
||||||
@@ -192,21 +136,58 @@ class Transform {
|
|||||||
let next = redos.peek()
|
let next = redos.peek()
|
||||||
if (!next) return state
|
if (!next) return state
|
||||||
|
|
||||||
// Remove the next history from the redo stack.
|
// Shift the next state into the undo stack.
|
||||||
redos = redos.pop()
|
redos = redos.pop()
|
||||||
|
undos = undos.push(next)
|
||||||
|
|
||||||
// Snapshot the current state, and move it into the undos stack.
|
// Replay the next operations.
|
||||||
let snapshot = this.snapshot()
|
const { operations } = next
|
||||||
undos = undos.push(snapshot)
|
operations.forEach(op => {
|
||||||
|
this.applyOperation(op)
|
||||||
|
})
|
||||||
|
|
||||||
// Return the next state, with the updated history.
|
// Update the state's history and force `isNative` to false.
|
||||||
let { document, selection } = next
|
|
||||||
history = history.merge({ undos, redos })
|
history = history.merge({ undos, redos })
|
||||||
state = state.merge({
|
state = this.state.merge({
|
||||||
document,
|
|
||||||
selection,
|
|
||||||
history,
|
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
|
return state
|
||||||
|
@@ -568,7 +568,6 @@ function Plugin(options = {}) {
|
|||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.moveTo(selection)
|
.moveTo(selection)
|
||||||
.focus()
|
|
||||||
.apply()
|
.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) {
|
else if (selection.marks) {
|
||||||
const marks = selection.marks.add(mark)
|
const marks = selection.marks.add(mark)
|
||||||
const sel = selection.merge({ marks })
|
const sel = selection.merge({ marks })
|
||||||
return transform.setSelection(sel)
|
return transform.moveTo(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
const marks = document.getMarksAtRange(selection).add(mark)
|
const marks = document.getMarksAtRange(selection).add(mark)
|
||||||
const sel = selection.merge({ marks })
|
const sel = selection.merge({ marks })
|
||||||
return transform.setSelection(sel)
|
return transform.moveTo(sel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export function _delete(transform) {
|
|||||||
|
|
||||||
return transform
|
return transform
|
||||||
.deleteAtRange(selection)
|
.deleteAtRange(selection)
|
||||||
.setSelection(after)
|
.moveTo(after)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,7 +155,7 @@ export function deleteBackward(transform, n = 1) {
|
|||||||
|
|
||||||
return transform
|
return transform
|
||||||
.deleteBackwardAtRange(selection, n)
|
.deleteBackwardAtRange(selection, n)
|
||||||
.setSelection(after)
|
.moveTo(after)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,7 +214,7 @@ export function deleteForward(transform, n = 1) {
|
|||||||
|
|
||||||
return transform
|
return transform
|
||||||
.deleteForwardAtRange(selection, n)
|
.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 text = document.getTexts().find(n => !keys.includes(n.key))
|
||||||
const after = selection.collapseToEndOf(text)
|
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)
|
.moveForward(lastText.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transform.setSelection(after)
|
return transform.moveTo(after)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -319,7 +319,7 @@ export function insertInline(transform, inline) {
|
|||||||
after = selection.collapseToEndOf(text)
|
after = selection.collapseToEndOf(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transform.setSelection(after)
|
return transform.moveTo(after)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,7 +354,7 @@ export function insertText(transform, text, marks) {
|
|||||||
|
|
||||||
return transform
|
return transform
|
||||||
.insertTextAtRange(selection, text, marks)
|
.insertTextAtRange(selection, text, marks)
|
||||||
.setSelection(after)
|
.moveTo(after)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -407,7 +407,7 @@ export function splitBlock(transform, depth = 1) {
|
|||||||
const nextNode = document.getNextText(startNode)
|
const nextNode = document.getNextText(startNode)
|
||||||
const after = selection.collapseToStartOf(nextNode)
|
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)
|
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) {
|
else if (selection.marks) {
|
||||||
const marks = selection.marks.remove(mark)
|
const marks = selection.marks.remove(mark)
|
||||||
const sel = selection.merge({ marks })
|
const sel = selection.merge({ marks })
|
||||||
return transform.setSelection(sel)
|
return transform.moveTo(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
const marks = document.getMarksAtRange(selection).remove(mark)
|
const marks = document.getMarksAtRange(selection).remove(mark)
|
||||||
const sel = selection.merge({ marks })
|
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)
|
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
|
return transform
|
||||||
.wrapTextAtRange(selection, prefix, suffix)
|
.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) {
|
export function addMarkByKey(transform, key, offset, length, mark) {
|
||||||
mark = Normalize.mark(mark)
|
mark = Normalize.mark(mark)
|
||||||
|
|
||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
|
return transform.addMarkOperation(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.operate(operation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,21 +36,7 @@ export function insertNodeByKey(transform, key, index, node) {
|
|||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
const newPath = path.slice().push(index)
|
const newPath = path.slice().push(index)
|
||||||
|
return transform.insertNodeOperation(path, index, node)
|
||||||
const inverse = {
|
|
||||||
type: 'remove_node',
|
|
||||||
path: newPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
const operation = {
|
|
||||||
type: 'insert_node',
|
|
||||||
path,
|
|
||||||
index,
|
|
||||||
node,
|
|
||||||
inverse,
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform.operate(operation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,23 +54,7 @@ export function insertTextByKey(transform, key, offset, text, marks) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
|
return transform.insertTextOperation(path, offset, text, marks)
|
||||||
const inverse = {
|
|
||||||
type: 'remove_text',
|
|
||||||
path,
|
|
||||||
offset,
|
|
||||||
length: text.length,
|
|
||||||
}
|
|
||||||
|
|
||||||
const operation = {
|
|
||||||
type: 'insert_text',
|
|
||||||
path,
|
|
||||||
offset,
|
|
||||||
text,
|
|
||||||
marks,
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform.operate(operation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,30 +70,9 @@ export function insertTextByKey(transform, key, offset, text, marks) {
|
|||||||
export function moveNodeByKey(transform, key, newKey, newIndex) {
|
export function moveNodeByKey(transform, key, newKey, newIndex) {
|
||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const node = document.assertDescendant(key)
|
|
||||||
const path = document.getPath(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 newPath = document.getPath(newKey)
|
||||||
const nodePath = newPath.slice().concat([newIndex])
|
return transform.moveNodeOperation(path, newPath, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,29 +88,10 @@ export function moveNodeByKey(transform, key, newKey, newIndex) {
|
|||||||
|
|
||||||
export function removeMarkByKey(transform, key, offset, length, mark) {
|
export function removeMarkByKey(transform, key, offset, length, mark) {
|
||||||
mark = Normalize.mark(mark)
|
mark = Normalize.mark(mark)
|
||||||
|
|
||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
|
return transform.removeMarkOperation(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.operate(operation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,25 +105,8 @@ export function removeMarkByKey(transform, key, offset, length, mark) {
|
|||||||
export function removeNodeByKey(transform, key) {
|
export function removeNodeByKey(transform, key) {
|
||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const node = document.assertDescendant(key)
|
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
const parentPath = path.slice(0, -1)
|
return transform.removeNodeOperation(path)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,19 +123,7 @@ export function removeTextByKey(transform, key, offset, length) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
|
return transform.removeTextOperation(path, offset, length)
|
||||||
// TODO!
|
|
||||||
const inverse = {}
|
|
||||||
|
|
||||||
const operation = {
|
|
||||||
type: 'remove_text',
|
|
||||||
path,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
inverse,
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform.operate(operation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,36 +140,10 @@ export function removeTextByKey(transform, key, offset, length) {
|
|||||||
export function setMarkByKey(transform, key, offset, length, mark, properties) {
|
export function setMarkByKey(transform, key, offset, length, mark, properties) {
|
||||||
mark = Normalize.mark(mark)
|
mark = Normalize.mark(mark)
|
||||||
properties = Normalize.markProperties(properties)
|
properties = Normalize.markProperties(properties)
|
||||||
|
|
||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
const prevProps = {}
|
return transform.setMarkOperation(path, offset, length, mark, properties)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,31 +157,11 @@ export function setMarkByKey(transform, key, offset, length, mark, properties) {
|
|||||||
|
|
||||||
export function setNodeByKey(transform, key, properties) {
|
export function setNodeByKey(transform, key, properties) {
|
||||||
properties = Normalize.nodeProperties(properties)
|
properties = Normalize.nodeProperties(properties)
|
||||||
|
|
||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const node = document.assertDescendant(key)
|
const node = document.assertDescendant(key)
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
const prevProps = {}
|
return transform.setNodeOperation(path, properties)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -341,17 +177,5 @@ export function splitNodeByKey(transform, key, offset) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const path = document.getPath(key)
|
const path = document.getPath(key)
|
||||||
|
return transform.splitNodeOperation(path, offset)
|
||||||
// TODO!
|
|
||||||
const inverse = {}
|
|
||||||
|
|
||||||
const operation = {
|
|
||||||
type: 'split_node',
|
|
||||||
path,
|
|
||||||
offset,
|
|
||||||
inverse,
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform.operate(operation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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.
|
* At range.
|
||||||
*/
|
*/
|
||||||
@@ -98,7 +124,6 @@ import {
|
|||||||
moveTo,
|
moveTo,
|
||||||
moveToOffsets,
|
moveToOffsets,
|
||||||
moveToRangeOf,
|
moveToRangeOf,
|
||||||
setSelection,
|
|
||||||
} from './on-selection'
|
} from './on-selection'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +143,28 @@ import {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply operation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
applyOperation,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
addMarkOperation,
|
||||||
|
insertNodeOperation,
|
||||||
|
insertTextOperation,
|
||||||
|
moveNodeOperation,
|
||||||
|
removeMarkOperation,
|
||||||
|
removeNodeOperation,
|
||||||
|
removeTextOperation,
|
||||||
|
setMarkOperation,
|
||||||
|
setNodeOperation,
|
||||||
|
setSelectionOperation,
|
||||||
|
splitNodeOperation,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* At range.
|
* At range.
|
||||||
*/
|
*/
|
||||||
@@ -210,7 +257,6 @@ export default {
|
|||||||
moveTo,
|
moveTo,
|
||||||
moveToOffsets,
|
moveToOffsets,
|
||||||
moveToRangeOf,
|
moveToRangeOf,
|
||||||
setSelection,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize.
|
* Normalize.
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
|
|
||||||
import Normalize from '../utils/normalize'
|
|
||||||
import Selection from '../models/selection'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blur the selection.
|
* Blur the selection.
|
||||||
*
|
*
|
||||||
@@ -13,7 +10,7 @@ export function blur(transform) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.blur()
|
const sel = selection.blur()
|
||||||
return transform.setSelection(sel)
|
return transform.setSelectionOperation(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +24,7 @@ export function collapseToAnchor(transform) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.collapseToAnchor()
|
const sel = selection.collapseToAnchor()
|
||||||
return transform.setSelection(sel)
|
return transform.setSelectionOperation(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +38,7 @@ export function collapseToEnd(transform) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.collapseToEnd()
|
const sel = selection.collapseToEnd()
|
||||||
return transform.setSelection(sel)
|
return transform.setSelectionOperation(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +52,7 @@ export function collapseToFocus(transform) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.collapseToFocus()
|
const sel = selection.collapseToFocus()
|
||||||
return transform.setSelection(sel)
|
return transform.setSelectionOperation(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +66,7 @@ export function collapseToStart(transform) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.collapseToStart()
|
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 { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.collapseToEndOf(node)
|
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
|
if (!next) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToEndOf(next)
|
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
|
if (!next) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToEndOf(next)
|
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
|
if (!previous) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToEndOf(previous)
|
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
|
if (!previous) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToEndOf(previous)
|
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 { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.collapseToStartOf(node)
|
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
|
if (!next) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToStartOf(next)
|
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
|
if (!next) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToStartOf(next)
|
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
|
if (!previous) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToStartOf(previous)
|
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
|
if (!previous) return transform
|
||||||
|
|
||||||
const sel = selection.collapseToStartOf(previous)
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.extendBackward(n).normalize(document)
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.extendForward(n).normalize(document)
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.extendToEndOf(node).normalize(document)
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.extendToStartOf(node).normalize(document)
|
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 { state } = transform
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const sel = selection.focus()
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.moveBackward(n).normalize(document)
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.moveForward(n).normalize(document)
|
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) {
|
export function moveTo(transform, properties) {
|
||||||
properties = Normalize.selection(properties)
|
return transform.setSelectionOperation(properties)
|
||||||
const { state } = transform
|
|
||||||
const { document, selection } = state
|
|
||||||
const sel = selection.merge(properties).normalize(document)
|
|
||||||
return transform.setSelection(sel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -387,7 +380,7 @@ export function moveToOffsets(transform, anchor, fokus) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.moveToOffsets(anchor, fokus)
|
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 { state } = transform
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const sel = selection.moveToRangeOf(start, end).normalize(document)
|
const sel = selection.moveToRangeOf(start, end).normalize(document)
|
||||||
return transform.setSelection(sel)
|
return transform.setSelectionOperation(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)
|
|
||||||
}
|
}
|
||||||
|
@@ -1,300 +1,375 @@
|
|||||||
|
|
||||||
import Debug from 'debug'
|
import Normalize from '../utils/normalize'
|
||||||
import uid from '../utils/uid'
|
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`.
|
* Add mark to text at `offset` and `length` in node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} offset
|
||||||
|
* @param {Number} length
|
||||||
|
* @param {Mixed} mark
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function addMark(state, operation) {
|
export function addMarkOperation(transform, path, offset, length, mark) {
|
||||||
debug('add_mark', operation)
|
const inverse = [{
|
||||||
const { path, offset, length, mark } = operation
|
type: 'remove_mark',
|
||||||
let { document } = state
|
path,
|
||||||
let node = document.assertPath(path)
|
offset,
|
||||||
node = node.addMark(offset, length, mark)
|
length,
|
||||||
document = document.updateDescendant(node)
|
mark,
|
||||||
state = state.merge({ document })
|
}]
|
||||||
return state
|
|
||||||
|
const operation = {
|
||||||
|
type: 'add_mark',
|
||||||
|
path,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
mark,
|
||||||
|
inverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform.applyOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a `node` at `index` in a node by `path`.
|
* Insert a `node` at `index` in a node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} index
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function insertNode(state, operation) {
|
export function insertNodeOperation(transform, path, index, node) {
|
||||||
debug('insert_node', operation)
|
const inversePath = path.slice().concat([index])
|
||||||
const { path, index, node } = operation
|
const inverse = [{
|
||||||
let { document } = state
|
type: 'remove_node',
|
||||||
let parent = document.assertPath(path)
|
path: inversePath,
|
||||||
const isParent = document == parent
|
}]
|
||||||
const nodes = parent.nodes.splice(index, 0, node)
|
|
||||||
parent = parent.merge({ nodes })
|
const operation = {
|
||||||
document = isParent ? parent : document.updateDescendant(parent)
|
type: 'insert_node',
|
||||||
state = state.merge({ document })
|
path,
|
||||||
return state
|
index,
|
||||||
|
node,
|
||||||
|
inverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform.applyOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert `text` at `offset` in node by `path`.
|
* Insert `text` at `offset` in node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} offset
|
||||||
|
* @param {String} text
|
||||||
|
* @param {Set} marks (optional)
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function insertText(state, operation) {
|
export function insertTextOperation(transform, path, offset, text, marks) {
|
||||||
debug('insert_text', operation)
|
const inverseLength = text.length
|
||||||
const { path, offset, text, marks } = operation
|
const inverse = [{
|
||||||
let { document } = state
|
type: 'remove_text',
|
||||||
let node = document.assertPath(path)
|
path,
|
||||||
node = node.insertText(offset, text, marks)
|
offset,
|
||||||
document = document.updateDescendant(node)
|
length: inverseLength,
|
||||||
state = state.merge({ document })
|
}]
|
||||||
return state
|
|
||||||
|
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 {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Array} newPath
|
||||||
|
* @param {Number} newIndex
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function moveNode(state, operation) {
|
export function moveNodeOperation(transform, path, newPath, newIndex) {
|
||||||
debug('move_node', operation)
|
const { state } = transform
|
||||||
const { path, newPath, newIndex } = operation
|
const { document } = state
|
||||||
let { document } = state
|
const parentPath = path.slice(0, -1)
|
||||||
const node = document.assertPath(path)
|
const parentIndex = path[path.length - 1]
|
||||||
|
const inversePath = newPath.slice().concat([newIndex])
|
||||||
|
|
||||||
let parent = document.getParent(node)
|
const inverse = [{
|
||||||
const isParent = document == parent
|
type: 'move_node',
|
||||||
const index = parent.nodes.indexOf(node)
|
path: inversePath,
|
||||||
parent = parent.removeNode(index)
|
newPath: parentPath,
|
||||||
document = isParent ? parent : document.updateDescendant(parent)
|
newIndex: parentIndex,
|
||||||
|
}]
|
||||||
|
|
||||||
let target = document.assertPath(newPath)
|
const operation = {
|
||||||
const isTarget = document == target
|
type: 'move_node',
|
||||||
target = target.insertNode(newIndex, node)
|
path,
|
||||||
document = isTarget ? target : document.updateDescendant(target)
|
newPath,
|
||||||
|
newIndex,
|
||||||
|
inverse,
|
||||||
|
}
|
||||||
|
|
||||||
state = state.merge({ document })
|
return transform.applyOperation(operation)
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove mark from text at `offset` and `length` in node by `path`.
|
* Remove mark from text at `offset` and `length` in node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} offset
|
||||||
|
* @param {Number} length
|
||||||
|
* @param {Mark} mark
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function removeMark(state, operation) {
|
export function removeMarkOperation(transform, path, offset, length, mark) {
|
||||||
debug('remove_mark', operation)
|
const inverse = [{
|
||||||
const { path, offset, length, mark } = operation
|
type: 'add_mark',
|
||||||
let { document } = state
|
path,
|
||||||
let node = document.assertPath(path)
|
offset,
|
||||||
node = node.removeMark(offset, length, mark)
|
length,
|
||||||
document = document.updateDescendant(node)
|
mark,
|
||||||
state = state.merge({ document })
|
}]
|
||||||
return state
|
|
||||||
|
const operation = {
|
||||||
|
type: 'remove_mark',
|
||||||
|
path,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
mark,
|
||||||
|
inverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform.applyOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a node by `path`.
|
* Remove a node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function removeNode(state, operation) {
|
export function removeNodeOperation(transform, path) {
|
||||||
debug('remove_node', operation)
|
const { state } = transform
|
||||||
const { path } = operation
|
const { document } = state
|
||||||
let { document } = state
|
|
||||||
const node = document.assertPath(path)
|
const node = document.assertPath(path)
|
||||||
let parent = document.getParent(node)
|
const inversePath = path.slice(0, -1)
|
||||||
const index = parent.nodes.indexOf(node)
|
const inverseIndex = path.slice(-1)
|
||||||
const isParent = document == parent
|
|
||||||
parent = parent.removeNode(index)
|
const inverse = [{
|
||||||
document = isParent ? parent : document.updateDescendant(parent)
|
type: 'insert_node',
|
||||||
document = document.normalize()
|
path: inversePath,
|
||||||
state = state.merge({ document })
|
index: inverseIndex,
|
||||||
return state
|
node,
|
||||||
|
}]
|
||||||
|
|
||||||
|
const operation = {
|
||||||
|
type: 'remove_node',
|
||||||
|
path,
|
||||||
|
inverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform.applyOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove text at `offset` and `length` in node by `path`.
|
* Remove text at `offset` and `length` in node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} offset
|
||||||
|
* @param {Number} length
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function removeText(state, operation) {
|
export function removeTextOperation(transform, path, offset, length) {
|
||||||
debug('remove_text', operation)
|
const { state } = transform
|
||||||
const { path, offset, length } = operation
|
const { document } = state
|
||||||
let { document } = state
|
const node = document.assertPath(path)
|
||||||
let node = document.assertPath(path)
|
const ranges = node.getRanges()
|
||||||
node = node.removeText(offset, length)
|
const inverse = []
|
||||||
document = document.updateDescendant(node)
|
|
||||||
document = document.normalize()
|
ranges.reduce((start, range) => {
|
||||||
state = state.merge({ document })
|
const { text, marks } = range
|
||||||
return state
|
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`.
|
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} offset
|
||||||
|
* @param {Number} length
|
||||||
|
* @param {Mark} mark
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function setMark(state, operation) {
|
export function setMarkOperation(transform, path, offset, length, mark, properties) {
|
||||||
debug('set_mark', operation)
|
const inverseProps = {}
|
||||||
const { path, offset, length, mark, properties } = operation
|
|
||||||
let { document } = state
|
for (const k in properties) {
|
||||||
let node = document.assertPath(path)
|
inverseProps[k] = mark[k]
|
||||||
node = node.updateMark(offset, length, mark, properties)
|
}
|
||||||
document = document.updateDescendant(node)
|
|
||||||
state = state.merge({ document })
|
const inverse = [{
|
||||||
return state
|
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`.
|
* Set `properties` on a node by `path`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Object || String} properties
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function setNode(state, operation) {
|
export function setNodeOperation(transform, path, properties) {
|
||||||
debug('set_node', operation)
|
const { state } = transform
|
||||||
const { path, properties } = operation
|
const { document } = state
|
||||||
let { document } = state
|
const node = document.assertPath(path)
|
||||||
let node = document.assertPath(path)
|
const inverseProps = {}
|
||||||
node = node.merge(properties)
|
|
||||||
document = document.updateDescendant(node)
|
for (const k in properties) {
|
||||||
document = document.normalize()
|
inverseProps[k] = node[k]
|
||||||
state = state.merge({ document })
|
}
|
||||||
return state
|
|
||||||
|
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 {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Mixed} selection
|
||||||
* @return {State}
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function setSelection(state, operation) {
|
export function setSelectionOperation(transform, properties) {
|
||||||
debug('set_selection', operation)
|
properties = Normalize.selectionProperties(properties)
|
||||||
let { properties } = operation
|
|
||||||
let { selection } = state
|
const { state } = transform
|
||||||
selection = selection.merge(properties)
|
const { selection } = state
|
||||||
state = state.merge({ selection })
|
const prevProps = {}
|
||||||
return state
|
|
||||||
|
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`.
|
* Split a node by `path` at `offset`.
|
||||||
*
|
*
|
||||||
* @param {State} state
|
* @param {Transform} transform
|
||||||
* @param {Object} operation
|
* @param {Array} path
|
||||||
* @return {State}
|
* @param {Number} offset
|
||||||
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function splitNode(state, operation) {
|
export function splitNodeOperation(transform, path, offset) {
|
||||||
debug('split_node', operation)
|
const inverse = []
|
||||||
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
|
const operation = {
|
||||||
let one
|
type: 'split_node',
|
||||||
let two
|
path,
|
||||||
|
offset,
|
||||||
if (node.kind != 'text') {
|
inverse,
|
||||||
child = node.getTextAtOffset(offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (child && child != parent) {
|
return transform.applyOperation(operation)
|
||||||
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,
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user