mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-07-31 04:20:26 +02:00
Change how slate-history handles selection undo (#4717)
* Change how slate-history handles selection undo * fix test * fix lint * cleanup and simplify * Fix redo by applying undo beforeSelection before applying the redo * remove unused shouldClear function * fix lint * add changeset
This commit is contained in:
5
.changeset/cuddly-kiwis-work.md
Normal file
5
.changeset/cuddly-kiwis-work.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate-history': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Changes how selections are stored in the history resulting in more consistent results
|
@@ -1,5 +1,10 @@
|
|||||||
import { isPlainObject } from 'is-plain-object'
|
import { isPlainObject } from 'is-plain-object'
|
||||||
import { Operation } from 'slate'
|
import { Operation, Range } from 'slate'
|
||||||
|
|
||||||
|
interface Batch {
|
||||||
|
operations: Operation[]
|
||||||
|
selectionBefore: Range | null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `History` objects hold all of the operations that are applied to a value, so
|
* `History` objects hold all of the operations that are applied to a value, so
|
||||||
@@ -7,8 +12,8 @@ import { Operation } from 'slate'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface History {
|
export interface History {
|
||||||
redos: Operation[][]
|
redos: Batch[]
|
||||||
undos: Operation[][]
|
undos: Batch[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-redeclare
|
// eslint-disable-next-line no-redeclare
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Editor, Operation, Path } from 'slate'
|
import { Editor, Operation, Path, Range, Transforms } from 'slate'
|
||||||
|
|
||||||
import { HistoryEditor } from './history-editor'
|
import { HistoryEditor } from './history-editor'
|
||||||
|
|
||||||
@@ -24,9 +24,13 @@ export const withHistory = <T extends Editor>(editor: T) => {
|
|||||||
if (redos.length > 0) {
|
if (redos.length > 0) {
|
||||||
const batch = redos[redos.length - 1]
|
const batch = redos[redos.length - 1]
|
||||||
|
|
||||||
|
if (batch.selectionBefore) {
|
||||||
|
Transforms.setSelection(e, batch.selectionBefore)
|
||||||
|
}
|
||||||
|
|
||||||
HistoryEditor.withoutSaving(e, () => {
|
HistoryEditor.withoutSaving(e, () => {
|
||||||
Editor.withoutNormalizing(e, () => {
|
Editor.withoutNormalizing(e, () => {
|
||||||
for (const op of batch) {
|
for (const op of batch.operations) {
|
||||||
e.apply(op)
|
e.apply(op)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -46,11 +50,14 @@ export const withHistory = <T extends Editor>(editor: T) => {
|
|||||||
|
|
||||||
HistoryEditor.withoutSaving(e, () => {
|
HistoryEditor.withoutSaving(e, () => {
|
||||||
Editor.withoutNormalizing(e, () => {
|
Editor.withoutNormalizing(e, () => {
|
||||||
const inverseOps = batch.map(Operation.inverse).reverse()
|
const inverseOps = batch.operations.map(Operation.inverse).reverse()
|
||||||
|
|
||||||
for (const op of inverseOps) {
|
for (const op of inverseOps) {
|
||||||
e.apply(op)
|
e.apply(op)
|
||||||
}
|
}
|
||||||
|
if (batch.selectionBefore) {
|
||||||
|
Transforms.setSelection(e, batch.selectionBefore)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,8 +70,8 @@ export const withHistory = <T extends Editor>(editor: T) => {
|
|||||||
const { operations, history } = e
|
const { operations, history } = e
|
||||||
const { undos } = history
|
const { undos } = history
|
||||||
const lastBatch = undos[undos.length - 1]
|
const lastBatch = undos[undos.length - 1]
|
||||||
const lastOp = lastBatch && lastBatch[lastBatch.length - 1]
|
const lastOp =
|
||||||
const overwrite = shouldOverwrite(op, lastOp)
|
lastBatch && lastBatch.operations[lastBatch.operations.length - 1]
|
||||||
let save = HistoryEditor.isSaving(e)
|
let save = HistoryEditor.isSaving(e)
|
||||||
let merge = HistoryEditor.isMerging(e)
|
let merge = HistoryEditor.isMerging(e)
|
||||||
|
|
||||||
@@ -79,18 +86,17 @@ export const withHistory = <T extends Editor>(editor: T) => {
|
|||||||
} else if (operations.length !== 0) {
|
} else if (operations.length !== 0) {
|
||||||
merge = true
|
merge = true
|
||||||
} else {
|
} else {
|
||||||
merge = shouldMerge(op, lastOp) || overwrite
|
merge = shouldMerge(op, lastOp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastBatch && merge) {
|
if (lastBatch && merge) {
|
||||||
if (overwrite) {
|
lastBatch.operations.push(op)
|
||||||
lastBatch.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
lastBatch.push(op)
|
|
||||||
} else {
|
} else {
|
||||||
const batch = [op]
|
const batch = {
|
||||||
|
operations: [op],
|
||||||
|
selectionBefore: e.selection,
|
||||||
|
}
|
||||||
undos.push(batch)
|
undos.push(batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +104,7 @@ export const withHistory = <T extends Editor>(editor: T) => {
|
|||||||
undos.shift()
|
undos.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldClear(op)) {
|
history.redos = []
|
||||||
history.redos = []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(op)
|
apply(op)
|
||||||
@@ -114,10 +118,6 @@ export const withHistory = <T extends Editor>(editor: T) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
||||||
if (op.type === 'set_selection') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prev &&
|
prev &&
|
||||||
op.type === 'insert_text' &&
|
op.type === 'insert_text' &&
|
||||||
@@ -146,36 +146,6 @@ const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const shouldSave = (op: Operation, prev: Operation | undefined): boolean => {
|
const shouldSave = (op: Operation, prev: Operation | undefined): boolean => {
|
||||||
if (
|
|
||||||
op.type === 'set_selection' &&
|
|
||||||
(op.properties == null || op.newProperties == null)
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an operation should overwrite the previous one.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const shouldOverwrite = (
|
|
||||||
op: Operation,
|
|
||||||
prev: Operation | undefined
|
|
||||||
): boolean => {
|
|
||||||
if (prev && op.type === 'set_selection' && prev.type === 'set_selection') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an operation should clear the redos stack.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const shouldClear = (op: Operation): boolean => {
|
|
||||||
if (op.type === 'set_selection') {
|
if (op.type === 'set_selection') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,6 @@ export const output = {
|
|||||||
],
|
],
|
||||||
selection: {
|
selection: {
|
||||||
anchor: { path: [0, 0], offset: 5 },
|
anchor: { path: [0, 0], offset: 5 },
|
||||||
focus: { path: [0, 0], offset: 5 },
|
focus: { path: [0, 0], offset: 0 },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user