@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.
Files changed (34) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  2. package/lib/_chunks-cjs/editor-provider.cjs +211 -74
  3. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  4. package/lib/_chunks-es/behavior.core.js.map +1 -1
  5. package/lib/_chunks-es/editor-provider.js +212 -75
  6. package/lib/_chunks-es/editor-provider.js.map +1 -1
  7. package/lib/behaviors/index.d.cts +150 -354
  8. package/lib/behaviors/index.d.ts +150 -354
  9. package/lib/index.d.cts +145 -77
  10. package/lib/index.d.ts +145 -77
  11. package/lib/plugins/index.cjs +19 -19
  12. package/lib/plugins/index.cjs.map +1 -1
  13. package/lib/plugins/index.d.cts +134 -77
  14. package/lib/plugins/index.d.ts +134 -77
  15. package/lib/plugins/index.js +21 -21
  16. package/lib/plugins/index.js.map +1 -1
  17. package/lib/selectors/index.d.cts +123 -77
  18. package/lib/selectors/index.d.ts +123 -77
  19. package/lib/utils/index.d.cts +123 -77
  20. package/lib/utils/index.d.ts +123 -77
  21. package/package.json +6 -6
  22. package/src/behavior-actions/behavior.actions.ts +2 -2
  23. package/src/behaviors/behavior.types.ts +10 -7
  24. package/src/editor/PortableTextEditor.tsx +22 -0
  25. package/src/editor/__tests__/self-solving.test.tsx +33 -1
  26. package/src/editor/components/Synchronizer.tsx +17 -3
  27. package/src/editor/editor-machine.ts +31 -15
  28. package/src/editor/mutation-machine.ts +160 -46
  29. package/src/editor/plugins/create-with-event-listeners.ts +1 -0
  30. package/src/editor/plugins/createWithPatches.ts +10 -3
  31. package/src/editor/with-applying-behavior-actions.ts +8 -6
  32. package/src/plugins/index.ts +2 -1
  33. package/src/plugins/plugin.decorator-shortcut.ts +3 -0
  34. 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.35.3",
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.9",
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.76.3",
90
- "@sanity/types": "^3.76.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.76.3",
119
- "@sanity/types": "^3.76.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.error(
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.error(
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 = PickFromUnion<
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
- : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>
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
- : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
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, strongPatch],
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<PatchEvent | MutationEvent>
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': emit(({event}) => {
274
- assertEvent(event, 'patch')
275
- return event
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
- enqueue(emit(event))
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((behavior) => behavior.on === event.behaviorEvent.type)
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 {assign, emit, setup} from 'xstate'
5
- import {fromSlateValue} from '../internal-utils/values'
6
- import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
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
- pendingPatches: Array<Patch>
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 {type: 'patch'; patch: Patch},
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 mutation': emit(({context}) => ({
40
- type: 'mutation' as const,
41
- patches: context.pendingPatches,
42
- snapshot: fromSlateValue(
43
- context.slateEditor.children,
44
- context.schema.block.name,
45
- KEY_TO_VALUE_ELEMENT.get(context.slateEditor),
46
- ),
47
- })),
48
- 'clear pending patches': assign({
49
- pendingPatches: [],
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
- pendingPatches: ({context, event}) => [
53
- ...context.pendingPatches,
54
- event.patch,
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
- pendingPatches: [],
142
+ pendingMutations: [],
66
143
  schema: input.schema,
67
144
  slateEditor: input.slateEditor,
68
145
  }),
69
- initial: 'idle',
146
+ type: 'parallel',
70
147
  states: {
71
- 'idle': {
72
- on: {
73
- patch: {
74
- actions: ['defer patch', 'emit has pending patches'],
75
- target: 'has pending patches',
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
- 'has pending patches': {
80
- after: {
81
- [FLUSH_PATCHES_THROTTLED_MS]: [
82
- {
83
- guard: 'slate is normalizing',
84
- target: 'idle',
85
- actions: ['emit mutation', 'clear pending patches'],
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
- target: 'has pending patches',
89
- reenter: true,
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
  },
@@ -29,6 +29,7 @@ export function createWithEventListeners(
29
29
  case 'loading':
30
30
  case 'mutation':
31
31
  case 'patch':
32
+ case 'internal.patch':
32
33
  case 'patches':
33
34
  case 'read only':
34
35
  case 'ready':
@@ -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
- patches.forEach((patch) => {
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 IS_APPLYING_BEHAVIOR_ACTIONS: WeakMap<Editor, boolean | undefined> =
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
- const prev = IS_APPLYING_BEHAVIOR_ACTIONS.get(editor)
9
- IS_APPLYING_BEHAVIOR_ACTIONS.set(editor, true)
7
+ CURRENT_ACTION_ID.set(editor, defaultKeyGenerator())
10
8
  Editor.withoutNormalizing(editor, fn)
11
- IS_APPLYING_BEHAVIOR_ACTIONS.set(editor, prev)
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 IS_APPLYING_BEHAVIOR_ACTIONS.get(editor) ?? false
17
+ return getCurrentActionId(editor) !== undefined
16
18
  }
17
19
 
18
20
  ////////