@portabletext/editor 1.35.3 → 1.36.0
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-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/editor-provider.cjs +211 -74
- package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/editor-provider.js +212 -75
- package/lib/_chunks-es/editor-provider.js.map +1 -1
- package/lib/behaviors/index.d.cts +150 -354
- package/lib/behaviors/index.d.ts +150 -354
- package/lib/index.d.cts +145 -77
- package/lib/index.d.ts +145 -77
- package/lib/plugins/index.cjs +19 -19
- package/lib/plugins/index.cjs.map +1 -1
- package/lib/plugins/index.d.cts +134 -77
- package/lib/plugins/index.d.ts +134 -77
- package/lib/plugins/index.js +21 -21
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.d.cts +123 -77
- package/lib/selectors/index.d.ts +123 -77
- package/lib/utils/index.d.cts +123 -77
- package/lib/utils/index.d.ts +123 -77
- package/package.json +6 -6
- package/src/behavior-actions/behavior.actions.ts +2 -2
- package/src/behaviors/behavior.types.ts +10 -7
- package/src/editor/PortableTextEditor.tsx +22 -0
- package/src/editor/__tests__/self-solving.test.tsx +33 -1
- package/src/editor/components/Synchronizer.tsx +17 -3
- package/src/editor/editor-machine.ts +31 -15
- package/src/editor/mutation-machine.ts +160 -46
- package/src/editor/plugins/create-with-event-listeners.ts +1 -0
- package/src/editor/plugins/createWithPatches.ts +10 -3
- package/src/editor/with-applying-behavior-actions.ts +8 -6
- package/src/plugins/index.ts +2 -1
- package/src/plugins/plugin.decorator-shortcut.ts +3 -0
- package/src/utils/util.slice-blocks.test.ts +64 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -79,15 +79,15 @@
|
|
|
79
79
|
"slate-react": "0.112.1",
|
|
80
80
|
"use-effect-event": "^1.0.2",
|
|
81
81
|
"xstate": "^5.19.2",
|
|
82
|
-
"@portabletext/block-tools": "1.1.
|
|
82
|
+
"@portabletext/block-tools": "1.1.10",
|
|
83
83
|
"@portabletext/patches": "1.1.3"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@portabletext/toolkit": "^2.0.17",
|
|
87
87
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
88
88
|
"@sanity/pkg-utils": "^7.0.4",
|
|
89
|
-
"@sanity/schema": "^3.
|
|
90
|
-
"@sanity/types": "^3.
|
|
89
|
+
"@sanity/schema": "^3.77.1",
|
|
90
|
+
"@sanity/types": "^3.77.1",
|
|
91
91
|
"@testing-library/jest-dom": "^6.6.3",
|
|
92
92
|
"@testing-library/react": "^16.2.0",
|
|
93
93
|
"@types/debug": "^4.1.12",
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"racejar": "1.2.0"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
|
-
"@sanity/schema": "^3.
|
|
119
|
-
"@sanity/types": "^3.
|
|
118
|
+
"@sanity/schema": "^3.77.1",
|
|
119
|
+
"@sanity/types": "^3.77.1",
|
|
120
120
|
"react": "^16.9 || ^17 || ^18 || ^19",
|
|
121
121
|
"rxjs": "^7.8.2"
|
|
122
122
|
},
|
|
@@ -112,7 +112,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
112
112
|
},
|
|
113
113
|
'delete.text': deleteTextActionImplementation,
|
|
114
114
|
'deserialization.failure': ({action}) => {
|
|
115
|
-
console.
|
|
115
|
+
console.warn(
|
|
116
116
|
`Deserialization of ${action.mimeType} failed with reason ${action.reason}`,
|
|
117
117
|
)
|
|
118
118
|
},
|
|
@@ -248,7 +248,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
248
248
|
Transforms.select(action.editor, nextBlockPath)
|
|
249
249
|
},
|
|
250
250
|
'serialization.failure': ({action}) => {
|
|
251
|
-
console.
|
|
251
|
+
console.warn(
|
|
252
252
|
`Serialization of ${action.mimeType} failed with reason ${action.reason}`,
|
|
253
253
|
)
|
|
254
254
|
},
|
|
@@ -302,6 +302,7 @@ export type BehaviorEvent =
|
|
|
302
302
|
| SyntheticBehaviorEvent
|
|
303
303
|
| NativeBehaviorEvent
|
|
304
304
|
| CustomBehaviorEvent
|
|
305
|
+
| {type: '*'}
|
|
305
306
|
|
|
306
307
|
/**
|
|
307
308
|
* @beta
|
|
@@ -309,11 +310,9 @@ export type BehaviorEvent =
|
|
|
309
310
|
export type Behavior<
|
|
310
311
|
TBehaviorEventType extends BehaviorEvent['type'] = BehaviorEvent['type'],
|
|
311
312
|
TGuardResponse = true,
|
|
312
|
-
TBehaviorEvent extends BehaviorEvent =
|
|
313
|
-
BehaviorEvent
|
|
314
|
-
'type',
|
|
315
|
-
TBehaviorEventType
|
|
316
|
-
>,
|
|
313
|
+
TBehaviorEvent extends BehaviorEvent = TBehaviorEventType extends '*'
|
|
314
|
+
? BehaviorEvent
|
|
315
|
+
: PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
|
|
317
316
|
> = {
|
|
318
317
|
/**
|
|
319
318
|
* The internal editor event that triggers this behavior.
|
|
@@ -389,7 +388,9 @@ export function defineBehavior<
|
|
|
389
388
|
TGuardResponse,
|
|
390
389
|
TBehaviorEventType extends `custom.${infer TType}`
|
|
391
390
|
? CustomBehaviorEvent<TPayload, TType>
|
|
392
|
-
:
|
|
391
|
+
: TBehaviorEventType extends '*'
|
|
392
|
+
? OmitFromUnion<BehaviorEvent, 'type', '*'>
|
|
393
|
+
: PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>
|
|
393
394
|
>,
|
|
394
395
|
): Behavior
|
|
395
396
|
export function defineBehavior<
|
|
@@ -399,7 +400,9 @@ export function defineBehavior<
|
|
|
399
400
|
TBehaviorEvent extends
|
|
400
401
|
BehaviorEvent = TBehaviorEventType extends `custom.${infer TType}`
|
|
401
402
|
? CustomBehaviorEvent<TPayload, TType>
|
|
402
|
-
:
|
|
403
|
+
: TBehaviorEventType extends '*'
|
|
404
|
+
? OmitFromUnion<BehaviorEvent, 'type', '*'>
|
|
405
|
+
: PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
|
|
403
406
|
>(
|
|
404
407
|
behavior: Behavior<TBehaviorEventType, TGuardResponse, TBehaviorEvent>,
|
|
405
408
|
): Behavior {
|
|
@@ -687,11 +687,33 @@ export class PortableTextEditor extends Component<
|
|
|
687
687
|
return editor.editable?.getFragment()
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
+
/**
|
|
691
|
+
* @deprecated
|
|
692
|
+
* Use `editor.send(...)` instead
|
|
693
|
+
*
|
|
694
|
+
* ```
|
|
695
|
+
* const editor = useEditor()
|
|
696
|
+
* editor.send({
|
|
697
|
+
* type: 'history.undo',
|
|
698
|
+
* })
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
690
701
|
static undo = (editor: PortableTextEditor): void => {
|
|
691
702
|
debug('Host undoing')
|
|
692
703
|
editor.editable?.undo()
|
|
693
704
|
}
|
|
694
705
|
|
|
706
|
+
/**
|
|
707
|
+
* @deprecated
|
|
708
|
+
* Use `editor.send(...)` instead
|
|
709
|
+
*
|
|
710
|
+
* ```
|
|
711
|
+
* const editor = useEditor()
|
|
712
|
+
* editor.send({
|
|
713
|
+
* type: 'history.redo',
|
|
714
|
+
* })
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
695
717
|
static redo = (editor: PortableTextEditor): void => {
|
|
696
718
|
debug('Host redoing')
|
|
697
719
|
editor.editable?.redo()
|
|
@@ -147,7 +147,39 @@ describe('Feature: Self-solving', () => {
|
|
|
147
147
|
})
|
|
148
148
|
expect(onChange).toHaveBeenNthCalledWith(8, {
|
|
149
149
|
type: 'mutation',
|
|
150
|
-
patches: [spanPatch, blockPatch
|
|
150
|
+
patches: [spanPatch, blockPatch],
|
|
151
|
+
snapshot: [
|
|
152
|
+
block({
|
|
153
|
+
_key: 'b1',
|
|
154
|
+
children: [
|
|
155
|
+
span({
|
|
156
|
+
_key: 's1',
|
|
157
|
+
text: 'foo',
|
|
158
|
+
marks: [],
|
|
159
|
+
}),
|
|
160
|
+
],
|
|
161
|
+
style: 'normal',
|
|
162
|
+
markDefs: [],
|
|
163
|
+
}),
|
|
164
|
+
],
|
|
165
|
+
value: [
|
|
166
|
+
block({
|
|
167
|
+
_key: 'b1',
|
|
168
|
+
children: [
|
|
169
|
+
span({
|
|
170
|
+
_key: 's1',
|
|
171
|
+
text: 'foo',
|
|
172
|
+
marks: [],
|
|
173
|
+
}),
|
|
174
|
+
],
|
|
175
|
+
style: 'normal',
|
|
176
|
+
markDefs: [],
|
|
177
|
+
}),
|
|
178
|
+
],
|
|
179
|
+
})
|
|
180
|
+
expect(onChange).toHaveBeenNthCalledWith(9, {
|
|
181
|
+
type: 'mutation',
|
|
182
|
+
patches: [strongPatch],
|
|
151
183
|
snapshot: [
|
|
152
184
|
block({
|
|
153
185
|
_key: 'b1',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {useActorRef, useSelector} from '@xstate/react'
|
|
2
2
|
import {useEffect} from 'react'
|
|
3
3
|
import {debugWithName} from '../../internal-utils/debug'
|
|
4
|
+
import {fromSlateValue} from '../../internal-utils/values'
|
|
5
|
+
import {KEY_TO_VALUE_ELEMENT} from '../../internal-utils/weakMaps'
|
|
4
6
|
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
5
7
|
import type {EditorActor} from '../editor-machine'
|
|
6
8
|
import {mutationMachine} from '../mutation-machine'
|
|
@@ -80,6 +82,18 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
80
82
|
type: 'notify.value changed',
|
|
81
83
|
})
|
|
82
84
|
break
|
|
85
|
+
case 'patch':
|
|
86
|
+
props.editorActor.send({
|
|
87
|
+
...event,
|
|
88
|
+
type: 'internal.patch',
|
|
89
|
+
value: fromSlateValue(
|
|
90
|
+
slateEditor.children,
|
|
91
|
+
props.editorActor.getSnapshot().context.schema.block.name,
|
|
92
|
+
KEY_TO_VALUE_ELEMENT.get(slateEditor),
|
|
93
|
+
),
|
|
94
|
+
})
|
|
95
|
+
break
|
|
96
|
+
|
|
83
97
|
default:
|
|
84
98
|
props.editorActor.send(event)
|
|
85
99
|
}
|
|
@@ -88,7 +102,7 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
88
102
|
return () => {
|
|
89
103
|
subscription.unsubscribe()
|
|
90
104
|
}
|
|
91
|
-
}, [props.editorActor, syncActorRef])
|
|
105
|
+
}, [props.editorActor, slateEditor, syncActorRef])
|
|
92
106
|
|
|
93
107
|
useEffect(() => {
|
|
94
108
|
syncActorRef.send({type: 'update readOnly', readOnly})
|
|
@@ -102,8 +116,8 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
102
116
|
// Subscribe to, and handle changes from the editor
|
|
103
117
|
useEffect(() => {
|
|
104
118
|
debug('Subscribing to patch events')
|
|
105
|
-
const sub = editorActor.on('patch', (event) => {
|
|
106
|
-
mutationActorRef.send(event)
|
|
119
|
+
const sub = editorActor.on('internal.patch', (event) => {
|
|
120
|
+
mutationActorRef.send({...event, type: 'patch'})
|
|
107
121
|
})
|
|
108
122
|
return () => {
|
|
109
123
|
debug('Unsubscribing to patch events')
|
|
@@ -151,6 +151,11 @@ type PatchEvent = {
|
|
|
151
151
|
patch: Patch
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
type InternalPatchEvent = NamespaceEvent<PatchEvent, 'internal'> & {
|
|
155
|
+
actionId?: string
|
|
156
|
+
value: Array<PortableTextBlock>
|
|
157
|
+
}
|
|
158
|
+
|
|
154
159
|
type UnsetEvent = {
|
|
155
160
|
type: 'unset'
|
|
156
161
|
previousValue: Array<PortableTextBlock>
|
|
@@ -191,9 +196,9 @@ export type InternalEditorEvent =
|
|
|
191
196
|
| CustomBehaviorEvent
|
|
192
197
|
| ExternalEditorEvent
|
|
193
198
|
| MutationEvent
|
|
199
|
+
| InternalPatchEvent
|
|
194
200
|
| NamespaceEvent<EditorEmittedEvent, 'notify'>
|
|
195
201
|
| NamespaceEvent<UnsetEvent, 'notify'>
|
|
196
|
-
| PatchEvent
|
|
197
202
|
| SyntheticBehaviorEvent
|
|
198
203
|
| {type: 'dragstart'}
|
|
199
204
|
| {type: 'dragend'}
|
|
@@ -204,6 +209,7 @@ export type InternalEditorEvent =
|
|
|
204
209
|
*/
|
|
205
210
|
export type InternalEditorEmittedEvent =
|
|
206
211
|
| EditorEmittedEvent
|
|
212
|
+
| InternalPatchEvent
|
|
207
213
|
| PatchesEvent
|
|
208
214
|
| UnsetEvent
|
|
209
215
|
| {
|
|
@@ -221,7 +227,7 @@ export const editorMachine = setup({
|
|
|
221
227
|
behaviors: Set<Behavior>
|
|
222
228
|
converters: Set<Converter>
|
|
223
229
|
keyGenerator: () => string
|
|
224
|
-
pendingEvents: Array<
|
|
230
|
+
pendingEvents: Array<InternalPatchEvent | MutationEvent>
|
|
225
231
|
schema: EditorSchema
|
|
226
232
|
initialReadOnly: boolean
|
|
227
233
|
maxBlocks: number | undefined
|
|
@@ -270,9 +276,11 @@ export const editorMachine = setup({
|
|
|
270
276
|
return event.schema
|
|
271
277
|
},
|
|
272
278
|
}),
|
|
273
|
-
'emit patch event':
|
|
274
|
-
assertEvent(event, 'patch')
|
|
275
|
-
|
|
279
|
+
'emit patch event': enqueueActions(({event, enqueue}) => {
|
|
280
|
+
assertEvent(event, 'internal.patch')
|
|
281
|
+
|
|
282
|
+
enqueue.emit(event)
|
|
283
|
+
enqueue.emit({type: 'patch', patch: event.patch})
|
|
276
284
|
}),
|
|
277
285
|
'emit mutation event': emit(({event}) => {
|
|
278
286
|
assertEvent(event, 'mutation')
|
|
@@ -282,13 +290,18 @@ export const editorMachine = setup({
|
|
|
282
290
|
'emit editable': emit({type: 'editable'}),
|
|
283
291
|
'defer event': assign({
|
|
284
292
|
pendingEvents: ({context, event}) => {
|
|
285
|
-
assertEvent(event, ['patch', 'mutation'])
|
|
293
|
+
assertEvent(event, ['internal.patch', 'mutation'])
|
|
286
294
|
return [...context.pendingEvents, event]
|
|
287
295
|
},
|
|
288
296
|
}),
|
|
289
297
|
'emit pending events': enqueueActions(({context, enqueue}) => {
|
|
290
298
|
for (const event of context.pendingEvents) {
|
|
291
|
-
|
|
299
|
+
if (event.type === 'internal.patch') {
|
|
300
|
+
enqueue.emit(event)
|
|
301
|
+
enqueue.emit({type: 'patch', patch: event.patch})
|
|
302
|
+
} else {
|
|
303
|
+
enqueue.emit(event)
|
|
304
|
+
}
|
|
292
305
|
}
|
|
293
306
|
}),
|
|
294
307
|
'emit ready': emit({type: 'ready'}),
|
|
@@ -321,7 +334,10 @@ export const editorMachine = setup({
|
|
|
321
334
|
...foundationalBehaviors,
|
|
322
335
|
...context.behaviors.values(),
|
|
323
336
|
...defaultBehaviors,
|
|
324
|
-
].filter(
|
|
337
|
+
].filter(
|
|
338
|
+
(behavior) =>
|
|
339
|
+
behavior.on === '*' || behavior.on === event.behaviorEvent.type,
|
|
340
|
+
)
|
|
325
341
|
|
|
326
342
|
if (eventBehaviors.length === 0) {
|
|
327
343
|
if (defaultActionCallback) {
|
|
@@ -664,7 +680,7 @@ export const editorMachine = setup({
|
|
|
664
680
|
'setting up': {
|
|
665
681
|
exit: ['emit ready'],
|
|
666
682
|
on: {
|
|
667
|
-
'patch': {
|
|
683
|
+
'internal.patch': {
|
|
668
684
|
actions: 'defer event',
|
|
669
685
|
},
|
|
670
686
|
'mutation': {
|
|
@@ -680,14 +696,14 @@ export const editorMachine = setup({
|
|
|
680
696
|
states: {
|
|
681
697
|
idle: {
|
|
682
698
|
on: {
|
|
683
|
-
normalizing: {
|
|
699
|
+
'normalizing': {
|
|
684
700
|
target: 'normalizing',
|
|
685
701
|
},
|
|
686
|
-
patch: {
|
|
702
|
+
'internal.patch': {
|
|
687
703
|
actions: 'defer event',
|
|
688
704
|
target: '#editor.setup.dirty',
|
|
689
705
|
},
|
|
690
|
-
mutation: {
|
|
706
|
+
'mutation': {
|
|
691
707
|
actions: 'defer event',
|
|
692
708
|
target: '#editor.setup.dirty',
|
|
693
709
|
},
|
|
@@ -698,7 +714,7 @@ export const editorMachine = setup({
|
|
|
698
714
|
'done normalizing': {
|
|
699
715
|
target: 'idle',
|
|
700
716
|
},
|
|
701
|
-
'patch': {
|
|
717
|
+
'internal.patch': {
|
|
702
718
|
actions: 'defer event',
|
|
703
719
|
},
|
|
704
720
|
'mutation': {
|
|
@@ -711,10 +727,10 @@ export const editorMachine = setup({
|
|
|
711
727
|
'dirty': {
|
|
712
728
|
entry: ['emit pending events', 'clear pending events'],
|
|
713
729
|
on: {
|
|
714
|
-
patch: {
|
|
730
|
+
'internal.patch': {
|
|
715
731
|
actions: 'emit patch event',
|
|
716
732
|
},
|
|
717
|
-
mutation: {
|
|
733
|
+
'mutation': {
|
|
718
734
|
actions: 'emit mutation event',
|
|
719
735
|
},
|
|
720
736
|
},
|
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
import type {Patch} from '@portabletext/patches'
|
|
2
2
|
import type {PortableTextBlock} from '@sanity/types'
|
|
3
3
|
import {Editor} from 'slate'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
and,
|
|
6
|
+
assertEvent,
|
|
7
|
+
assign,
|
|
8
|
+
emit,
|
|
9
|
+
enqueueActions,
|
|
10
|
+
fromCallback,
|
|
11
|
+
not,
|
|
12
|
+
setup,
|
|
13
|
+
stateIn,
|
|
14
|
+
type AnyEventObject,
|
|
15
|
+
} from 'xstate'
|
|
7
16
|
import type {PortableTextSlateEditor} from '../types/editor'
|
|
8
17
|
import type {EditorSchema} from './define-schema'
|
|
9
18
|
|
|
10
|
-
const FLUSH_PATCHES_THROTTLED_MS = process.env.NODE_ENV === 'test' ? 500 : 1000
|
|
11
|
-
|
|
12
19
|
/**
|
|
13
20
|
* Makes sure editor mutation events are debounced
|
|
14
21
|
*/
|
|
15
22
|
export const mutationMachine = setup({
|
|
16
23
|
types: {
|
|
17
24
|
context: {} as {
|
|
18
|
-
|
|
25
|
+
pendingMutations: Array<{
|
|
26
|
+
actionId?: string
|
|
27
|
+
value: Array<PortableTextBlock> | undefined
|
|
28
|
+
patches: Array<Patch>
|
|
29
|
+
}>
|
|
19
30
|
schema: EditorSchema
|
|
20
31
|
slateEditor: PortableTextSlateEditor
|
|
21
32
|
},
|
|
22
|
-
events: {} as
|
|
33
|
+
events: {} as
|
|
34
|
+
| {
|
|
35
|
+
type: 'patch'
|
|
36
|
+
patch: Patch
|
|
37
|
+
actionId?: string
|
|
38
|
+
value: Array<PortableTextBlock>
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: 'typing'
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
type: 'not typing'
|
|
45
|
+
},
|
|
23
46
|
input: {} as {
|
|
24
47
|
schema: EditorSchema
|
|
25
48
|
slateEditor: PortableTextSlateEditor
|
|
@@ -36,65 +59,156 @@ export const mutationMachine = setup({
|
|
|
36
59
|
},
|
|
37
60
|
actions: {
|
|
38
61
|
'emit has pending patches': emit({type: 'has pending patches'}),
|
|
39
|
-
'emit
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
'clear pending
|
|
49
|
-
|
|
62
|
+
'emit mutations': enqueueActions(({context, enqueue}) => {
|
|
63
|
+
for (const bulk of context.pendingMutations) {
|
|
64
|
+
enqueue.emit({
|
|
65
|
+
type: 'mutation',
|
|
66
|
+
patches: bulk.patches,
|
|
67
|
+
snapshot: bulk.value,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
'clear pending mutations': assign({
|
|
72
|
+
pendingMutations: [],
|
|
50
73
|
}),
|
|
51
74
|
'defer patch': assign({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
pendingMutations: ({context, event}) => {
|
|
76
|
+
assertEvent(event, 'patch')
|
|
77
|
+
|
|
78
|
+
if (context.pendingMutations.length === 0) {
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
actionId: event.actionId,
|
|
82
|
+
value: event.value,
|
|
83
|
+
patches: [event.patch],
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const lastBulk = context.pendingMutations.at(-1)
|
|
89
|
+
|
|
90
|
+
if (lastBulk && lastBulk.actionId === event.actionId) {
|
|
91
|
+
return context.pendingMutations.slice(0, -1).concat({
|
|
92
|
+
value: event.value,
|
|
93
|
+
actionId: lastBulk.actionId,
|
|
94
|
+
patches: [...lastBulk.patches, event.patch],
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return context.pendingMutations.concat({
|
|
99
|
+
value: event.value,
|
|
100
|
+
actionId: event.actionId,
|
|
101
|
+
patches: [event.patch],
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
actors: {
|
|
107
|
+
'type listener': fromCallback<
|
|
108
|
+
AnyEventObject,
|
|
109
|
+
{slateEditor: PortableTextSlateEditor},
|
|
110
|
+
{type: 'typing'} | {type: 'not typing'}
|
|
111
|
+
>(({input, sendBack}) => {
|
|
112
|
+
const originalApply = input.slateEditor.apply
|
|
113
|
+
|
|
114
|
+
input.slateEditor.apply = (op) => {
|
|
115
|
+
if (op.type === 'insert_text' || op.type === 'remove_text') {
|
|
116
|
+
sendBack({type: 'typing'})
|
|
117
|
+
} else {
|
|
118
|
+
sendBack({type: 'not typing'})
|
|
119
|
+
}
|
|
120
|
+
originalApply(op)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
input.slateEditor.apply = originalApply
|
|
125
|
+
}
|
|
56
126
|
}),
|
|
57
127
|
},
|
|
58
128
|
guards: {
|
|
129
|
+
'is typing': stateIn({typing: 'typing'}),
|
|
130
|
+
'no pending mutations': ({context}) =>
|
|
131
|
+
context.pendingMutations.length === 0,
|
|
59
132
|
'slate is normalizing': ({context}) =>
|
|
60
133
|
Editor.isNormalizing(context.slateEditor),
|
|
61
134
|
},
|
|
135
|
+
delays: {
|
|
136
|
+
'mutation debounce': process.env.NODE_ENV === 'test' ? 250 : 0,
|
|
137
|
+
'type debounce': process.env.NODE_ENV === 'test' ? 0 : 250,
|
|
138
|
+
},
|
|
62
139
|
}).createMachine({
|
|
63
140
|
id: 'mutation',
|
|
64
141
|
context: ({input}) => ({
|
|
65
|
-
|
|
142
|
+
pendingMutations: [],
|
|
66
143
|
schema: input.schema,
|
|
67
144
|
slateEditor: input.slateEditor,
|
|
68
145
|
}),
|
|
69
|
-
|
|
146
|
+
type: 'parallel',
|
|
70
147
|
states: {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
148
|
+
typing: {
|
|
149
|
+
initial: 'idle',
|
|
150
|
+
invoke: {
|
|
151
|
+
src: 'type listener',
|
|
152
|
+
input: ({context}) => ({slateEditor: context.slateEditor}),
|
|
153
|
+
},
|
|
154
|
+
states: {
|
|
155
|
+
idle: {
|
|
156
|
+
on: {
|
|
157
|
+
typing: {
|
|
158
|
+
target: 'typing',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
typing: {
|
|
163
|
+
after: {
|
|
164
|
+
'type debounce': {
|
|
165
|
+
target: 'idle',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
on: {
|
|
169
|
+
'not typing': {
|
|
170
|
+
target: 'idle',
|
|
171
|
+
},
|
|
172
|
+
'typing': {
|
|
173
|
+
target: 'typing',
|
|
174
|
+
reenter: true,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
76
177
|
},
|
|
77
178
|
},
|
|
78
179
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
180
|
+
mutations: {
|
|
181
|
+
initial: 'idle',
|
|
182
|
+
states: {
|
|
183
|
+
'idle': {
|
|
184
|
+
on: {
|
|
185
|
+
patch: {
|
|
186
|
+
actions: ['defer patch', 'emit has pending patches'],
|
|
187
|
+
target: 'emitting mutations',
|
|
188
|
+
},
|
|
86
189
|
},
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
190
|
+
},
|
|
191
|
+
'emitting mutations': {
|
|
192
|
+
after: {
|
|
193
|
+
'mutation debounce': [
|
|
194
|
+
{
|
|
195
|
+
guard: and([not('is typing'), 'slate is normalizing']),
|
|
196
|
+
target: 'idle',
|
|
197
|
+
actions: ['emit mutations', 'clear pending mutations'],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
target: 'emitting mutations',
|
|
201
|
+
reenter: true,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
on: {
|
|
206
|
+
patch: {
|
|
207
|
+
target: 'emitting mutations',
|
|
208
|
+
actions: ['defer patch'],
|
|
209
|
+
reenter: true,
|
|
210
|
+
},
|
|
90
211
|
},
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
on: {
|
|
94
|
-
patch: {
|
|
95
|
-
target: 'has pending patches',
|
|
96
|
-
actions: ['defer patch'],
|
|
97
|
-
reenter: true,
|
|
98
212
|
},
|
|
99
213
|
},
|
|
100
214
|
},
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
PortableTextSlateEditor,
|
|
31
31
|
} from '../../types/editor'
|
|
32
32
|
import type {EditorActor} from '../editor-machine'
|
|
33
|
+
import {getCurrentActionId} from '../with-applying-behavior-actions'
|
|
33
34
|
import {withoutSaving} from './createWithUndoRedo'
|
|
34
35
|
|
|
35
36
|
const debug = debugWithName('plugin:withPatches')
|
|
@@ -287,12 +288,18 @@ export function createWithPatches({
|
|
|
287
288
|
|
|
288
289
|
// Emit all patches
|
|
289
290
|
if (patches.length > 0) {
|
|
290
|
-
|
|
291
|
+
for (const patch of patches) {
|
|
291
292
|
editorActor.send({
|
|
292
|
-
type: 'patch',
|
|
293
|
+
type: 'internal.patch',
|
|
293
294
|
patch: {...patch, origin: 'local'},
|
|
295
|
+
actionId: getCurrentActionId(editor),
|
|
296
|
+
value: fromSlateValue(
|
|
297
|
+
editor.children,
|
|
298
|
+
schemaTypes.block.name,
|
|
299
|
+
KEY_TO_VALUE_ELEMENT.get(editor),
|
|
300
|
+
),
|
|
294
301
|
})
|
|
295
|
-
}
|
|
302
|
+
}
|
|
296
303
|
}
|
|
297
304
|
return editor
|
|
298
305
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import {Editor} from 'slate'
|
|
2
2
|
import {defaultKeyGenerator} from './key-generator'
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
new WeakMap()
|
|
4
|
+
const CURRENT_ACTION_ID: WeakMap<Editor, string | undefined> = new WeakMap()
|
|
6
5
|
|
|
7
6
|
export function withApplyingBehaviorActions(editor: Editor, fn: () => void) {
|
|
8
|
-
|
|
9
|
-
IS_APPLYING_BEHAVIOR_ACTIONS.set(editor, true)
|
|
7
|
+
CURRENT_ACTION_ID.set(editor, defaultKeyGenerator())
|
|
10
8
|
Editor.withoutNormalizing(editor, fn)
|
|
11
|
-
|
|
9
|
+
CURRENT_ACTION_ID.set(editor, undefined)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getCurrentActionId(editor: Editor) {
|
|
13
|
+
return CURRENT_ACTION_ID.get(editor)
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function isApplyingBehaviorActions(editor: Editor) {
|
|
15
|
-
return
|
|
17
|
+
return getCurrentActionId(editor) !== undefined
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
////////
|