@portabletext/editor 1.0.18 → 1.1.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 (75) hide show
  1. package/lib/index.d.mts +140 -66
  2. package/lib/index.d.ts +140 -66
  3. package/lib/index.esm.js +1164 -410
  4. package/lib/index.esm.js.map +1 -1
  5. package/lib/index.js +1164 -410
  6. package/lib/index.js.map +1 -1
  7. package/lib/index.mjs +1164 -410
  8. package/lib/index.mjs.map +1 -1
  9. package/package.json +8 -4
  10. package/src/editor/Editable.tsx +107 -36
  11. package/src/editor/PortableTextEditor.tsx +47 -12
  12. package/src/editor/__tests__/PortableTextEditor.test.tsx +42 -15
  13. package/src/editor/__tests__/PortableTextEditorTester.tsx +50 -38
  14. package/src/editor/__tests__/RangeDecorations.test.tsx +0 -1
  15. package/src/editor/__tests__/handleClick.test.tsx +28 -9
  16. package/src/editor/__tests__/insert-block.test.tsx +22 -6
  17. package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +30 -62
  18. package/src/editor/__tests__/utils.ts +10 -3
  19. package/src/editor/components/DraggableBlock.tsx +36 -13
  20. package/src/editor/components/Element.tsx +59 -17
  21. package/src/editor/components/Leaf.tsx +106 -68
  22. package/src/editor/components/SlateContainer.tsx +12 -5
  23. package/src/editor/components/Synchronizer.tsx +5 -2
  24. package/src/editor/hooks/usePortableTextEditor.ts +2 -2
  25. package/src/editor/hooks/usePortableTextEditorSelection.tsx +9 -3
  26. package/src/editor/hooks/useSyncValue.test.tsx +9 -4
  27. package/src/editor/hooks/useSyncValue.ts +199 -130
  28. package/src/editor/nodes/DefaultAnnotation.tsx +6 -3
  29. package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +25 -7
  30. package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +26 -9
  31. package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +15 -5
  32. package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +60 -19
  33. package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +4 -2
  34. package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +4 -2
  35. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +61 -550
  36. package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +6 -3
  37. package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +30 -13
  38. package/src/editor/plugins/createWithEditableAPI.ts +354 -115
  39. package/src/editor/plugins/createWithHotKeys.ts +41 -121
  40. package/src/editor/plugins/createWithInsertBreak.ts +166 -27
  41. package/src/editor/plugins/createWithInsertData.ts +60 -23
  42. package/src/editor/plugins/createWithMaxBlocks.ts +5 -2
  43. package/src/editor/plugins/createWithObjectKeys.ts +7 -3
  44. package/src/editor/plugins/createWithPatches.ts +60 -16
  45. package/src/editor/plugins/createWithPlaceholderBlock.ts +7 -3
  46. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +17 -7
  47. package/src/editor/plugins/createWithPortableTextLists.ts +21 -8
  48. package/src/editor/plugins/createWithPortableTextMarkModel.ts +301 -155
  49. package/src/editor/plugins/createWithPortableTextSelections.ts +4 -2
  50. package/src/editor/plugins/createWithSchemaTypes.ts +25 -9
  51. package/src/editor/plugins/createWithUndoRedo.ts +107 -24
  52. package/src/editor/plugins/createWithUtils.ts +32 -10
  53. package/src/editor/plugins/index.ts +31 -10
  54. package/src/types/editor.ts +44 -15
  55. package/src/types/options.ts +4 -2
  56. package/src/types/slate.ts +2 -2
  57. package/src/utils/__tests__/dmpToOperations.test.ts +38 -13
  58. package/src/utils/__tests__/operationToPatches.test.ts +3 -2
  59. package/src/utils/__tests__/patchToOperations.test.ts +15 -4
  60. package/src/utils/__tests__/ranges.test.ts +8 -3
  61. package/src/utils/__tests__/valueNormalization.test.tsx +12 -4
  62. package/src/utils/__tests__/values.test.ts +0 -1
  63. package/src/utils/applyPatch.ts +71 -20
  64. package/src/utils/getPortableTextMemberSchemaTypes.ts +30 -15
  65. package/src/utils/operationToPatches.ts +126 -43
  66. package/src/utils/paths.ts +24 -7
  67. package/src/utils/ranges.ts +12 -5
  68. package/src/utils/selection.ts +19 -7
  69. package/src/utils/validateValue.ts +118 -45
  70. package/src/utils/values.ts +31 -10
  71. package/src/utils/weakMaps.ts +18 -8
  72. package/src/utils/withChanges.ts +4 -2
  73. package/src/editor/plugins/__tests__/withHotkeys.test.tsx +0 -212
  74. package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +0 -220
  75. package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +0 -133
