From c2979725397bb42b39f0d8a22a75329cec8a7b15 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Tue, 16 Aug 2016 17:36:17 -0700 Subject: [PATCH] refactor transforms to apply operations immediately --- lib/models/state.js | 4 +-- lib/models/transform.js | 68 ++++++++++++++-------------------------- lib/transforms/by-key.js | 46 +++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/lib/models/state.js b/lib/models/state.js index bdb855de9..a7df45256 100644 --- a/lib/models/state.js +++ b/lib/models/state.js @@ -383,9 +383,7 @@ class State extends new Record(DEFAULTS) { transform = rule.normalize(transform, document, value) } - return transform.steps.size - ? transform.apply({ snapshot: false }) - : state + return transform.apply({ snapshot: false }) } /** diff --git a/lib/models/transform.js b/lib/models/transform.js index b1a5fd8d5..589a11c13 100644 --- a/lib/models/transform.js +++ b/lib/models/transform.js @@ -11,14 +11,14 @@ import { List, Record } from 'immutable' const Snapshot = new Record({ document: null, selection: null, - steps: new List() + operations: new List() }) /** - * Step. + * Operation. */ -const Step = new Record({ +const Operation = new Record({ type: null, args: null }) @@ -29,7 +29,7 @@ const Step = new Record({ const DEFAULT_PROPERTIES = { state: null, - steps: new List() + operations: new List() } /** @@ -91,7 +91,7 @@ class Transform extends new Record(DEFAULT_PROPERTIES) { apply(options = {}) { const transform = this - let { state, steps } = transform + let { state, operations } = transform let { cursorMarks, history, selection } = state let { undos, redos } = history @@ -110,9 +110,6 @@ class Transform extends new Record(DEFAULT_PROPERTIES) { 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 (state.cursorMarks && state.cursorMarks == cursorMarks) { state = state.merge({ @@ -130,56 +127,36 @@ class Transform extends new Record(DEFAULT_PROPERTIES) { } /** - * Apply a single `step` to a `state`, differentiating between types. - * - * @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. + * Check whether the current transform operations should create a snapshot. * * @return {Boolean} */ shouldSnapshot() { const transform = this - const { state, steps } = transform + const { state, operations } = transform const { cursorMarks, history, selection } = state const { undos, redos } = history const previous = undos.peek() - // If the only steps applied are selection transforms, don't snapshot. - const onlySelections = steps.every(step => includes(SELECTION_TRANSFORMS, step.type)) + // If the only operations applied are selection transforms, don't snapshot. + const onlySelections = operations.every(operation => includes(SELECTION_TRANSFORMS, operation.type)) if (onlySelections) return false // If there isn't a previous state, snapshot. if (!previous) return true - // If there is a previous state but the steps are different, snapshot. - const types = steps.map(step => step.type) - const prevTypes = previous.steps.map(step => step.type) + // If there is a previous state but the operations are different, snapshot. + const types = operations.map(operation => operation.type) + const prevTypes = previous.operations.map(operation => operation.type) const diff = xor(types.toArray(), prevTypes.toArray()) 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 = ( - steps.every(step => step.type == 'insertText') || - steps.every(step => step.type == 'deleteForward') || - steps.every(step => step.type == 'deleteBackward') + operations.every(operation => operation.type == 'insertText') || + operations.every(operation => operation.type == 'deleteForward') || + operations.every(operation => operation.type == 'deleteBackward') ) if (!allCombinable) return true @@ -195,9 +172,9 @@ class Transform extends new Record(DEFAULT_PROPERTIES) { */ snapshot() { - let { state, steps } = this + let { state, operations } = this 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) => { Transform.prototype[type] = function (...args) { let transform = this - let { steps } = transform - steps = steps.push(new Step({ type, args })) - transform = transform.merge({ steps }) + let { operations, state } = transform + operations = operations.push(new Operation({ type, args })) + state = Transforms[type](state, ...args) + transform = transform.merge({ operations, state }) return transform } }) diff --git a/lib/transforms/by-key.js b/lib/transforms/by-key.js index c593a7a52..c0e1d6bf1 100644 --- a/lib/transforms/by-key.js +++ b/lib/transforms/by-key.js @@ -6,7 +6,7 @@ import Normalize from '../utils/normalize' * * @param {State} state * @param {String} key - * @return {State} state + * @return {State} */ export function removeNodeByKey(state, key) { @@ -23,7 +23,7 @@ export function removeNodeByKey(state, key) { * @param {State} state * @param {String} key * @param {Object or String} properties - * @return {State} state + * @return {State} */ export function setNodeByKey(state, key, properties) { @@ -35,3 +35,45 @@ export function setNodeByKey(state, key, properties) { state = state.merge({ document }) 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 +}