1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-07-31 12:30:11 +02:00

got undo working properly for different transforms!

This commit is contained in:
Ian Storm Taylor
2016-06-18 19:18:47 -07:00
parent 3bc080d967
commit 8f64e8941f
4 changed files with 73 additions and 53 deletions

View File

@@ -111,7 +111,7 @@ class Content extends React.Component {
} }
state = transform state = transform
.insert(data) .insertText(data)
.apply() .apply()
this.onChange(state) this.onChange(state)

View File

@@ -224,14 +224,14 @@ class Document extends Record(DEFAULT_PROPERTIES) {
} }
/** /**
* Insert `data` at a `range`. * Insert `text` at a `range`.
* *
* @param {Selection} range * @param {Selection} range
* @param {String or Node or OrderedMap} data * @param {String} text
* @return {Document} document * @return {Document} document
*/ */
insertAtRange(range, data) { insertTextAtRange(range, text) {
let document = this let document = this
// When still expanded, remove the current range first. // When still expanded, remove the current range first.
@@ -240,33 +240,30 @@ class Document extends Record(DEFAULT_PROPERTIES) {
range = range.moveToStart() range = range.moveToStart()
} }
// When the data is a string of characters... let { startKey, startOffset } = range
if (typeof data == 'string') { let startNode = document.getNode(startKey)
let { startNode, startOffset } = document let { characters } = startNode
let { characters } = startNode
// Create a list of the new characters, with the right marks. // Create a list of the new characters, with the right marks.
const marks = characters.has(startOffset) const marks = characters.has(startOffset)
? characters.get(startOffset).marks ? characters.get(startOffset).marks
: null : null
const newCharacters = data.split('').reduce((list, char) => { const newCharacters = text.split('').reduce((list, char) => {
const obj = { text: char } const obj = { text }
if (marks) obj.marks = marks if (marks) obj.marks = marks
return list.push(Character.create(obj)) return list.push(Character.create(obj))
}, Character.createList()) }, Character.createList())
// Splice in the new characters. // Splice in the new characters.
const resumeOffset = startOffset + data.length - 1 const resumeOffset = startOffset + text.length - 1
characters = characters.slice(0, startOffset) characters = characters.slice(0, startOffset)
.concat(newCharacters) .concat(newCharacters)
.concat(characters.slice(resumeOffset, Infinity)) .concat(characters.slice(resumeOffset, Infinity))
// Update the existing text node. // Update the existing text node.
startNode = startNode.merge({ characters }) startNode = startNode.merge({ characters })
document = document.updateNode(startNode) document = document.updateNode(startNode)
return document
}
// Normalize the document. // Normalize the document.
return document.normalize() return document.normalize()

View File

@@ -146,23 +146,17 @@ class State extends Record(DEFAULT_PROPERTIES) {
/** /**
* Insert a `text` string at the current cursor position. * Insert a `text` string at the current cursor position.
* *
* @param {String or Node or OrderedMap} data * @param {String} text
* @return {State} state * @return {State} state
*/ */
insert(data) { insertText(text) {
let state = this let state = this
let { document, selection } = state let { document, selection } = state
let after
// Determine what the selection should be after inserting. // Insert the text and update the selection.
if (typeof data == 'string') { document = document.insertTextAtRange(selection, text)
after = selection.moveForward(data.length) selection = selection.moveForward(text.length)
}
// Insert the data and update the selection.
document = document.insertAtRange(selection, data)
selection = after
state = state.merge({ document, selection }) state = state.merge({ document, selection })
return state return state
} }

View File

@@ -1,4 +1,6 @@
import uniq from 'lodash/uniq'
import xor from 'lodash/xor'
import { List, Record } from 'immutable' import { List, Record } from 'immutable'
/** /**
@@ -7,7 +9,8 @@ import { List, Record } from 'immutable'
const Snapshot = Record({ const Snapshot = Record({
document: null, document: null,
selection: null selection: null,
steps: new List()
}) })
/** /**
@@ -39,8 +42,8 @@ const TRANSFORM_TYPES = [
'deleteBackwardAtRange', 'deleteBackwardAtRange',
'deleteForward', 'deleteForward',
'deleteForwardAtRange', 'deleteForwardAtRange',
'insert', 'insertText',
'insertAtRange', 'insertTextAtRange',
'split', 'split',
'splitAtRange' 'splitAtRange'
] ]
@@ -58,9 +61,9 @@ class Transform extends Record(DEFAULT_PROPERTIES) {
*/ */
snapshot() { snapshot() {
let { state } = this let { state, steps } = this
let { document, selection } = state let { document, selection } = state
return new Snapshot({ document, selection }) return new Snapshot({ document, selection, steps })
} }
/** /**
@@ -70,17 +73,44 @@ class Transform extends Record(DEFAULT_PROPERTIES) {
*/ */
apply() { apply() {
let transform = this const transform = this
let { state, steps } = transform let { state, steps } = transform
let { history } = state let { history } = state
let { undos, redos } = history let { undos, redos } = history
// Save the current state into the history before transforming. // Determine whether we need to create a new snapshot.
let snapshot = transform.snapshot() let shouldSnapshot = false
undos = undos.unshift(snapshot) const previous = undos.peek()
redos = redos.clear()
history = history.merge({ undos, redos }) // If there isn't a previous state, snapshot.
state = state.merge({ history }) if (!previous) shouldSnapshot = true
// If there is a previous state but the steps are different, snapshot.
if (!shouldSnapshot && previous) {
const types = steps.map(step => step.type)
const prevTypes = previous.steps.map(step => step.type)
const diff = xor(types.toArray(), prevTypes.toArray())
if (diff.length) shouldSnapshot = true
}
// If the current steps aren't one of the "combinale" types, snapshot.
if (!shouldSnapshot) {
const allCombinable = (
steps.every(step => step.type == 'insertText') ||
steps.every(step => step.type == 'deleteForward') ||
steps.every(step => step.type == 'deleteBackward')
)
if (!allCombinable) shouldSnapshot = true
}
// If we should, save a snapshot into the history before transforming.
if (shouldSnapshot) {
const snapshot = transform.snapshot()
undos = undos.unshift(snapshot)
redos = redos.clear()
history = history.merge({ undos, redos })
state = state.merge({ history })
}
// Apply each of the steps in the transform, arriving at a new state. // Apply each of the steps in the transform, arriving at a new state.
state = steps.reduce((state, step) => { state = steps.reduce((state, step) => {
@@ -91,7 +121,6 @@ class Transform extends Record(DEFAULT_PROPERTIES) {
return state return state
} }
/** /**
* Undo to the previous state in the history. * Undo to the previous state in the history.
* *
@@ -99,7 +128,7 @@ class Transform extends Record(DEFAULT_PROPERTIES) {
*/ */
undo() { undo() {
let transform = this const transform = this
let { state } = transform let { state } = transform
let { history } = state let { history } = state
let { undos, redos } = history let { undos, redos } = history
@@ -129,7 +158,7 @@ class Transform extends Record(DEFAULT_PROPERTIES) {
*/ */
redo() { redo() {
let transform = this const transform = this
let { state } = transform let { state } = transform
let { history } = state let { history } = state
let { undos, redos } = history let { undos, redos } = history