@@ -5,14 +5,23 @@ import {
5
5
  type PortableTextBlock,
6
6
  type PortableTextChild,
7
7
  type PortableTextObject,
8
+ type PortableTextSpan,
8
9
  type PortableTextTextBlock,
9
10
  type SchemaType,
10
11
  } from '@sanity/types'
11
- import {Editor, Element as SlateElement, Node, Range, Text, Transforms} from 'slate'
12
+ import {
13
+ Editor,
14
+ Node,
15
+ Range,
16
+ Element as SlateElement,
17
+ Path as SlatePath,
18
+ Text,
19
+ Transforms,
20
+ } from 'slate'
12
21
  import {ReactEditor} from 'slate-react'
13
22
  import {type DOMNode} from 'slate-react/dist/utils/dom'
14
-
15
23
  import {
24
+ type EditableAPI,
16
25
  type EditableAPIDeleteOptions,
17
26
  type EditorSelection,
18
27
  type PortableTextMemberSchemaTypes,
@@ -20,8 +29,15 @@ import {
20
29
  } from '../../types/editor'
21
30
  import {debugWithName} from '../../utils/debug'
22
31
  import {toPortableTextRange, toSlateRange} from '../../utils/ranges'
23
- import {fromSlateValue, isEqualToEmptyEditor, toSlateValue} from '../../utils/values'
24
- import {KEY_TO_VALUE_ELEMENT, SLATE_TO_PORTABLE_TEXT_RANGE} from '../../utils/weakMaps'
32
+ import {
33
+ fromSlateValue,
34
+ isEqualToEmptyEditor,
35
+ toSlateValue,
36
+ } from '../../utils/values'
37
+ import {
38
+ KEY_TO_VALUE_ELEMENT,
39
+ SLATE_TO_PORTABLE_TEXT_RANGE,
40
+ } from '../../utils/weakMaps'
25
41
  import {type PortableTextEditor} from '../PortableTextEditor'
26
42
 
27
43
  const debug = debugWithName('API:editable')
@@ -31,7 +47,9 @@ export function createWithEditableAPI(
31
47
  types: PortableTextMemberSchemaTypes,
32
48
  keyGenerator: () => string,
33
49
  ) {
34
- return function withEditableAPI(editor: PortableTextSlateEditor): PortableTextSlateEditor {
50
+ return function withEditableAPI(
51
+ editor: PortableTextSlateEditor,
52
+ ): PortableTextSlateEditor {
35
53
  portableTextEditor.setEditable({
36
54
  focus: (): void => {
37
55
  ReactEditor.focus(editor)
@@ -78,16 +96,26 @@ export function createWithEditableAPI(
78
96
  },
79
97
  focusBlock: (): PortableTextBlock | undefined => {
80
98
  if (editor.selection) {
81
- const block = Node.descendant(editor, editor.selection.focus.path.slice(0, 1))
99
+ const block = Node.descendant(
100
+ editor,
101
+ editor.selection.focus.path.slice(0, 1),
102
+ )
82
103
  if (block) {
83
- return fromSlateValue([block], types.block.name, KEY_TO_VALUE_ELEMENT.get(editor))[0]
104
+ return fromSlateValue(
105
+ [block],
106
+ types.block.name,
107
+ KEY_TO_VALUE_ELEMENT.get(editor),
108
+ )[0]
84
109
  }
85
110
  }
86
111
  return undefined
87
112
  },
88
113
  focusChild: (): PortableTextChild | undefined => {
89
114
  if (editor.selection) {
90
- const block = Node.descendant(editor, editor.selection.focus.path.slice(0, 1))
115
+ const block = Node.descendant(
116
+ editor,
117
+ editor.selection.focus.path.slice(0, 1),
118
+ )
91
119
  if (block && editor.isTextBlock(block)) {
92
120
  const ptBlock = fromSlateValue(
93
121
  [block],
@@ -116,7 +144,9 @@ export function createWithEditableAPI(
116
144
  type.name !== types.span.name &&
117
145
  !types.inlineObjects.some((t) => t.name === type.name)
118
146
  ) {
119
- throw new Error('This type cannot be inserted as a child to a text block')
147
+ throw new Error(
148
+ 'This type cannot be inserted as a child to a text block',
149
+ )
120
150
  }
121
151
  const block = toSlateValue(
122
152
  [
@@ -142,7 +172,9 @@ export function createWithEditableAPI(
142
172
  // If we are inserting a span, and currently have focus on an inline object,
143
173
  // move the selection to the next span (guaranteed by normalizing rules) before inserting it.
144
174
  if (isSpanNode && focusNode._type !== types.span.name) {
145
- debug('Inserting span child next to inline object child, moving selection + 1')
175
+ debug(
176
+ 'Inserting span child next to inline object child, moving selection + 1',
177
+ )
146
178
  editor.move({distance: 1, unit: 'character'})
147
179
  }
148
180
 
@@ -153,7 +185,11 @@ export function createWithEditableAPI(
153
185
  editor.onChange()
154
186
  return (
155
187
  toPortableTextRange(
156
- fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
188
+ fromSlateValue(
189
+ editor.children,
190
+ types.block.name,
191
+ KEY_TO_VALUE_ELEMENT.get(editor),
192
+ ),
157
193
  editor.selection,
158
194
  types,
159
195
  )?.focus.path || []
@@ -194,7 +230,11 @@ export function createWithEditableAPI(
194
230
 
195
231
  return (
196
232
  toPortableTextRange(
197
- fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
233
+ fromSlateValue(
234
+ editor.children,
235
+ types.block.name,
236
+ KEY_TO_VALUE_ELEMENT.get(editor),
237
+ ),
198
238
  editor.selection,
199
239
  types,
200
240
  )?.focus.path ?? []
@@ -218,7 +258,11 @@ export function createWithEditableAPI(
218
258
 
219
259
  return (
220
260
  toPortableTextRange(
221
- fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
261
+ fromSlateValue(
262
+ editor.children,
263
+ types.block.name,
264
+ KEY_TO_VALUE_ELEMENT.get(editor),
265
+ ),
222
266
  editor.selection,
223
267
  types,
224
268
  )?.focus.path || []
@@ -247,16 +291,25 @@ export function createWithEditableAPI(
247
291
  },
248
292
  findByPath: (
249
293
  path: Path,
250
- ): [PortableTextBlock | PortableTextChild | undefined, Path | undefined] => {
294
+ ): [
295
+ PortableTextBlock | PortableTextChild | undefined,
296
+ Path | undefined,
297
+ ] => {
251
298
  const slatePath = toSlateRange(
252
299
  {focus: {path, offset: 0}, anchor: {path, offset: 0}},
253
300
  editor,
254
301
  )
255
302
  if (slatePath) {
256
- const [block, blockPath] = Editor.node(editor, slatePath.focus.path.slice(0, 1))
303
+ const [block, blockPath] = Editor.node(
304
+ editor,
305
+ slatePath.focus.path.slice(0, 1),
306
+ )
257
307
  if (block && blockPath && typeof block._key === 'string') {
258
308
  if (path.length === 1 && slatePath.focus.path.length === 1) {
259
- return [fromSlateValue([block], types.block.name)[0], [{_key: block._key}]]
309
+ return [
310
+ fromSlateValue([block], types.block.name)[0],
311
+ [{_key: block._key}],
312
+ ]
260
313
  }
261
314
  const ptBlock = fromSlateValue(
262
315
  [block],
@@ -266,14 +319,19 @@ export function createWithEditableAPI(
266
319
  if (editor.isTextBlock(ptBlock)) {
267
320
  const ptChild = ptBlock.children[slatePath.focus.path[1]]
268
321
  if (ptChild) {
269
- return [ptChild, [{_key: block._key}, 'children', {_key: ptChild._key}]]
322
+ return [
323
+ ptChild,
324
+ [{_key: block._key}, 'children', {_key: ptChild._key}],
325
+ ]
270
326
  }
271
327
  }
272
328
  }
273
329
  }
274
330
  return [undefined, undefined]
275
331
  },
276
- findDOMNode: (element: PortableTextBlock | PortableTextChild): DOMNode | undefined => {
332
+ findDOMNode: (
333
+ element: PortableTextBlock | PortableTextChild,
334
+ ): DOMNode | undefined => {
277
335
  let node: DOMNode | undefined
278
336
  try {
279
337
  const [item] = Array.from(
@@ -322,7 +380,9 @@ export function createWithEditableAPI(
322
380
  return []
323
381
  }
324
382
  },
325
- isAnnotationActive: (annotationType: PortableTextObject['_type']): boolean => {
383
+ isAnnotationActive: (
384
+ annotationType: PortableTextObject['_type'],
385
+ ): boolean => {
326
386
  if (!editor.selection || editor.selection.focus.path.length < 2) {
327
387
  return false
328
388
  }
@@ -341,7 +401,10 @@ export function createWithEditableAPI(
341
401
 
342
402
  if (
343
403
  spans.some(
344
- ([span]) => !isPortableTextSpan(span) || !span.marks || span.marks?.length === 0,
404
+ ([span]) =>
405
+ !isPortableTextSpan(span) ||
406
+ !span.marks ||
407
+ span.marks?.length === 0,
345
408
  )
346
409
  )
347
410
  return false
@@ -358,7 +421,8 @@ export function createWithEditableAPI(
358
421
  if (!isPortableTextSpan(span)) return false
359
422
 
360
423
  const spanMarkDefs = span.marks?.map(
361
- (markKey) => selectionMarkDefs.find((def) => def?._key === markKey)?._type,
424
+ (markKey) =>
425
+ selectionMarkDefs.find((def) => def?._key === markKey)?._type,
362
426
  )
363
427
 
364
428
  return spanMarkDefs?.includes(annotationType)
@@ -367,81 +431,144 @@ export function createWithEditableAPI(
367
431
  return false
368
432
  }
369
433
  },
370
- addAnnotation: (
371
- type: ObjectSchemaType,
372
- value?: {[prop: string]: unknown},
373
- ): {spanPath: Path; markDefPath: Path} | undefined => {
434
+ addAnnotation: (type, value) => {
374
435
  const {selection: originalSelection} = editor
375
- let returnValue: {spanPath: Path; markDefPath: Path} | undefined = undefined
436
+ let returnValue: ReturnType<EditableAPI['addAnnotation']> | undefined =
437
+ undefined
438
+
376
439
  if (originalSelection) {
377
- const [block] = Editor.node(editor, originalSelection.focus, {depth: 1})
378
- if (!editor.isTextBlock(block)) {
379
- return undefined
380
- }
381
440
  if (Range.isCollapsed(originalSelection)) {
382
441
  editor.pteExpandToWord()
383
442
  editor.onChange()
384
443
  }
385
- const [textNode] = Editor.node(editor, originalSelection.focus, {depth: 2})
386
444
 
387
445
  // If we still have a selection, add the annotation to the selected text
388
446
  if (editor.selection) {
447
+ let spanPath: Path | undefined
448
+ let markDefPath: Path | undefined
449
+ const markDefPaths: Path[] = []
450
+
389
451
  Editor.withoutNormalizing(editor, () => {
390
- // Add markDefs to the block
391
- const annotationKey = keyGenerator()
392
- Transforms.setNodes(
393
- editor,
394
- {
395
- markDefs: [
396
- ...(block.markDefs || []),
397
- {_type: type.name, _key: annotationKey, ...value} as PortableTextObject,
398
- ],
399
- },
400
- {at: originalSelection.focus},
401
- )
402
- editor.onChange()
452
+ if (!editor.selection) {
453
+ return
454
+ }
403
455
 
404
- // Split if needed
405
- Transforms.setNodes(editor, {}, {match: Text.isText, split: true})
406
- editor.onChange()
456
+ const selectedBlocks = Editor.nodes(editor, {
457
+ at: editor.selection,
458
+ match: (node) => editor.isTextBlock(node),
459
+ reverse: Range.isBackward(editor.selection),
460
+ })
461
+
462
+ for (const [block, blockPath] of selectedBlocks) {
463
+ if (block.children.length === 0) {
464
+ continue
465
+ }
466
+
467
+ if (
468
+ block.children.length === 1 &&
469
+ block.children[0].text === ''
470
+ ) {
471
+ continue
472
+ }
473
+
474
+ const annotationKey = keyGenerator()
475
+ const markDefs = block.markDefs ?? []
476
+ const existingMarkDef = markDefs.find(
477
+ (markDef) =>
478
+ markDef._type === type.name &&
479
+ markDef._key === annotationKey,
480
+ )
481
+
482
+ if (existingMarkDef === undefined) {
483
+ Transforms.setNodes(
484
+ editor,
485
+ {
486
+ markDefs: [
487
+ ...markDefs,
488
+ {
489
+ _type: type.name,
490
+ _key: annotationKey,
491
+ ...value,
492
+ },
493
+ ],
494
+ },
495
+ {at: blockPath},
496
+ )
497
+
498
+ markDefPath = [
499
+ {_key: block._key},
500
+ 'markDefs',
501
+ {_key: annotationKey},
502
+ ]
503
+ if (Range.isBackward(editor.selection)) {
504
+ markDefPaths.unshift(markDefPath)
505
+ } else {
506
+ markDefPaths.push(markDefPath)
507
+ }
508
+ }
407
509
 
408
- // Add marks to the span node
409
- if (editor.selection && Text.isText(textNode)) {
410
510
  Transforms.setNodes(
411
511
  editor,
412
- {
413
- marks: [...((textNode.marks || []) as string[]), annotationKey],
414
- },
415
- {
416
- at: editor.selection,
417
- match: (n) => n._type === types.span.name,
418
- },
512
+ {},
513
+ {match: Text.isText, split: true},
419
514
  )
515
+
516
+ const children = Node.children(editor, blockPath)
517
+
518
+ for (const [span, path] of children) {
519
+ if (!editor.isTextSpan(span)) {
520
+ continue
521
+ }
522
+
523
+ if (!Range.includes(editor.selection, path)) {
524
+ continue
525
+ }
526
+
527
+ const marks = span.marks ?? []
528
+ const existingSameTypeAnnotations = marks.filter((mark) =>
529
+ markDefs.some(
530
+ (markDef) =>
531
+ markDef._key === mark && markDef._type === type.name,
532
+ ),
533
+ )
534
+
535
+ Transforms.setNodes(
536
+ editor,
537
+ {
538
+ marks: [
539
+ ...marks.filter(
540
+ (mark) => !existingSameTypeAnnotations.includes(mark),
541
+ ),
542
+ annotationKey,
543
+ ],
544
+ },
545
+ {at: path},
546
+ )
547
+ spanPath = [{_key: block._key}, 'children', {_key: span._key}]
548
+ }
420
549
  }
421
- editor.onChange()
422
550
 
423
- const newPortableTextEditorSelection = toPortableTextRange(
424
- fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
425
- editor.selection,
426
- types,
427
- )
428
- if (newPortableTextEditorSelection) {
551
+ if (markDefPath && spanPath) {
429
552
  returnValue = {
430
- spanPath: newPortableTextEditorSelection.focus.path,
431
- markDefPath: [{_key: block._key}, 'markDefs', {_key: annotationKey}],
553
+ markDefPath,
554
+ markDefPaths,
555
+ spanPath,
432
556
  }
433
557
  }
434
558
  })
435
- Editor.normalize(editor)
436
559
  editor.onChange()
437
560
  }
438
561
  }
439
562
  return returnValue
440
563
  },
441
- delete: (selection: EditorSelection, options?: EditableAPIDeleteOptions): void => {
564
+ delete: (
565
+ selection: EditorSelection,
566
+ options?: EditableAPIDeleteOptions,
567
+ ): void => {
442
568
  if (selection) {
443
569
  const range = toSlateRange(selection, editor)
444
- const hasRange = range && range.anchor.path.length > 0 && range.focus.path.length > 0
570
+ const hasRange =
571
+ range && range.anchor.path.length > 0 && range.focus.path.length > 0
445
572
  if (!hasRange) {
446
573
  throw new Error('Invalid range')
447
574
  }
@@ -488,61 +615,162 @@ export function createWithEditableAPI(
488
615
  // that would insert the placeholder into the actual value
489
616
  // which should remain empty)
490
617
  if (editor.children.length === 0) {
491
- editor.children = [editor.pteCreateEmptyBlock()]
618
+ editor.children = [editor.pteCreateTextBlock({decorators: []})]
492
619
  }
493
620
  editor.onChange()
494
621
  }
495
622
  }
496
623
  },
497
624
  removeAnnotation: (type: ObjectSchemaType): void => {
498
- let {selection} = editor
499
625
  debug('Removing annotation', type)
500
- if (selection) {
501
- // Select the whole annotation if collapsed
502
- if (Range.isCollapsed(selection)) {
503
- const [node, nodePath] = Editor.node(editor, selection, {depth: 2})
504
- if (Text.isText(node) && node.marks && typeof node.text === 'string') {
505
- Transforms.select(editor, nodePath)
506
- selection = editor.selection
507
- }
626
+
627
+ Editor.withoutNormalizing(editor, () => {
628
+ if (!editor.selection) {
629
+ return
508
630
  }
509
- // Do this without normalization or span references will be unstable!
510
- Editor.withoutNormalizing(editor, () => {
511
- if (selection && Range.isExpanded(selection)) {
512
- selection = editor.selection
513
- if (!selection) {
514
- return
631
+
632
+ if (Range.isCollapsed(editor.selection)) {
633
+ const [block, blockPath] = Editor.node(editor, editor.selection, {
634
+ depth: 1,
635
+ })
636
+
637
+ if (!editor.isTextBlock(block)) {
638
+ return
639
+ }
640
+
641
+ const markDefs = block.markDefs ?? []
642
+ const potentialAnnotations = markDefs.filter(
643
+ (markDef) => markDef._type === type.name,
644
+ )
645
+
646
+ const [selectedChild, selectedChildPath] = Editor.node(
647
+ editor,
648
+ editor.selection,
649
+ {
650
+ depth: 2,
651
+ },
652
+ )
653
+
654
+ if (!editor.isTextSpan(selectedChild)) {
655
+ return
656
+ }
657
+
658
+ const annotationToRemove = selectedChild.marks?.find((mark) =>
659
+ potentialAnnotations.some((markDef) => markDef._key === mark),
660
+ )
661
+
662
+ if (!annotationToRemove) {
663
+ return
664
+ }
665
+
666
+ const previousSpansWithSameAnnotation: Array<
667
+ [span: PortableTextSpan, path: SlatePath]
668
+ > = []
669
+
670
+ for (const [child, childPath] of Node.children(editor, blockPath, {
671
+ reverse: true,
672
+ })) {
673
+ if (!editor.isTextSpan(child)) {
674
+ continue
515
675
  }
516
- // Find the selected block, to identify the annotation to remove
517
- const blocks = [
518
- ...Editor.nodes(editor, {
519
- at: selection,
520
- match: (node) => {
521
- return (
522
- editor.isTextBlock(node) &&
523
- Array.isArray(node.markDefs) &&
524
- node.markDefs.some((def) => def._type === type.name)
525
- )
526
- },
527
- }),
528
- ]
529
- const removedMarks: string[] = []
530
-
531
- // Removes the marks from the text nodes
532
- blocks.forEach(([block]) => {
533
- if (editor.isTextBlock(block) && Array.isArray(block.markDefs)) {
534
- const marksToRemove = block.markDefs.filter((def) => def._type === type.name)
535
- marksToRemove.forEach((def) => {
536
- if (!removedMarks.includes(def._key)) removedMarks.push(def._key)
537
- Editor.removeMark(editor, def._key)
538
- })
676
+
677
+ if (!SlatePath.isBefore(childPath, selectedChildPath)) {
678
+ continue
679
+ }
680
+
681
+ if (child.marks?.includes(annotationToRemove)) {
682
+ previousSpansWithSameAnnotation.push([child, childPath])
683
+ } else {
684
+ break
685
+ }
686
+ }
687
+
688
+ const nextSpansWithSameAnnotation: Array<
689
+ [span: PortableTextSpan, path: SlatePath]
690
+ > = []
691
+
692
+ for (const [child, childPath] of Node.children(editor, blockPath)) {
693
+ if (!editor.isTextSpan(child)) {
694
+ continue
695
+ }
696
+
697
+ if (!SlatePath.isAfter(childPath, selectedChildPath)) {
698
+ continue
699
+ }
700
+
701
+ if (child.marks?.includes(annotationToRemove)) {
702
+ nextSpansWithSameAnnotation.push([child, childPath])
703
+ } else {
704
+ break
705
+ }
706
+ }
707
+
708
+ for (const [child, childPath] of [
709
+ ...previousSpansWithSameAnnotation,
710
+ [selectedChild, selectedChildPath] as const,
711
+ ...nextSpansWithSameAnnotation,
712
+ ]) {
713
+ Transforms.setNodes(
714
+ editor,
715
+ {
716
+ marks: child.marks?.filter(
717
+ (mark) => mark !== annotationToRemove,
718
+ ),
719
+ },
720
+ {at: childPath},
721
+ )
722
+ }
723
+ } else {
724
+ Transforms.setNodes(
725
+ editor,
726
+ {},
727
+ {
728
+ match: (node) => editor.isTextSpan(node),
729
+ split: true,
730
+ hanging: true,
731
+ },
732
+ )
733
+
734
+ const blocks = Editor.nodes(editor, {
735
+ at: editor.selection,
736
+ match: (node) => editor.isTextBlock(node),
737
+ })
738
+
739
+ for (const [block, blockPath] of blocks) {
740
+ const children = Node.children(editor, blockPath)
741
+
742
+ for (const [child, childPath] of children) {
743
+ if (!editor.isTextSpan(child)) {
744
+ continue
539
745
  }
540
- })
746
+
747
+ if (!Range.includes(editor.selection, childPath)) {
748
+ continue
749
+ }
750
+
751
+ const markDefs = block.markDefs ?? []
752
+ const marks = child.marks ?? []
753
+ const marksWithoutAnnotation = marks.filter((mark) => {
754
+ const markDef = markDefs.find(
755
+ (markDef) => markDef._key === mark,
756
+ )
757
+ return markDef?._type !== type.name
758
+ })
759
+
760
+ if (marksWithoutAnnotation.length !== marks.length) {
761
+ Transforms.setNodes(
762
+ editor,
763
+ {
764
+ marks: marksWithoutAnnotation,
765
+ },
766
+ {at: childPath},
767
+ )
768
+ }
769
+ }
541
770
  }
542
- })
543
- Editor.normalize(editor)
544
- editor.onChange()
545
- }
771
+ }
772
+ })
773
+ editor.onChange()
546
774
  },
547
775
  getSelection: (): EditorSelection | null => {
548
776
  let ptRange: EditorSelection = null
@@ -552,7 +780,11 @@ export function createWithEditableAPI(
552
780
  return existing
553
781
  }
554
782
  ptRange = toPortableTextRange(
555
- fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
783
+ fromSlateValue(
784
+ editor.children,
785
+ types.block.name,
786
+ KEY_TO_VALUE_ELEMENT.get(editor),
787
+ ),
556
788
  editor.selection,
557
789
  types,
558
790
  )
@@ -561,7 +793,11 @@ export function createWithEditableAPI(
561
793
  return ptRange
562
794
  },
563
795
  getValue: () => {
564
- return fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor))
796
+ return fromSlateValue(
797
+ editor.children,
798
+ types.block.name,
799
+ KEY_TO_VALUE_ELEMENT.get(editor),
800
+ )
565
801
  },
566
802
  isCollapsedSelection: () => {
567
803
  return !!editor.selection && Range.isCollapsed(editor.selection)
@@ -576,7 +812,10 @@ export function createWithEditableAPI(
576
812
  getFragment: () => {
577
813
  return fromSlateValue(editor.getFragment(), types.block.name)
578
814
  },
579
- isSelectionsOverlapping: (selectionA: EditorSelection, selectionB: EditorSelection) => {
815
+ isSelectionsOverlapping: (
816
+ selectionA: EditorSelection,
817
+ selectionB: EditorSelection,
818
+ ) => {
580
819
  // Convert the selections to Slate ranges
581
820
  const rangeA = toSlateRange(selectionA, editor)
582
821
  const rangeB = toSlateRange(selectionB, editor)