@portabletext/editor 1.50.2 → 1.50.4

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 (33) hide show
  1. package/lib/behaviors/index.d.cts +1 -0
  2. package/lib/behaviors/index.d.ts +1 -0
  3. package/lib/index.cjs +577 -286
  4. package/lib/index.cjs.map +1 -1
  5. package/lib/index.d.cts +15 -2
  6. package/lib/index.d.ts +15 -2
  7. package/lib/index.js +584 -292
  8. package/lib/index.js.map +1 -1
  9. package/lib/plugins/index.d.cts +7 -0
  10. package/lib/plugins/index.d.ts +7 -0
  11. package/lib/selectors/index.d.cts +1 -0
  12. package/lib/selectors/index.d.ts +1 -0
  13. package/lib/utils/index.d.cts +1 -0
  14. package/lib/utils/index.d.ts +1 -0
  15. package/package.json +14 -13
  16. package/src/editor/PortableTextEditor.tsx +22 -22
  17. package/src/editor/create-slate-editor.tsx +9 -1
  18. package/src/editor/editor-selector.ts +1 -5
  19. package/src/editor/editor-snapshot.ts +1 -3
  20. package/src/editor/plugins/createWithPatches.ts +37 -75
  21. package/src/editor/plugins/slate-plugin.update-value.ts +30 -0
  22. package/src/editor/plugins/with-plugins.ts +8 -4
  23. package/src/editor/relay-machine.ts +9 -0
  24. package/src/internal-utils/apply-operation-to-portable-text.test.ts +175 -0
  25. package/src/internal-utils/apply-operation-to-portable-text.ts +435 -0
  26. package/src/internal-utils/create-placeholder-block.ts +20 -0
  27. package/src/internal-utils/{__tests__/operationToPatches.test.ts → operation-to-patches.test.ts} +44 -39
  28. package/src/internal-utils/operation-to-patches.ts +467 -0
  29. package/src/internal-utils/portable-text-node.ts +209 -0
  30. package/src/types/editor.ts +8 -2
  31. package/src/internal-utils/__tests__/patchToOperations.test.ts +0 -312
  32. package/src/internal-utils/operationToPatches.ts +0 -489
  33. package/src/internal-utils/slate-children-to-blocks.ts +0 -49
@@ -2,14 +2,21 @@ import type {PortableTextTextBlock} from '@sanity/types'
2
2
  import {createEditor, type Descendant} from 'slate'
3
3
  import {beforeEach, describe, expect, it} from 'vitest'
4
4
  import {createActor} from 'xstate'
5
- import {schemaType} from '../../editor/__tests__/PortableTextEditorTester'
6
- import {editorMachine} from '../../editor/editor-machine'
7
- import {legacySchemaToEditorSchema} from '../../editor/editor-schema'
8
- import {defaultKeyGenerator} from '../../editor/key-generator'
9
- import {createLegacySchema} from '../../editor/legacy-schema'
10
- import {withPlugins} from '../../editor/plugins/with-plugins'
11
- import {relayMachine} from '../../editor/relay-machine'
12
- import {createOperationToPatches} from '../operationToPatches'
5
+ import {schemaType} from '../editor/__tests__/PortableTextEditorTester'
6
+ import {editorMachine} from '../editor/editor-machine'
7
+ import {legacySchemaToEditorSchema} from '../editor/editor-schema'
8
+ import {defaultKeyGenerator} from '../editor/key-generator'
9
+ import {createLegacySchema} from '../editor/legacy-schema'
10
+ import {withPlugins} from '../editor/plugins/with-plugins'
11
+ import {relayMachine} from '../editor/relay-machine'
12
+ import {
13
+ insertNodePatch,
14
+ insertTextPatch,
15
+ mergeNodePatch,
16
+ removeNodePatch,
17
+ removeTextPatch,
18
+ splitNodePatch,
19
+ } from './operation-to-patches'
13
20
 
14
21
  const legacySchema = createLegacySchema(schemaType)
15
22
  const schemaTypes = legacySchemaToEditorSchema(legacySchema)
@@ -21,7 +28,6 @@ const editorActor = createActor(editorMachine, {
21
28
  },
22
29
  })
23
30
  const relayActor = createActor(relayMachine)
24
- const operationToPatches = createOperationToPatches(editorActor)
25
31
 
