mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-11 17:53:59 +02:00
Avoid transforming dirty paths if the op won't touch them (#4735)
* Avoid transforming dirty paths if the op won't touch them * Avoid the loop entirely if op doesn't affect path
This commit is contained in:
5
.changeset/two-coins-enjoy.md
Normal file
5
.changeset/two-coins-enjoy.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Optimize path transforms during normalization
|
@@ -13,7 +13,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Transforms,
|
Transforms,
|
||||||
} from './'
|
} from './'
|
||||||
import { DIRTY_PATHS, FLUSHING } from './utils/weak-maps'
|
import { DIRTY_PATHS, DIRTY_PATH_KEYS, FLUSHING } from './utils/weak-maps'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Slate `Editor` object.
|
* Create a new Slate `Editor` object.
|
||||||
@@ -42,33 +42,41 @@ export const createEditor = (): Editor => {
|
|||||||
RangeRef.transform(ref, op)
|
RangeRef.transform(ref, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
const set = new Set()
|
const oldDirtyPaths = DIRTY_PATHS.get(editor) || []
|
||||||
const dirtyPaths: Path[] = []
|
const oldDirtyPathKeys = DIRTY_PATH_KEYS.get(editor) || new Set()
|
||||||
|
let dirtyPaths: Path[]
|
||||||
|
let dirtyPathKeys: Set<string>
|
||||||
|
|
||||||
const add = (path: Path | null) => {
|
const add = (path: Path | null) => {
|
||||||
if (path) {
|
if (path) {
|
||||||
const key = path.join(',')
|
const key = path.join(',')
|
||||||
|
|
||||||
if (!set.has(key)) {
|
if (!dirtyPathKeys.has(key)) {
|
||||||
set.add(key)
|
dirtyPathKeys.add(key)
|
||||||
dirtyPaths.push(path)
|
dirtyPaths.push(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldDirtyPaths = DIRTY_PATHS.get(editor) || []
|
if (Path.operationCanTransformPath(op)) {
|
||||||
const newDirtyPaths = getDirtyPaths(op)
|
dirtyPaths = []
|
||||||
|
dirtyPathKeys = new Set()
|
||||||
for (const path of oldDirtyPaths) {
|
for (const path of oldDirtyPaths) {
|
||||||
const newPath = Path.transform(path, op)
|
const newPath = Path.transform(path, op)
|
||||||
add(newPath)
|
add(newPath)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
dirtyPaths = oldDirtyPaths
|
||||||
|
dirtyPathKeys = oldDirtyPathKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDirtyPaths = getDirtyPaths(op)
|
||||||
for (const path of newDirtyPaths) {
|
for (const path of newDirtyPaths) {
|
||||||
add(path)
|
add(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
DIRTY_PATHS.set(editor, dirtyPaths)
|
DIRTY_PATHS.set(editor, dirtyPaths)
|
||||||
|
DIRTY_PATH_KEYS.set(editor, dirtyPathKeys)
|
||||||
Transforms.transform(editor, op)
|
Transforms.transform(editor, op)
|
||||||
editor.operations.push(op)
|
editor.operations.push(op)
|
||||||
Editor.normalize(editor)
|
Editor.normalize(editor)
|
||||||
|
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '..'
|
} from '..'
|
||||||
import {
|
import {
|
||||||
DIRTY_PATHS,
|
DIRTY_PATHS,
|
||||||
|
DIRTY_PATH_KEYS,
|
||||||
NORMALIZING,
|
NORMALIZING,
|
||||||
PATH_REFS,
|
PATH_REFS,
|
||||||
POINT_REFS,
|
POINT_REFS,
|
||||||
@@ -979,13 +980,26 @@ export const Editor: EditorInterface = {
|
|||||||
return DIRTY_PATHS.get(editor) || []
|
return DIRTY_PATHS.get(editor) || []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDirtyPathKeys = (editor: Editor) => {
|
||||||
|
return DIRTY_PATH_KEYS.get(editor) || new Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
const popDirtyPath = (editor: Editor): Path => {
|
||||||
|
const path = getDirtyPaths(editor).pop()!
|
||||||
|
const key = path.join(',')
|
||||||
|
getDirtyPathKeys(editor).delete(key)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
if (!Editor.isNormalizing(editor)) {
|
if (!Editor.isNormalizing(editor)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
const allPaths = Array.from(Node.nodes(editor), ([, p]) => p)
|
const allPaths = Array.from(Node.nodes(editor), ([, p]) => p)
|
||||||
|
const allPathKeys = new Set(allPaths.map(p => p.join(',')))
|
||||||
DIRTY_PATHS.set(editor, allPaths)
|
DIRTY_PATHS.set(editor, allPaths)
|
||||||
|
DIRTY_PATH_KEYS.set(editor, allPathKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getDirtyPaths(editor).length === 0) {
|
if (getDirtyPaths(editor).length === 0) {
|
||||||
@@ -1026,7 +1040,7 @@ export const Editor: EditorInterface = {
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dirtyPath = getDirtyPaths(editor).pop()!
|
const dirtyPath = popDirtyPath(editor)
|
||||||
|
|
||||||
// If the node doesn't exist in the tree, it does not need to be normalized.
|
// If the node doesn't exist in the tree, it does not need to be normalized.
|
||||||
if (Node.has(editor, dirtyPath)) {
|
if (Node.has(editor, dirtyPath)) {
|
||||||
|
@@ -34,6 +34,7 @@ export interface PathInterface {
|
|||||||
}
|
}
|
||||||
) => Path[]
|
) => Path[]
|
||||||
next: (path: Path) => Path
|
next: (path: Path) => Path
|
||||||
|
operationCanTransformPath: (operation: Operation) => boolean
|
||||||
parent: (path: Path) => Path
|
parent: (path: Path) => Path
|
||||||
previous: (path: Path) => Path
|
previous: (path: Path) => Path
|
||||||
relative: (path: Path, ancestor: Path) => Path
|
relative: (path: Path, ancestor: Path) => Path
|
||||||
@@ -291,6 +292,26 @@ export const Path: PathInterface = {
|
|||||||
return path.slice(0, -1).concat(last + 1)
|
return path.slice(0, -1).concat(last + 1)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this operation can affect paths or not. Used as an
|
||||||
|
* optimization when updating dirty paths during normalization
|
||||||
|
*
|
||||||
|
* NOTE: This *must* be kept in sync with the implementation of 'transform'
|
||||||
|
* below
|
||||||
|
*/
|
||||||
|
operationCanTransformPath(operation: Operation): boolean {
|
||||||
|
switch (operation.type) {
|
||||||
|
case 'insert_node':
|
||||||
|
case 'remove_node':
|
||||||
|
case 'merge_node':
|
||||||
|
case 'split_node':
|
||||||
|
case 'move_node':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a path, return a new path referring to the parent node above it.
|
* Given a path, return a new path referring to the parent node above it.
|
||||||
*/
|
*/
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Editor, Path, PathRef, PointRef, RangeRef } from '..'
|
import { Editor, Path, PathRef, PointRef, RangeRef } from '..'
|
||||||
|
|
||||||
export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap()
|
export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap()
|
||||||
|
export const DIRTY_PATH_KEYS: WeakMap<Editor, Set<string>> = new WeakMap()
|
||||||
export const FLUSHING: WeakMap<Editor, boolean> = new WeakMap()
|
export const FLUSHING: WeakMap<Editor, boolean> = new WeakMap()
|
||||||
export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap()
|
export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap()
|
||||||
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()
|
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()
|
||||||
|
Reference in New Issue
Block a user