@portabletext/editor 2.21.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/_chunks-dts/index.d.ts +49 -209
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js +103 -20
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
- package/lib/_chunks-es/{util.get-text-block-text.js → util.slice-blocks.js} +73 -24
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -0
- package/lib/_chunks-es/util.slice-text-block.js +13 -2
- package/lib/_chunks-es/util.slice-text-block.js.map +1 -1
- package/lib/behaviors/index.d.ts +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +339 -341
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.ts +2 -133
- package/lib/plugins/index.js +2 -796
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.d.ts +2 -24
- package/lib/selectors/index.js +28 -130
- package/lib/selectors/index.js.map +1 -1
- package/lib/utils/index.d.ts +6 -4
- package/lib/utils/index.js +98 -9
- package/lib/utils/index.js.map +1 -1
- package/package.json +1 -3
- package/src/behaviors/behavior.abstract.split.ts +1 -0
- package/src/behaviors/behavior.perform-event.ts +7 -7
- package/src/converters/converter.portable-text.ts +1 -0
- package/src/converters/converter.text-html.ts +1 -0
- package/src/converters/converter.text-plain.ts +1 -0
- package/src/editor/Editable.tsx +1 -0
- package/src/editor/PortableTextEditor.tsx +0 -19
- package/src/editor/create-editor.ts +0 -3
- package/src/editor/editor-machine.ts +0 -10
- package/src/editor/event-to-change.tsx +5 -1
- package/src/editor/plugins/create-with-event-listeners.ts +30 -6
- package/src/editor/plugins/createWithObjectKeys.ts +2 -1
- package/src/editor/plugins/createWithPatches.ts +3 -3
- package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -1
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -1
- package/src/editor/plugins/with-plugins.ts +10 -14
- package/src/editor/relay-machine.ts +0 -4
- package/src/editor/sync-machine.ts +2 -2
- package/src/editor.ts +0 -4
- package/src/history/behavior.operation.history.redo.ts +67 -0
- package/src/history/behavior.operation.history.undo.ts +71 -0
- package/src/history/event.history.undo.test.tsx +672 -0
- package/src/history/history.preserving-keys.test.tsx +112 -0
- package/src/history/remote-patches.ts +20 -0
- package/src/history/slate-plugin.history.ts +146 -0
- package/src/history/slate-plugin.redoing.ts +21 -0
- package/src/history/slate-plugin.undoing.ts +21 -0
- package/src/history/slate-plugin.without-history.ts +23 -0
- package/src/history/transform-operation.ts +245 -0
- package/src/history/undo-redo-collaboration.test.tsx +541 -0
- package/src/history/undo-redo.feature +125 -0
- package/src/history/undo-redo.test.tsx +195 -0
- package/src/history/undo-step.ts +148 -0
- package/src/index.ts +0 -1
- package/src/internal-utils/operation-to-patches.test.ts +23 -25
- package/src/internal-utils/operation-to-patches.ts +31 -22
- package/src/internal-utils/selection-text.test.ts +3 -0
- package/src/internal-utils/selection-text.ts +5 -2
- package/src/internal-utils/values.ts +23 -11
- package/src/operations/behavior.operation.block.set.ts +1 -0
- package/src/operations/behavior.operation.block.unset.ts +2 -0
- package/src/operations/behavior.operation.insert.block.ts +1 -0
- package/src/operations/behavior.operations.ts +2 -4
- package/src/plugins/index.ts +0 -3
- package/src/selectors/index.ts +0 -3
- package/src/test/vitest/step-definitions.tsx +57 -0
- package/src/test/vitest/test-editor.tsx +1 -1
- package/src/utils/parse-blocks.test.ts +296 -16
- package/src/utils/parse-blocks.ts +81 -22
- package/src/utils/util.merge-text-blocks.ts +5 -1
- package/src/utils/util.slice-blocks.ts +24 -10
- package/lib/_chunks-es/selector.get-selection-text.js +0 -92
- package/lib/_chunks-es/selector.get-selection-text.js.map +0 -1
- package/lib/_chunks-es/selector.get-text-before.js +0 -36
- package/lib/_chunks-es/selector.get-text-before.js.map +0 -1
- package/lib/_chunks-es/util.get-text-block-text.js.map +0 -1
- package/lib/_chunks-es/util.is-empty-text-block.js +0 -40
- package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
- package/lib/_chunks-es/util.merge-text-blocks.js +0 -101
- package/lib/_chunks-es/util.merge-text-blocks.js.map +0 -1
- package/src/editor/plugins/createWithMaxBlocks.ts +0 -53
- package/src/editor/plugins/createWithUndoRedo.ts +0 -628
- package/src/editor/with-undo-step.ts +0 -37
- package/src/editor/withUndoRedo.ts +0 -34
- package/src/editor-event-listener.tsx +0 -28
- package/src/plugins/plugin.decorator-shortcut.ts +0 -238
- package/src/plugins/plugin.markdown.test.tsx +0 -42
- package/src/plugins/plugin.markdown.tsx +0 -131
- package/src/plugins/plugin.one-line.tsx +0 -123
- package/src/selectors/selector.get-list-state.test.ts +0 -189
- package/src/selectors/selector.get-list-state.ts +0 -96
- package/src/selectors/selector.get-selected-slice.ts +0 -13
- package/src/selectors/selector.get-trimmed-selection.test.ts +0 -657
- package/src/selectors/selector.get-trimmed-selection.ts +0 -189
|
@@ -1,628 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This plugin will make the editor support undo/redo on the local state only.
|
|
3
|
-
* The undo/redo steps are rebased against incoming patches since the step occurred.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {Patch} from '@portabletext/patches'
|
|
7
|
-
import {
|
|
8
|
-
DIFF_DELETE,
|
|
9
|
-
DIFF_EQUAL,
|
|
10
|
-
DIFF_INSERT,
|
|
11
|
-
parsePatch,
|
|
12
|
-
} from '@sanity/diff-match-patch'
|
|
13
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
14
|
-
import {flatten, isEqual} from 'lodash'
|
|
15
|
-
import {
|
|
16
|
-
Editor,
|
|
17
|
-
Operation,
|
|
18
|
-
Path,
|
|
19
|
-
Transforms,
|
|
20
|
-
type Descendant,
|
|
21
|
-
type SelectionOperation,
|
|
22
|
-
} from 'slate'
|
|
23
|
-
import {debugWithName} from '../../internal-utils/debug'
|
|
24
|
-
import {fromSlateValue} from '../../internal-utils/values'
|
|
25
|
-
import type {BehaviorOperationImplementation} from '../../operations/behavior.operations'
|
|
26
|
-
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
27
|
-
import type {EditorActor} from '../editor-machine'
|
|
28
|
-
import {isNormalizingNode} from '../with-normalizing-node'
|
|
29
|
-
import {getCurrentUndoStepId} from '../with-undo-step'
|
|
30
|
-
import {isChangingRemotely} from '../withChanges'
|
|
31
|
-
import {
|
|
32
|
-
isRedoing,
|
|
33
|
-
isUndoing,
|
|
34
|
-
setIsRedoing,
|
|
35
|
-
setIsUndoing,
|
|
36
|
-
withRedoing,
|
|
37
|
-
withUndoing,
|
|
38
|
-
} from '../withUndoRedo'
|
|
39
|
-
|
|
40
|
-
const debug = debugWithName('plugin:withUndoRedo')
|
|
41
|
-
const debugVerbose = debug.enabled && false
|
|
42
|
-
|
|
43
|
-
const SAVING = new WeakMap<Editor, boolean | undefined>()
|
|
44
|
-
const REMOTE_PATCHES = new WeakMap<
|
|
45
|
-
Editor,
|
|
46
|
-
{
|
|
47
|
-
patch: Patch
|
|
48
|
-
time: Date
|
|
49
|
-
snapshot: PortableTextBlock[] | undefined
|
|
50
|
-
previousSnapshot: PortableTextBlock[] | undefined
|
|
51
|
-
}[]
|
|
52
|
-
>()
|
|
53
|
-
const UNDO_STEP_LIMIT = 1000
|
|
54
|
-
|
|
55
|
-
const isSaving = (editor: Editor): boolean | undefined => {
|
|
56
|
-
const state = SAVING.get(editor)
|
|
57
|
-
return state === undefined ? true : state
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface Options {
|
|
61
|
-
editorActor: EditorActor
|
|
62
|
-
subscriptions: Array<() => () => void>
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const getRemotePatches = (editor: Editor) => {
|
|
66
|
-
if (!REMOTE_PATCHES.get(editor)) {
|
|
67
|
-
REMOTE_PATCHES.set(editor, [])
|
|
68
|
-
}
|
|
69
|
-
return REMOTE_PATCHES.get(editor) || []
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function createWithUndoRedo(
|
|
73
|
-
options: Options,
|
|
74
|
-
): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
|
|
75
|
-
const {editorActor} = options
|
|
76
|
-
|
|
77
|
-
return (editor: PortableTextSlateEditor) => {
|
|
78
|
-
let previousSnapshot: PortableTextBlock[] | undefined = fromSlateValue(
|
|
79
|
-
editor.children,
|
|
80
|
-
editorActor.getSnapshot().context.schema.block.name,
|
|
81
|
-
)
|
|
82
|
-
const remotePatches = getRemotePatches(editor)
|
|
83
|
-
let previousUndoStepId = getCurrentUndoStepId(editor)
|
|
84
|
-
|
|
85
|
-
options.subscriptions.push(() => {
|
|
86
|
-
debug('Subscribing to patches')
|
|
87
|
-
const sub = editorActor.on('patches', ({patches, snapshot}) => {
|
|
88
|
-
let reset = false
|
|
89
|
-
patches.forEach((patch) => {
|
|
90
|
-
if (!reset && patch.origin !== 'local' && remotePatches) {
|
|
91
|
-
if (patch.type === 'unset' && patch.path.length === 0) {
|
|
92
|
-
debug(
|
|
93
|
-
'Someone else cleared the content, resetting undo/redo history',
|
|
94
|
-
)
|
|
95
|
-
editor.history = {undos: [], redos: []}
|
|
96
|
-
remotePatches.splice(0, remotePatches.length)
|
|
97
|
-
SAVING.set(editor, true)
|
|
98
|
-
reset = true
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
remotePatches.push({
|
|
102
|
-
patch,
|
|
103
|
-
time: new Date(),
|
|
104
|
-
snapshot,
|
|
105
|
-
previousSnapshot,
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
previousSnapshot = snapshot
|
|
110
|
-
})
|
|
111
|
-
return () => {
|
|
112
|
-
debug('Unsubscribing to patches')
|
|
113
|
-
sub.unsubscribe()
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
editor.history = {undos: [], redos: []}
|
|
118
|
-
|
|
119
|
-
const {apply} = editor
|
|
120
|
-
|
|
121
|
-
editor.apply = (op: Operation) => {
|
|
122
|
-
if (editorActor.getSnapshot().matches({'edit mode': 'read only'})) {
|
|
123
|
-
apply(op)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* We don't want to run any side effects when the editor is processing
|
|
129
|
-
* remote changes.
|
|
130
|
-
*/
|
|
131
|
-
if (isChangingRemotely(editor)) {
|
|
132
|
-
apply(op)
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* We don't want to run any side effects when the editor is undoing or
|
|
138
|
-
* redoing operations.
|
|
139
|
-
*/
|
|
140
|
-
if (isUndoing(editor) || isRedoing(editor)) {
|
|
141
|
-
apply(op)
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const savingUndoSteps = isSaving(editor)
|
|
146
|
-
const currentUndoStepId = getCurrentUndoStepId(editor)
|
|
147
|
-
|
|
148
|
-
if (!savingUndoSteps) {
|
|
149
|
-
// If we are bypassing saving undo steps, then we can just move along.
|
|
150
|
-
|
|
151
|
-
previousUndoStepId = currentUndoStepId
|
|
152
|
-
|
|
153
|
-
apply(op)
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (op.type !== 'set_selection') {
|
|
159
|
-
// Clear the repo steps if any actual changes are made
|
|
160
|
-
editor.history.redos = []
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const step = editor.history.undos.at(editor.history.undos.length - 1)
|
|
164
|
-
|
|
165
|
-
if (!step) {
|
|
166
|
-
// If the undo stack is empty, then we can just create a new step and
|
|
167
|
-
// move along.
|
|
168
|
-
|
|
169
|
-
editor.history.undos.push({
|
|
170
|
-
operations: [
|
|
171
|
-
...(editor.selection === null
|
|
172
|
-
? []
|
|
173
|
-
: [createSelectOperation(editor)]),
|
|
174
|
-
op,
|
|
175
|
-
],
|
|
176
|
-
timestamp: new Date(),
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
apply(op)
|
|
180
|
-
|
|
181
|
-
previousUndoStepId = currentUndoStepId
|
|
182
|
-
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const selectingWithoutUndoStepId =
|
|
187
|
-
op.type === 'set_selection' &&
|
|
188
|
-
currentUndoStepId === undefined &&
|
|
189
|
-
previousUndoStepId !== undefined
|
|
190
|
-
const selectingWithDifferentUndoStepId =
|
|
191
|
-
op.type === 'set_selection' &&
|
|
192
|
-
currentUndoStepId !== undefined &&
|
|
193
|
-
previousUndoStepId !== undefined &&
|
|
194
|
-
previousUndoStepId !== currentUndoStepId
|
|
195
|
-
|
|
196
|
-
const lastOp = step.operations.at(-1)
|
|
197
|
-
const mergeOpIntoPreviousStep =
|
|
198
|
-
editor.operations.length > 0
|
|
199
|
-
? currentUndoStepId === previousUndoStepId ||
|
|
200
|
-
isNormalizingNode(editor)
|
|
201
|
-
: selectingWithoutUndoStepId ||
|
|
202
|
-
selectingWithDifferentUndoStepId ||
|
|
203
|
-
(currentUndoStepId === undefined &&
|
|
204
|
-
previousUndoStepId === undefined)
|
|
205
|
-
? shouldMerge(op, lastOp) ||
|
|
206
|
-
(lastOp?.type === 'set_selection' && op.type === 'set_selection')
|
|
207
|
-
: currentUndoStepId === previousUndoStepId ||
|
|
208
|
-
isNormalizingNode(editor)
|
|
209
|
-
|
|
210
|
-
if (mergeOpIntoPreviousStep) {
|
|
211
|
-
step.operations.push(op)
|
|
212
|
-
} else {
|
|
213
|
-
editor.history.undos.push({
|
|
214
|
-
operations: [
|
|
215
|
-
...(editor.selection === null
|
|
216
|
-
? []
|
|
217
|
-
: [createSelectOperation(editor)]),
|
|
218
|
-
op,
|
|
219
|
-
],
|
|
220
|
-
timestamp: new Date(),
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Make sure we don't exceed the maximum number of undo steps we want
|
|
225
|
-
// to store.
|
|
226
|
-
while (editor.history.undos.length > UNDO_STEP_LIMIT) {
|
|
227
|
-
editor.history.undos.shift()
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
previousUndoStepId = currentUndoStepId
|
|
231
|
-
|
|
232
|
-
apply(op)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return editor
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export const historyUndoOperationImplementation: BehaviorOperationImplementation<
|
|
240
|
-
'history.undo'
|
|
241
|
-
> = ({operation}) => {
|
|
242
|
-
const editor = operation.editor
|
|
243
|
-
const {undos} = editor.history
|
|
244
|
-
const remotePatches = getRemotePatches(editor)
|
|
245
|
-
|
|
246
|
-
if (undos.length > 0) {
|
|
247
|
-
const step = undos[undos.length - 1]
|
|
248
|
-
debug('Undoing', step)
|
|
249
|
-
if (step.operations.length > 0) {
|
|
250
|
-
const otherPatches = remotePatches.filter(
|
|
251
|
-
(item) => item.time >= step.timestamp,
|
|
252
|
-
)
|
|
253
|
-
let transformedOperations = step.operations
|
|
254
|
-
otherPatches.forEach((item) => {
|
|
255
|
-
transformedOperations = flatten(
|
|
256
|
-
transformedOperations.map((op) =>
|
|
257
|
-
transformOperation(
|
|
258
|
-
editor,
|
|
259
|
-
item.patch,
|
|
260
|
-
op,
|
|
261
|
-
item.snapshot,
|
|
262
|
-
item.previousSnapshot,
|
|
263
|
-
),
|
|
264
|
-
),
|
|
265
|
-
)
|
|
266
|
-
})
|
|
267
|
-
const reversedOperations = transformedOperations
|
|
268
|
-
.map(Operation.inverse)
|
|
269
|
-
.reverse()
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
Editor.withoutNormalizing(editor, () => {
|
|
273
|
-
withUndoing(editor, () => {
|
|
274
|
-
withoutSaving(editor, () => {
|
|
275
|
-
reversedOperations.forEach((op) => {
|
|
276
|
-
editor.apply(op)
|
|
277
|
-
})
|
|
278
|
-
})
|
|
279
|
-
})
|
|
280
|
-
})
|
|
281
|
-
} catch (err) {
|
|
282
|
-
debug('Could not perform undo step', err)
|
|
283
|
-
remotePatches.splice(0, remotePatches.length)
|
|
284
|
-
Transforms.deselect(editor)
|
|
285
|
-
editor.history = {undos: [], redos: []}
|
|
286
|
-
SAVING.set(editor, true)
|
|
287
|
-
setIsUndoing(editor, false)
|
|
288
|
-
editor.onChange()
|
|
289
|
-
return
|
|
290
|
-
}
|
|
291
|
-
editor.history.redos.push(step)
|
|
292
|
-
editor.history.undos.pop()
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export const historyRedoOperationImplementation: BehaviorOperationImplementation<
|
|
298
|
-
'history.redo'
|
|
299
|
-
> = ({operation}) => {
|
|
300
|
-
const editor = operation.editor
|
|
301
|
-
const {redos} = editor.history
|
|
302
|
-
const remotePatches = getRemotePatches(editor)
|
|
303
|
-
|
|
304
|
-
if (redos.length > 0) {
|
|
305
|
-
const step = redos[redos.length - 1]
|
|
306
|
-
debug('Redoing', step)
|
|
307
|
-
if (step.operations.length > 0) {
|
|
308
|
-
const otherPatches = remotePatches.filter(
|
|
309
|
-
(item) => item.time >= step.timestamp,
|
|
310
|
-
)
|
|
311
|
-
let transformedOperations = step.operations
|
|
312
|
-
otherPatches.forEach((item) => {
|
|
313
|
-
transformedOperations = flatten(
|
|
314
|
-
transformedOperations.map((op) =>
|
|
315
|
-
transformOperation(
|
|
316
|
-
editor,
|
|
317
|
-
item.patch,
|
|
318
|
-
op,
|
|
319
|
-
item.snapshot,
|
|
320
|
-
item.previousSnapshot,
|
|
321
|
-
),
|
|
322
|
-
),
|
|
323
|
-
)
|
|
324
|
-
})
|
|
325
|
-
try {
|
|
326
|
-
Editor.withoutNormalizing(editor, () => {
|
|
327
|
-
withRedoing(editor, () => {
|
|
328
|
-
withoutSaving(editor, () => {
|
|
329
|
-
transformedOperations.forEach((op) => {
|
|
330
|
-
editor.apply(op)
|
|
331
|
-
})
|
|
332
|
-
})
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
} catch (err) {
|
|
336
|
-
debug('Could not perform redo step', err)
|
|
337
|
-
remotePatches.splice(0, remotePatches.length)
|
|
338
|
-
Transforms.deselect(editor)
|
|
339
|
-
editor.history = {undos: [], redos: []}
|
|
340
|
-
SAVING.set(editor, true)
|
|
341
|
-
setIsRedoing(editor, false)
|
|
342
|
-
editor.onChange()
|
|
343
|
-
return
|
|
344
|
-
}
|
|
345
|
-
editor.history.undos.push(step)
|
|
346
|
-
editor.history.redos.pop()
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* This will adjust the operation paths and offsets according to the
|
|
353
|
-
* remote patches by other editors since the step operations was performed.
|
|
354
|
-
*/
|
|
355
|
-
function transformOperation(
|
|
356
|
-
editor: PortableTextSlateEditor,
|
|
357
|
-
patch: Patch,
|
|
358
|
-
operation: Operation,
|
|
359
|
-
snapshot: PortableTextBlock[] | undefined,
|
|
360
|
-
previousSnapshot: PortableTextBlock[] | undefined,
|
|
361
|
-
): Operation[] {
|
|
362
|
-
if (debugVerbose) {
|
|
363
|
-
debug(
|
|
364
|
-
`Adjusting '${operation.type}' operation paths for '${patch.type}' patch`,
|
|
365
|
-
)
|
|
366
|
-
debug(`Operation ${JSON.stringify(operation)}`)
|
|
367
|
-
debug(`Patch ${JSON.stringify(patch)}`)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const transformedOperation = {...operation}
|
|
371
|
-
|
|
372
|
-
if (patch.type === 'insert' && patch.path.length === 1) {
|
|
373
|
-
const insertBlockIndex = (snapshot || []).findIndex((blk) =>
|
|
374
|
-
isEqual({_key: blk._key}, patch.path[0]),
|
|
375
|
-
)
|
|
376
|
-
debug(
|
|
377
|
-
`Adjusting block path (+${patch.items.length}) for '${transformedOperation.type}' operation and patch '${patch.type}'`,
|
|
378
|
-
)
|
|
379
|
-
return [
|
|
380
|
-
adjustBlockPath(
|
|
381
|
-
transformedOperation,
|
|
382
|
-
patch.items.length,
|
|
383
|
-
insertBlockIndex,
|
|
384
|
-
),
|
|
385
|
-
]
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (patch.type === 'unset' && patch.path.length === 1) {
|
|
389
|
-
const unsetBlockIndex = (previousSnapshot || []).findIndex((blk) =>
|
|
390
|
-
isEqual({_key: blk._key}, patch.path[0]),
|
|
391
|
-
)
|
|
392
|
-
// If this operation is targeting the same block that got removed, return empty
|
|
393
|
-
if (
|
|
394
|
-
'path' in transformedOperation &&
|
|
395
|
-
Array.isArray(transformedOperation.path) &&
|
|
396
|
-
transformedOperation.path[0] === unsetBlockIndex
|
|
397
|
-
) {
|
|
398
|
-
debug('Skipping transformation that targeted removed block')
|
|
399
|
-
return []
|
|
400
|
-
}
|
|
401
|
-
if (debugVerbose) {
|
|
402
|
-
debug(`Selection ${JSON.stringify(editor.selection)}`)
|
|
403
|
-
debug(
|
|
404
|
-
`Adjusting block path (-1) for '${transformedOperation.type}' operation and patch '${patch.type}'`,
|
|
405
|
-
)
|
|
406
|
-
}
|
|
407
|
-
return [adjustBlockPath(transformedOperation, -1, unsetBlockIndex)]
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Someone reset the whole value
|
|
411
|
-
if (patch.type === 'unset' && patch.path.length === 0) {
|
|
412
|
-
debug(
|
|
413
|
-
`Adjusting selection for unset everything patch and ${operation.type} operation`,
|
|
414
|
-
)
|
|
415
|
-
return []
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (patch.type === 'diffMatchPatch') {
|
|
419
|
-
const operationTargetBlock = findOperationTargetBlock(
|
|
420
|
-
editor,
|
|
421
|
-
transformedOperation,
|
|
422
|
-
)
|
|
423
|
-
if (
|
|
424
|
-
!operationTargetBlock ||
|
|
425
|
-
!isEqual({_key: operationTargetBlock._key}, patch.path[0])
|
|
426
|
-
) {
|
|
427
|
-
return [transformedOperation]
|
|
428
|
-
}
|
|
429
|
-
const diffPatches = parsePatch(patch.value)
|
|
430
|
-
diffPatches.forEach((diffPatch) => {
|
|
431
|
-
let adjustOffsetBy = 0
|
|
432
|
-
let changedOffset = diffPatch.utf8Start1
|
|
433
|
-
const {diffs} = diffPatch
|
|
434
|
-
diffs.forEach((diff, index) => {
|
|
435
|
-
const [diffType, text] = diff
|
|
436
|
-
if (diffType === DIFF_INSERT) {
|
|
437
|
-
adjustOffsetBy += text.length
|
|
438
|
-
changedOffset += text.length
|
|
439
|
-
} else if (diffType === DIFF_DELETE) {
|
|
440
|
-
adjustOffsetBy -= text.length
|
|
441
|
-
changedOffset -= text.length
|
|
442
|
-
} else if (diffType === DIFF_EQUAL) {
|
|
443
|
-
// Only up to the point where there are no other changes
|
|
444
|
-
if (!diffs.slice(index).every(([dType]) => dType === DIFF_EQUAL)) {
|
|
445
|
-
changedOffset += text.length
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
})
|
|
449
|
-
// Adjust accordingly if someone inserted text in the same node before us
|
|
450
|
-
if (transformedOperation.type === 'insert_text') {
|
|
451
|
-
if (changedOffset < transformedOperation.offset) {
|
|
452
|
-
transformedOperation.offset += adjustOffsetBy
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
// Adjust accordingly if someone removed text in the same node before us
|
|
456
|
-
if (transformedOperation.type === 'remove_text') {
|
|
457
|
-
if (
|
|
458
|
-
changedOffset <=
|
|
459
|
-
transformedOperation.offset - transformedOperation.text.length
|
|
460
|
-
) {
|
|
461
|
-
transformedOperation.offset += adjustOffsetBy
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// Adjust set_selection operation's points to new offset
|
|
465
|
-
if (transformedOperation.type === 'set_selection') {
|
|
466
|
-
const currentFocus = transformedOperation.properties?.focus
|
|
467
|
-
? {...transformedOperation.properties.focus}
|
|
468
|
-
: undefined
|
|
469
|
-
const currentAnchor = transformedOperation?.properties?.anchor
|
|
470
|
-
? {...transformedOperation.properties.anchor}
|
|
471
|
-
: undefined
|
|
472
|
-
const newFocus = transformedOperation?.newProperties?.focus
|
|
473
|
-
? {...transformedOperation.newProperties.focus}
|
|
474
|
-
: undefined
|
|
475
|
-
const newAnchor = transformedOperation?.newProperties?.anchor
|
|
476
|
-
? {...transformedOperation.newProperties.anchor}
|
|
477
|
-
: undefined
|
|
478
|
-
if ((currentFocus && currentAnchor) || (newFocus && newAnchor)) {
|
|
479
|
-
const points = [currentFocus, currentAnchor, newFocus, newAnchor]
|
|
480
|
-
points.forEach((point) => {
|
|
481
|
-
if (point && changedOffset < point.offset) {
|
|
482
|
-
point.offset += adjustOffsetBy
|
|
483
|
-
}
|
|
484
|
-
})
|
|
485
|
-
if (currentFocus && currentAnchor) {
|
|
486
|
-
transformedOperation.properties = {
|
|
487
|
-
focus: currentFocus,
|
|
488
|
-
anchor: currentAnchor,
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (newFocus && newAnchor) {
|
|
492
|
-
transformedOperation.newProperties = {
|
|
493
|
-
focus: newFocus,
|
|
494
|
-
anchor: newAnchor,
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
})
|
|
500
|
-
return [transformedOperation]
|
|
501
|
-
}
|
|
502
|
-
return [transformedOperation]
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Adjust the block path for a operation
|
|
506
|
-
*/
|
|
507
|
-
function adjustBlockPath(
|
|
508
|
-
operation: Operation,
|
|
509
|
-
level: number,
|
|
510
|
-
blockIndex: number,
|
|
511
|
-
): Operation {
|
|
512
|
-
const transformedOperation = {...operation}
|
|
513
|
-
if (
|
|
514
|
-
blockIndex >= 0 &&
|
|
515
|
-
transformedOperation.type !== 'set_selection' &&
|
|
516
|
-
Array.isArray(transformedOperation.path) &&
|
|
517
|
-
transformedOperation.path[0] >= blockIndex + level &&
|
|
518
|
-
transformedOperation.path[0] + level > -1
|
|
519
|
-
) {
|
|
520
|
-
const newPath = [
|
|
521
|
-
transformedOperation.path[0] + level,
|
|
522
|
-
...transformedOperation.path.slice(1),
|
|
523
|
-
]
|
|
524
|
-
transformedOperation.path = newPath
|
|
525
|
-
}
|
|
526
|
-
if (transformedOperation.type === 'set_selection') {
|
|
527
|
-
const currentFocus = transformedOperation.properties?.focus
|
|
528
|
-
? {...transformedOperation.properties.focus}
|
|
529
|
-
: undefined
|
|
530
|
-
const currentAnchor = transformedOperation?.properties?.anchor
|
|
531
|
-
? {...transformedOperation.properties.anchor}
|
|
532
|
-
: undefined
|
|
533
|
-
const newFocus = transformedOperation?.newProperties?.focus
|
|
534
|
-
? {...transformedOperation.newProperties.focus}
|
|
535
|
-
: undefined
|
|
536
|
-
const newAnchor = transformedOperation?.newProperties?.anchor
|
|
537
|
-
? {...transformedOperation.newProperties.anchor}
|
|
538
|
-
: undefined
|
|
539
|
-
if ((currentFocus && currentAnchor) || (newFocus && newAnchor)) {
|
|
540
|
-
const points = [currentFocus, currentAnchor, newFocus, newAnchor]
|
|
541
|
-
points.forEach((point) => {
|
|
542
|
-
if (
|
|
543
|
-
point &&
|
|
544
|
-
point.path[0] >= blockIndex + level &&
|
|
545
|
-
point.path[0] + level > -1
|
|
546
|
-
) {
|
|
547
|
-
point.path = [point.path[0] + level, ...point.path.slice(1)]
|
|
548
|
-
}
|
|
549
|
-
})
|
|
550
|
-
if (currentFocus && currentAnchor) {
|
|
551
|
-
transformedOperation.properties = {
|
|
552
|
-
focus: currentFocus,
|
|
553
|
-
anchor: currentAnchor,
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
if (newFocus && newAnchor) {
|
|
557
|
-
transformedOperation.newProperties = {
|
|
558
|
-
focus: newFocus,
|
|
559
|
-
anchor: newAnchor,
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
// // Assign fresh point objects (we don't want to mutate the original ones)
|
|
565
|
-
return transformedOperation
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Helper functions for editor.apply above
|
|
569
|
-
|
|
570
|
-
const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
|
571
|
-
if (op.type === 'set_selection') {
|
|
572
|
-
return true
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Text input
|
|
576
|
-
if (
|
|
577
|
-
prev &&
|
|
578
|
-
op.type === 'insert_text' &&
|
|
579
|
-
prev.type === 'insert_text' &&
|
|
580
|
-
op.offset === prev.offset + prev.text.length &&
|
|
581
|
-
Path.equals(op.path, prev.path) &&
|
|
582
|
-
op.text !== ' ' // Tokenize between words
|
|
583
|
-
) {
|
|
584
|
-
return true
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Text deletion
|
|
588
|
-
if (
|
|
589
|
-
prev &&
|
|
590
|
-
op.type === 'remove_text' &&
|
|
591
|
-
prev.type === 'remove_text' &&
|
|
592
|
-
op.offset + op.text.length === prev.offset &&
|
|
593
|
-
Path.equals(op.path, prev.path)
|
|
594
|
-
) {
|
|
595
|
-
return true
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// Don't merge
|
|
599
|
-
return false
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
export function withoutSaving(editor: Editor, fn: () => void): void {
|
|
603
|
-
const prev = isSaving(editor)
|
|
604
|
-
SAVING.set(editor, false)
|
|
605
|
-
fn()
|
|
606
|
-
SAVING.set(editor, prev)
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function createSelectOperation(editor: Editor): SelectionOperation {
|
|
610
|
-
return {
|
|
611
|
-
type: 'set_selection',
|
|
612
|
-
properties: {...editor.selection},
|
|
613
|
-
newProperties: {...editor.selection},
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function findOperationTargetBlock(
|
|
618
|
-
editor: PortableTextSlateEditor,
|
|
619
|
-
operation: Operation,
|
|
620
|
-
): Descendant | undefined {
|
|
621
|
-
let block: Descendant | undefined
|
|
622
|
-
if (operation.type === 'set_selection' && editor.selection) {
|
|
623
|
-
block = editor.children[editor.selection.focus.path[0]]
|
|
624
|
-
} else if ('path' in operation) {
|
|
625
|
-
block = editor.children[operation.path[0]]
|
|
626
|
-
}
|
|
627
|
-
return block
|
|
628
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type {Editor} from 'slate'
|
|
2
|
-
import {defaultKeyGenerator} from '../utils/key-generator'
|
|
3
|
-
|
|
4
|
-
const CURRENT_UNDO_STEP: WeakMap<Editor, {undoStepId: string} | undefined> =
|
|
5
|
-
new WeakMap()
|
|
6
|
-
|
|
7
|
-
export function withUndoStep(editor: Editor, fn: () => void) {
|
|
8
|
-
const current = CURRENT_UNDO_STEP.get(editor)
|
|
9
|
-
|
|
10
|
-
if (current) {
|
|
11
|
-
fn()
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
CURRENT_UNDO_STEP.set(
|
|
16
|
-
editor,
|
|
17
|
-
current ?? {
|
|
18
|
-
undoStepId: defaultKeyGenerator(),
|
|
19
|
-
},
|
|
20
|
-
)
|
|
21
|
-
fn()
|
|
22
|
-
CURRENT_UNDO_STEP.set(editor, undefined)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getCurrentUndoStepId(editor: Editor) {
|
|
26
|
-
return CURRENT_UNDO_STEP.get(editor)?.undoStepId
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createUndoStep(editor: Editor) {
|
|
30
|
-
CURRENT_UNDO_STEP.set(editor, {
|
|
31
|
-
undoStepId: defaultKeyGenerator(),
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function clearUndoStep(editor: Editor) {
|
|
36
|
-
CURRENT_UNDO_STEP.set(editor, undefined)
|
|
37
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type {Editor} from 'slate'
|
|
2
|
-
|
|
3
|
-
const IS_UDOING: WeakMap<Editor, boolean | undefined> = new WeakMap()
|
|
4
|
-
const IS_REDOING: WeakMap<Editor, boolean | undefined> = new WeakMap()
|
|
5
|
-
|
|
6
|
-
export function withUndoing(editor: Editor, fn: () => void) {
|
|
7
|
-
const prev = isUndoing(editor)
|
|
8
|
-
IS_UDOING.set(editor, true)
|
|
9
|
-
fn()
|
|
10
|
-
IS_UDOING.set(editor, prev)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function isUndoing(editor: Editor) {
|
|
14
|
-
return IS_UDOING.get(editor) ?? false
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function setIsUndoing(editor: Editor, isUndoing: boolean) {
|
|
18
|
-
IS_UDOING.set(editor, isUndoing)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function withRedoing(editor: Editor, fn: () => void) {
|
|
22
|
-
const prev = isRedoing(editor)
|
|
23
|
-
IS_REDOING.set(editor, true)
|
|
24
|
-
fn()
|
|
25
|
-
IS_REDOING.set(editor, prev)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function isRedoing(editor: Editor) {
|
|
29
|
-
return IS_REDOING.get(editor) ?? false
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function setIsRedoing(editor: Editor, isRedoing: boolean) {
|
|
33
|
-
IS_REDOING.set(editor, isRedoing)
|
|
34
|
-
}
|