26
32
  const editor = withPlugins(createEditor(), {
27
33
  editorActor,
@@ -58,8 +64,9 @@ describe('operationToPatches', () => {
58
64
 
59
65
  it('translates void items correctly when splitting spans', () => {
60
66
  expect(
61
- operationToPatches.splitNodePatch(
62
- editor,
67
+ splitNodePatch(
68
+ schemaTypes,
69
+ editor.children,
63
70
  {
64
71
  type: 'split_node',
65
72
  path: [0, 0],
@@ -111,8 +118,9 @@ describe('operationToPatches', () => {
111
118
 
112
119
  it('produce correct insert block patch', () => {
113
120
  expect(
114
- operationToPatches.insertNodePatch(
115
- editor,
121
+ insertNodePatch(
122
+ schemaTypes,
123
+ editor.children,
116
124
  {
117
125
  type: 'insert_node',
118
126
  path: [0],
@@ -152,8 +160,9 @@ describe('operationToPatches', () => {
152
160
  editor.children = []
153
161
  editor.onChange()
154
162
  expect(
155
- operationToPatches.insertNodePatch(
156
- editor,
163
+ insertNodePatch(
164
+ schemaTypes,
165
+ editor.children,
157
166
  {
158
167
  type: 'insert_node',
159
168
  path: [0],
@@ -194,8 +203,9 @@ describe('operationToPatches', () => {
194
203
 
195
204
  it('produce correct insert child patch', () => {
196
205
  expect(
197
- operationToPatches.insertNodePatch(
198
- editor,
206
+ insertNodePatch(
207
+ schemaTypes,
208
+ editor.children,
199
209
  {
200
210
  type: 'insert_node',
201
211
  path: [0, 3],
@@ -240,15 +250,15 @@ describe('operationToPatches', () => {
240
250
  ;(editor.children[0] as PortableTextTextBlock).children[2].text = '1'
241
251
  editor.onChange()
242
252
  expect(
243
- operationToPatches.insertTextPatch(
244
- editor,
253
+ insertTextPatch(
254
+ editorActor.getSnapshot().context.schema,
255
+ editor.children,
245
256
  {
246
257
  type: 'insert_text',
247
258
  path: [0, 2],
248
259
  text: '1',
249
260
  offset: 0,
250
261
  },
251
-
252
262
  createDefaultValue(),
253
263
  ),
254
264
  ).toMatchInlineSnapshot(`
@@ -277,8 +287,9 @@ describe('operationToPatches', () => {
277
287
  const before = createDefaultValue()
278
288
  ;(before[0] as PortableTextTextBlock).children[2].text = '1'
279
289
  expect(
280
- operationToPatches.removeTextPatch(
281
- editor,
290
+ removeTextPatch(
291
+ editorActor.getSnapshot().context.schema,
292
+ editor.children,
282
293
  {
283
294
  type: 'remove_text',
284
295
  path: [0, 2],
@@ -312,8 +323,9 @@ describe('operationToPatches', () => {
312
323
 
313
324
  it('produces correct remove child patch', () => {
314
325
  expect(
315
- operationToPatches.removeNodePatch(
316
- editor,
326
+ removeNodePatch(
327
+ editorActor.getSnapshot().context.schema,
328
+ createDefaultValue(),
317
329
  {
318
330
  type: 'remove_node',
319
331
  path: [0, 1],
@@ -325,8 +337,6 @@ describe('operationToPatches', () => {
325
337
  children: [{_type: 'span', _key: 'bogus', text: '', marks: []}],
326
338
  },
327
339
  },
328
-
329
- createDefaultValue(),
330
340
  ),
331
341
  ).toMatchInlineSnapshot(`
332
342
  [
@@ -349,16 +359,11 @@ describe('operationToPatches', () => {
349
359
  it('produce correct remove block patch', () => {
350
360
  const val = createDefaultValue()
351
361
  expect(
352
- operationToPatches.removeNodePatch(
353
- editor,
354
- {
355
- type: 'remove_node',
356
- path: [0],
357
- node: val[0],
358
- },
359
-
360
- val,
361
- ),
362
+ removeNodePatch(editorActor.getSnapshot().context.schema, val, {
363
+ type: 'remove_node',
364
+ path: [0],
365
+ node: val[0],
366
+ }),
362
367
  ).toMatchInlineSnapshot(`
363
368
  [
364
369
  {
@@ -386,15 +391,15 @@ describe('operationToPatches', () => {
386
391
  block.children[2].text = '1234'
387
392
  editor.onChange()
388
393
  expect(
389
- operationToPatches.mergeNodePatch(
390
- editor,
394
+ mergeNodePatch(
395
+ schemaTypes,
396
+ editor.children,
391
397
  {
392
398
  type: 'merge_node',
393
399
  path: [0, 3],
394
400
  position: 2,
395
401
  properties: {text: '1234'},
396
402
  },
397
-
398
403
  val,
399
404
  ),
400
405
  ).toMatchInlineSnapshot(`
@@ -0,0 +1,467 @@
1
+ import {
2
+ diffMatchPatch,
3
+ insert,
4
+ set,
5
+ setIfMissing,
6
+ unset,
7
+ type InsertPosition,
8
+ type Patch,
9
+ } from '@portabletext/patches'
10
+ import type {Path, PortableTextSpan, PortableTextTextBlock} from '@sanity/types'
11
+ import {get, isUndefined, omitBy} from 'lodash'
12
+ import {
13
+ Text,
14
+ type Descendant,
15
+ type InsertNodeOperation,
16
+ type InsertTextOperation,
17
+ type MergeNodeOperation,
18
+ type MoveNodeOperation,
19
+ type RemoveNodeOperation,
20
+ type RemoveTextOperation,
21
+ type SetNodeOperation,
22
+ type SplitNodeOperation,
23
+ } from 'slate'
24
+ import type {EditorSchema} from '../editor/editor-schema'
25
+ import {isSpan, isTextBlock} from './parse-blocks'
26
+ import {fromSlateValue} from './values'
27
+
28
+ export function insertTextPatch(
29
+ schema: EditorSchema,
30
+ children: Descendant[],
31
+ operation: InsertTextOperation,
32
+ beforeValue: Descendant[],
33
+ ): Array<Patch> {
34
+ const block =
35
+ isTextBlock({schema}, children[operation.path[0]]) &&
36
+ children[operation.path[0]]
37
+ if (!block) {
38
+ throw new Error('Could not find block')
39
+ }
40
+ const textChild =
41
+ isTextBlock({schema}, block) &&
42
+ isSpan({schema}, block.children[operation.path[1]]) &&
43
+ (block.children[operation.path[1]] as PortableTextSpan)
44
+ if (!textChild) {
45
+ throw new Error('Could not find child')
46
+ }
47
+ const path: Path = [
48
+ {_key: block._key},
49
+ 'children',
50
+ {_key: textChild._key},
51
+ 'text',
52
+ ]
53
+ const prevBlock = beforeValue[operation.path[0]]
54
+ const prevChild =
55
+ isTextBlock({schema}, prevBlock) && prevBlock.children[operation.path[1]]
56
+ const prevText = isSpan({schema}, prevChild) ? prevChild.text : ''
57
+ const patch = diffMatchPatch(prevText, textChild.text, path)
58
+ return patch.value.length ? [patch] : []
59
+ }
60
+
61
+ export function removeTextPatch(
62
+ schema: EditorSchema,
63
+ children: Descendant[],
64
+ operation: RemoveTextOperation,
65
+ beforeValue: Descendant[],
66
+ ): Array<Patch> {
67
+ const block = children[operation.path[0]]
68
+ if (!block) {
69
+ throw new Error('Could not find block')
70
+ }
71
+ const child =
72
+ (isTextBlock({schema}, block) && block.children[operation.path[1]]) ||
73
+ undefined
74
+ const textChild: PortableTextSpan | undefined = isSpan({schema}, child)
75
+ ? child
76
+ : undefined
77
+ if (child && !textChild) {
78
+ throw new Error('Expected span')
79
+ }
80
+ if (!textChild) {
81
+ throw new Error('Could not find child')
82
+ }
83
+ const path: Path = [
84
+ {_key: block._key},
85
+ 'children',
86
+ {_key: textChild._key},
87
+ 'text',
88
+ ]
89
+ const beforeBlock = beforeValue[operation.path[0]]
90
+ const prevTextChild =
91
+ isTextBlock({schema}, beforeBlock) &&
92
+ beforeBlock.children[operation.path[1]]
93
+ const prevText = isSpan({schema}, prevTextChild) && prevTextChild.text
94
+ const patch = diffMatchPatch(prevText || '', textChild.text, path)
95
+ return patch.value ? [patch] : []
96
+ }
97
+
98
+ export function setNodePatch(
99
+ schema: EditorSchema,
100
+ children: Descendant[],
101
+ operation: SetNodeOperation,
102
+ ): Array<Patch> {
103
+ if (operation.path.length === 1) {
104
+ const block = children[operation.path[0]]
105
+ if (typeof block._key !== 'string') {
106
+ throw new Error('Expected block to have a _key')
107
+ }
108
+ const setNode = omitBy(
109
+ {...children[operation.path[0]], ...operation.newProperties},
110
+ isUndefined,
111
+ ) as unknown as Descendant
112
+ return [
113
+ set(fromSlateValue([setNode], schema.block.name)[0], [
114
+ {_key: block._key},
115
+ ]),
116
+ ]
117
+ } else if (operation.path.length === 2) {
118
+ const block = children[operation.path[0]]
119
+ if (isTextBlock({schema}, block)) {
120
+ const child = block.children[operation.path[1]]
121
+ if (child) {
122
+ const blockKey = block._key
123
+ const childKey = child._key
124
+ const patches: Patch[] = []
125
+ const keys = Object.keys(operation.newProperties)
126
+ keys.forEach((keyName) => {
127
+ // Special case for setting _key on a child. We have to target it by index and not the _key.
128
+ if (keys.length === 1 && keyName === '_key') {
129
+ const val = get(operation.newProperties, keyName)
130
+ patches.push(
131
+ set(val, [
132
+ {_key: blockKey},
133
+ 'children',
134
+ block.children.indexOf(child),
135
+ keyName,
136
+ ]),
137
+ )
138
+ } else {
139
+ const val = get(operation.newProperties, keyName)
140
+ patches.push(
141
+ set(val, [
142
+ {_key: blockKey},
143
+ 'children',
144
+ {_key: childKey},
145
+ keyName,
146
+ ]),
147
+ )
148
+ }
149
+ })
150
+ return patches
151
+ }
152
+ throw new Error('Could not find a valid child')
153
+ }
154
+ throw new Error('Could not find a valid block')
155
+ } else {
156
+ throw new Error(
157
+ `Unexpected path encountered: ${JSON.stringify(operation.path)}`,
158
+ )
159
+ }
160
+ }
161
+
162
+ export function insertNodePatch(
163
+ schema: EditorSchema,
164
+ children: Descendant[],
165
+ operation: InsertNodeOperation,
166
+ beforeValue: Descendant[],
167
+ ): Array<Patch> {
168
+ const block = beforeValue[operation.path[0]]
169
+ if (operation.path.length === 1) {
170
+ const position = operation.path[0] === 0 ? 'before' : 'after'
171
+ const beforeBlock = beforeValue[operation.path[0] - 1]
172
+ const targetKey = operation.path[0] === 0 ? block?._key : beforeBlock?._key
173
+ if (targetKey) {
174
+ return [
175
+ insert(
176
+ [
177
+ fromSlateValue(
178
+ [operation.node as Descendant],
179
+ schema.block.name,
180
+ )[0],
181
+ ],
182
+ position,
183
+ [{_key: targetKey}],
184
+ ),
185
+ ]
186
+ }
187
+ return [
188
+ setIfMissing(beforeValue, []),
189
+ insert(
190
+ [fromSlateValue([operation.node as Descendant], schema.block.name)[0]],
191
+ 'before',
192
+ [operation.path[0]],
193
+ ),
194
+ ]
195
+ } else if (
196
+ isTextBlock({schema}, block) &&
197
+ operation.path.length === 2 &&
198
+ children[operation.path[0]]
199
+ ) {
200
+ const position =
201
+ block.children.length === 0 || !block.children[operation.path[1] - 1]
202
+ ? 'before'
203
+ : 'after'
204
+ const node = {...operation.node} as Descendant
205
+ if (!node._type && Text.isText(node)) {
206
+ node._type = 'span'
207
+ node.marks = []
208
+ }
209
+ const blk = fromSlateValue(
210
+ [
211
+ {
212
+ _key: 'bogus',
213
+ _type: schema.block.name,
214
+ children: [node],
215
+ },
216
+ ],
217
+ schema.block.name,
218
+ )[0] as PortableTextTextBlock
219
+ const child = blk.children[0]
220
+ return [
221
+ insert([child], position, [
222
+ {_key: block._key},
223
+ 'children',
224
+ block.children.length <= 1 || !block.children[operation.path[1] - 1]
225
+ ? 0
226
+ : {_key: block.children[operation.path[1] - 1]._key},
227
+ ]),
228
+ ]
229
+ }
230
+ return []
231
+ }
232
+
233
+ export function splitNodePatch(
234
+ schema: EditorSchema,
235
+ children: Descendant[],
236
+ operation: SplitNodeOperation,
237
+ beforeValue: Descendant[],
238
+ ): Array<Patch> {
239
+ const patches: Patch[] = []
240
+ const splitBlock = children[operation.path[0]]
241
+ if (!isTextBlock({schema}, splitBlock)) {
242
+ throw new Error(
243
+ `Block with path ${JSON.stringify(
244
+ operation.path[0],
245
+ )} is not a text block and can't be split`,
246
+ )
247
+ }
248
+ if (operation.path.length === 1) {
249
+ const oldBlock = beforeValue[operation.path[0]]
250
+ if (isTextBlock({schema}, oldBlock)) {
251
+ const targetValue = fromSlateValue(
252
+ [children[operation.path[0] + 1]],
253
+ schema.block.name,
254
+ )[0]
255
+ if (targetValue) {
256
+ patches.push(insert([targetValue], 'after', [{_key: splitBlock._key}]))
257
+ const spansToUnset = oldBlock.children.slice(operation.position)
258
+ spansToUnset.forEach((span) => {
259
+ const path = [{_key: oldBlock._key}, 'children', {_key: span._key}]
260
+ patches.push(unset(path))
261
+ })
262
+ }
263
+ }
264
+ return patches
265
+ }
266
+ if (operation.path.length === 2) {
267
+ const splitSpan = splitBlock.children[operation.path[1]]
268
+ if (isSpan({schema}, splitSpan)) {
269
+ const targetSpans = (
270
+ fromSlateValue(
271
+ [
272
+ {
273
+ ...splitBlock,
274
+ children: splitBlock.children.slice(
275
+ operation.path[1] + 1,
276
+ operation.path[1] + 2,
277
+ ),
278
+ } as Descendant,
279
+ ],
280
+ schema.block.name,
281
+ )[0] as PortableTextTextBlock
282
+ ).children
283
+
284
+ patches.push(
285
+ insert(targetSpans, 'after', [
286
+ {_key: splitBlock._key},
287
+ 'children',
288
+ {_key: splitSpan._key},
289
+ ]),
290
+ )
291
+ patches.push(
292
+ set(splitSpan.text, [
293
+ {_key: splitBlock._key},
294
+ 'children',
295
+ {_key: splitSpan._key},
296
+ 'text',
297
+ ]),
298
+ )
299
+ }
300
+ return patches
301
+ }
302
+ return patches
303
+ }
304
+
305
+ export function removeNodePatch(
306
+ schema: EditorSchema,
307
+ beforeValue: Descendant[],
308
+ operation: RemoveNodeOperation,
309
+ ): Array<Patch> {
310
+ const block = beforeValue[operation.path[0]]
311
+ if (operation.path.length === 1) {
312
+ // Remove a single block
313
+ if (block && block._key) {
314
+ return [unset([{_key: block._key}])]
315
+ }
316
+ throw new Error('Block not found')
317
+ } else if (isTextBlock({schema}, block) && operation.path.length === 2) {
318
+ const spanToRemove = block.children[operation.path[1]]
319
+
320
+ if (spanToRemove) {
321
+ const spansMatchingKey = block.children.filter(
322
+ (span) => span._key === operation.node._key,
323
+ )
324
+
325
+ if (spansMatchingKey.length > 1) {
326
+ console.warn(
327
+ `Multiple spans have \`_key\` ${operation.node._key}. It's ambiguous which one to remove.`,
328
+ JSON.stringify(block, null, 2),
329
+ )
330
+ return []
331
+ }
332
+
333
+ return [
334
+ unset([{_key: block._key}, 'children', {_key: spanToRemove._key}]),
335
+ ]
336
+ }
337
+ return []
338
+ } else {
339
+ return []
340
+ }
341
+ }
342
+
343
+ export function mergeNodePatch(
344
+ schema: EditorSchema,
345
+ children: Descendant[],
346
+ operation: MergeNodeOperation,
347
+ beforeValue: Descendant[],
348
+ ): Array<Patch> {
349
+ const patches: Patch[] = []
350
+
351
+ const block = beforeValue[operation.path[0]]
352
+ const updatedBlock = children[operation.path[0]]
353
+
354
+ if (operation.path.length === 1) {
355
+ if (block?._key) {
356
+ const newBlock = fromSlateValue(
357
+ [children[operation.path[0] - 1]],
358
+ schema.block.name,
359
+ )[0]
360
+ patches.push(set(newBlock, [{_key: newBlock._key}]))
361
+ patches.push(unset([{_key: block._key}]))
362
+ } else {
363
+ throw new Error('Target key not found!')
364
+ }
365
+ } else if (
366
+ isTextBlock({schema}, block) &&
367
+ isTextBlock({schema}, updatedBlock) &&
368
+ operation.path.length === 2
369
+ ) {
370
+ const updatedSpan =
371
+ updatedBlock.children[operation.path[1] - 1] &&
372
+ isSpan({schema}, updatedBlock.children[operation.path[1] - 1])
373
+ ? updatedBlock.children[operation.path[1] - 1]
374
+ : undefined
375
+ const removedSpan =
376
+ block.children[operation.path[1]] &&
377
+ isSpan({schema}, block.children[operation.path[1]])
378
+ ? block.children[operation.path[1]]
379
+ : undefined
380
+
381
+ if (updatedSpan) {
382
+ const spansMatchingKey = block.children.filter(
383
+ (span) => span._key === updatedSpan._key,
384
+ )
385
+
386
+ if (spansMatchingKey.length === 1) {
387
+ patches.push(
388
+ set(updatedSpan.text, [
389
+ {_key: block._key},
390
+ 'children',
391
+ {_key: updatedSpan._key},
392
+ 'text',
393
+ ]),
394
+ )
395
+ } else {
396
+ console.warn(
397
+ `Multiple spans have \`_key\` ${updatedSpan._key}. It's ambiguous which one to update.`,
398
+ JSON.stringify(block, null, 2),
399
+ )
400
+ }
401
+ }
402
+
403
+ if (removedSpan) {
404
+ const spansMatchingKey = block.children.filter(
405
+ (span) => span._key === removedSpan._key,
406
+ )
407
+
408
+ if (spansMatchingKey.length === 1) {
409
+ patches.push(
410
+ unset([{_key: block._key}, 'children', {_key: removedSpan._key}]),
411
+ )
412
+ } else {
413
+ console.warn(
414
+ `Multiple spans have \`_key\` ${removedSpan._key}. It's ambiguous which one to remove.`,
415
+ JSON.stringify(block, null, 2),
416
+ )
417
+ }
418
+ }
419
+ }
420
+ return patches
421
+ }
422
+
423
+ export function moveNodePatch(
424
+ schema: EditorSchema,
425
+ beforeValue: Descendant[],
426
+ operation: MoveNodeOperation,
427
+ ): Array<Patch> {
428
+ const patches: Patch[] = []
429
+ const block = beforeValue[operation.path[0]]
430
+ const targetBlock = beforeValue[operation.newPath[0]]
431
+
432
+ if (!targetBlock) {
433
+ return patches
434
+ }
435
+
436
+ if (operation.path.length === 1) {
437
+ const position: InsertPosition =
438
+ operation.path[0] > operation.newPath[0] ? 'before' : 'after'
439
+ patches.push(unset([{_key: block._key}]))
440
+ patches.push(
441
+ insert([fromSlateValue([block], schema.block.name)[0]], position, [
442
+ {_key: targetBlock._key},
443
+ ]),
444
+ )
445
+ } else if (
446
+ operation.path.length === 2 &&
447
+ isTextBlock({schema}, block) &&
448
+ isTextBlock({schema}, targetBlock)
449
+ ) {
450
+ const child = block.children[operation.path[1]]
451
+ const targetChild = targetBlock.children[operation.newPath[1]]
452
+ const position =
453
+ operation.newPath[1] === targetBlock.children.length ? 'after' : 'before'
454
+ const childToInsert = (
455
+ fromSlateValue([block], schema.block.name)[0] as PortableTextTextBlock
456
+ ).children[operation.path[1]]
457
+ patches.push(unset([{_key: block._key}, 'children', {_key: child._key}]))
458
+ patches.push(
459
+ insert([childToInsert], position, [
460
+ {_key: targetBlock._key},
461
+ 'children',
462
+ {_key: targetChild._key},
463
+ ]),
464
+ )
465
+ }
466
+ return patches
467
+ }