1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-21 14:41:23 +02:00

refactor transforms to apply operations immediately

This commit is contained in:
Ian Storm Taylor
2016-08-16 17:36:17 -07:00
parent 4162bb7a2a
commit c297972539
3 changed files with 68 additions and 50 deletions

View File

@@ -383,9 +383,7 @@ class State extends new Record(DEFAULTS) {
transform = rule.normalize(transform, document, value) transform = rule.normalize(transform, document, value)
} }
return transform.steps.size return transform.apply({ snapshot: false })
? transform.apply({ snapshot: false })
: state
} }
/** /**

View File

@@ -11,14 +11,14 @@ import { List, Record } from 'immutable'
const Snapshot = new Record({ const Snapshot = new Record({
document: null, document: null,
selection: null, selection: null,
steps: new List() operations: new List()
}) })
/** /**
* Step. * Operation.
*/ */
const Step = new Record({ const Operation = new Record({
type: null, type: null,
args: null args: null
}) })
@@ -29,7 +29,7 @@ const Step = new Record({
const DEFAULT_PROPERTIES = { const DEFAULT_PROPERTIES = {
state: null, state: null,
steps: new List() operations: new List()
} }
/** /**
@@ -91,7 +91,7 @@ class Transform extends new Record(DEFAULT_PROPERTIES) {
apply(options = {}) { apply(options = {}) {
const transform = this const transform = this
let { state, steps } = transform let { state, operations } = transform
let { cursorMarks, history, selection } = state let { cursorMarks, history, selection } = state
let { undos, redos } = history let { undos, redos } = history
@@ -110,9 +110,6 @@ class Transform extends new Record(DEFAULT_PROPERTIES) {
state = state.merge({ history }) state = state.merge({ history })
} }
// Apply each of the steps in the transform, arriving at a new state.
state = steps.reduce((memo, step) => this.applyStep(memo, step), state)
// If there are cursor marks and they haven't changed, remove them. // If there are cursor marks and they haven't changed, remove them.
if (state.cursorMarks && state.cursorMarks == cursorMarks) { if (state.cursorMarks && state.cursorMarks == cursorMarks) {
state = state.merge({ state = state.merge({
@@ -130,56 +127,36 @@ class Transform extends new Record(DEFAULT_PROPERTIES) {
} }
/** /**
* Apply a single `step` to a `state`, differentiating between types. * Check whether the current transform operations should create a snapshot.
*
* @param {State} state
* @param {Step} step
* @return {State} state
*/
applyStep(state, step) {
const { type, args } = step
const transform = Transforms[type]
if (!transform) {
throw new Error(`Unknown transform type: "${type}".`)
}
state = transform(state, ...args)
return state
}
/**
* Check whether the current transform steps should create a snapshot.
* *
* @return {Boolean} * @return {Boolean}
*/ */
shouldSnapshot() { shouldSnapshot() {
const transform = this const transform = this
const { state, steps } = transform const { state, operations } = transform
const { cursorMarks, history, selection } = state const { cursorMarks, history, selection } = state
const { undos, redos } = history const { undos, redos } = history
const previous = undos.peek() const previous = undos.peek()
// If the only steps applied are selection transforms, don't snapshot. // If the only operations applied are selection transforms, don't snapshot.
const onlySelections = steps.every(step => includes(SELECTION_TRANSFORMS, step.type)) const onlySelections = operations.every(operation => includes(SELECTION_TRANSFORMS, operation.type))
if (onlySelections) return false if (onlySelections) return false
// 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 there is a previous state but the steps are different, snapshot. // If there is a previous state but the operations are different, snapshot.
const types = steps.map(step => step.type) const types = operations.map(operation => operation.type)
const prevTypes = previous.steps.map(step => step.type) const prevTypes = previous.operations.map(operation => operation.type)
const diff = xor(types.toArray(), prevTypes.toArray()) const diff = xor(types.toArray(), prevTypes.toArray())
if (diff.length) return true if (diff.length) return true
// If the current steps aren't one of the "combinable" types, snapshot. // If the current operations aren't one of the "combinable" types, snapshot.
const allCombinable = ( const allCombinable = (
steps.every(step => step.type == 'insertText') || operations.every(operation => operation.type == 'insertText') ||
steps.every(step => step.type == 'deleteForward') || operations.every(operation => operation.type == 'deleteForward') ||
steps.every(step => step.type == 'deleteBackward') operations.every(operation => operation.type == 'deleteBackward')
) )
if (!allCombinable) return true if (!allCombinable) return true
@@ -195,9 +172,9 @@ class Transform extends new Record(DEFAULT_PROPERTIES) {
*/ */
snapshot() { snapshot() {
let { state, steps } = this let { state, operations } = this
let { document, selection } = state let { document, selection } = state
return new Snapshot({ document, selection, steps }) return new Snapshot({ document, selection, operations })
} }
/** /**
@@ -275,15 +252,16 @@ class Transform extends new Record(DEFAULT_PROPERTIES) {
} }
/** /**
* Add a step-creating method for each of the transforms. * Add a operation-creating method for each of the transforms.
*/ */
Object.keys(Transforms).forEach((type) => { Object.keys(Transforms).forEach((type) => {
Transform.prototype[type] = function (...args) { Transform.prototype[type] = function (...args) {
let transform = this let transform = this
let { steps } = transform let { operations, state } = transform
steps = steps.push(new Step({ type, args })) operations = operations.push(new Operation({ type, args }))
transform = transform.merge({ steps }) state = Transforms[type](state, ...args)
transform = transform.merge({ operations, state })
return transform return transform
} }
}) })

View File

@@ -6,7 +6,7 @@ import Normalize from '../utils/normalize'
* *
* @param {State} state * @param {State} state
* @param {String} key * @param {String} key
* @return {State} state * @return {State}
*/ */
export function removeNodeByKey(state, key) { export function removeNodeByKey(state, key) {
@@ -23,7 +23,7 @@ export function removeNodeByKey(state, key) {
* @param {State} state * @param {State} state
* @param {String} key * @param {String} key
* @param {Object or String} properties * @param {Object or String} properties
* @return {State} state * @return {State}
*/ */
export function setNodeByKey(state, key, properties) { export function setNodeByKey(state, key, properties) {
@@ -35,3 +35,45 @@ export function setNodeByKey(state, key, properties) {
state = state.merge({ document }) state = state.merge({ document })
return state return state
} }
/**
* Insert a `node` after a node by `key`.
*
* @param {State} state
* @param {String} key
* @param {Node} node
* @return {State}
*/
export function insertNodeAfterNodeByKey(state, key, node) {
let { document } = state
let descendant = document.assertDescendant(key)
let parent = document.getParent(key)
let index = parent.nodes.indexOf(descendant)
let nodes = parent.nodes.splice(index + 1, 0, node)
parent = parent.merge({ nodes })
document = document.updateDescendant(parent)
state = state.merge({ document })
return state
}
/**
* Insert a `node` before a node by `key`.
*
* @param {State} state
* @param {String} key
* @param {Node} node
* @return {State}
*/
export function insertNodeBeforeNodeByKey(state, key, node) {
let { document } = state
let descendant = document.assertDescendant(key)
let parent = document.getParent(key)
let index = parent.nodes.indexOf(descendant)
let nodes = parent.nodes.splice(index, 0, node)
parent = parent.merge({ nodes })
document = document.updateDescendant(parent)
state = state.merge({ document })
return state
}