@portabletext/editor 1.0.19 → 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 (72) hide show
  1. package/lib/index.d.mts +140 -66
  2. package/lib/index.d.ts +140 -66
  3. package/lib/index.esm.js +1125 -362
  4. package/lib/index.esm.js.map +1 -1
  5. package/lib/index.js +1125 -362
  6. package/lib/index.js.map +1 -1
  7. package/lib/index.mjs +1125 -362
  8. package/lib/index.mjs.map +1 -1
  9. package/package.json +2 -2
  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 -17
  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 -124
  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 +213 -46
  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 -9
  71. package/src/utils/weakMaps.ts +18 -8
  72. package/src/utils/withChanges.ts +4 -2
@@ -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,27 +431,12 @@ 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
376
- if (originalSelection) {
377
- const [block] = Editor.node(editor, originalSelection.focus, {depth: 1})
378
- if (!editor.isTextBlock(block)) {
379
- return undefined
380
- }
381
- const [textNode] = Editor.node(editor, originalSelection.focus, {depth: 2})
382
-
383
- if (!isPortableTextSpan(textNode)) {
384
- return undefined
385
- }
386
-
387
- if (textNode.text === '') {
388
- return undefined
389
- }
436
+ let returnValue: ReturnType<EditableAPI['addAnnotation']> | undefined =
437
+ undefined
390
438
 
439
+ if (originalSelection) {
391
440
  if (Range.isCollapsed(originalSelection)) {
392
441
  editor.pteExpandToWord()
393
442
  editor.onChange()
@@ -395,62 +444,131 @@ export function createWithEditableAPI(
395
444
 
396
445
  // If we still have a selection, add the annotation to the selected text
397
446
  if (editor.selection) {
447
+ let spanPath: Path | undefined
448
+ let markDefPath: Path | undefined
449
+ const markDefPaths: Path[] = []
450
+
398
451
  Editor.withoutNormalizing(editor, () => {
399
- // Add markDefs to the block
400
- const annotationKey = keyGenerator()
401
- Transforms.setNodes(
402
- editor,
403
- {
404
- markDefs: [
405
- ...(block.markDefs || []),
406
- {_type: type.name, _key: annotationKey, ...value} as PortableTextObject,
407
- ],
408
- },
409
- {at: originalSelection.focus},
410
- )
411
- editor.onChange()
452
+ if (!editor.selection) {
453
+ return
454
+ }
412
455
 
413
- // Split if needed
414
- Transforms.setNodes(editor, {}, {match: Text.isText, split: true})
415
- 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
+ }
416
509
 
417
- // Add marks to the span node
418
- if (editor.selection && Text.isText(textNode)) {
419
510
  Transforms.setNodes(
420
511
  editor,
421
- {
422
- marks: [...((textNode.marks || []) as string[]), annotationKey],
423
- },
424
- {
425
- at: editor.selection,
426
- match: (n) => n._type === types.span.name,
427
- },
512
+ {},
513
+ {match: Text.isText, split: true},
428
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
+ }
429
549
  }
430
- editor.onChange()
431
550
 
432
- const newPortableTextEditorSelection = toPortableTextRange(
433
- fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
434
- editor.selection,
435
- types,
436
- )
437
- if (newPortableTextEditorSelection) {
551
+ if (markDefPath && spanPath) {
438
552
  returnValue = {
439
- spanPath: newPortableTextEditorSelection.focus.path,
440
- markDefPath: [{_key: block._key}, 'markDefs', {_key: annotationKey}],
553
+ markDefPath,
554
+ markDefPaths,
555
+ spanPath,
441
556
  }
442
557
  }
443
558
  })
444
- Editor.normalize(editor)
445
559
  editor.onChange()
446
560
  }
447
561
  }
448
562
  return returnValue
449
563
  },
450
- delete: (selection: EditorSelection, options?: EditableAPIDeleteOptions): void => {
564
+ delete: (
565
+ selection: EditorSelection,
566
+ options?: EditableAPIDeleteOptions,
567
+ ): void => {
451
568
  if (selection) {
452
569
  const range = toSlateRange(selection, editor)
453
- 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
454
572
  if (!hasRange) {
455
573
  throw new Error('Invalid range')
456
574
  }
@@ -497,61 +615,162 @@ export function createWithEditableAPI(
497
615
  // that would insert the placeholder into the actual value
498
616
  // which should remain empty)
499
617
  if (editor.children.length === 0) {
500
- editor.children = [editor.pteCreateEmptyBlock()]
618
+ editor.children = [editor.pteCreateTextBlock({decorators: []})]
501
619
  }
502
620
  editor.onChange()
503
621
  }
504
622
  }
505
623
  },
506
624
  removeAnnotation: (type: ObjectSchemaType): void => {
507
- let {selection} = editor
508
625
  debug('Removing annotation', type)
509
- if (selection) {
510
- // Select the whole annotation if collapsed
511
- if (Range.isCollapsed(selection)) {
512
- const [node, nodePath] = Editor.node(editor, selection, {depth: 2})
513
- if (Text.isText(node) && node.marks && typeof node.text === 'string') {
514
- Transforms.select(editor, nodePath)
515
- selection = editor.selection
516
- }
626
+
627
+ Editor.withoutNormalizing(editor, () => {
628
+ if (!editor.selection) {
629
+ return
517
630
  }
518
- // Do this without normalization or span references will be unstable!
519
- Editor.withoutNormalizing(editor, () => {
520
- if (selection && Range.isExpanded(selection)) {
521
- selection = editor.selection
522
- if (!selection) {
523
- 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
524
675
  }
525
- // Find the selected block, to identify the annotation to remove
526
- const blocks = [
527
- ...Editor.nodes(editor, {
528
- at: selection,
529
- match: (node) => {
530
- return (
531
- editor.isTextBlock(node) &&
532
- Array.isArray(node.markDefs) &&
533
- node.markDefs.some((def) => def._type === type.name)
534
- )
535
- },
536
- }),
537
- ]
538
- const removedMarks: string[] = []
539
-
540
- // Removes the marks from the text nodes
541
- blocks.forEach(([block]) => {
542
- if (editor.isTextBlock(block) && Array.isArray(block.markDefs)) {
543
- const marksToRemove = block.markDefs.filter((def) => def._type === type.name)
544
- marksToRemove.forEach((def) => {
545
- if (!removedMarks.includes(def._key)) removedMarks.push(def._key)
546
- Editor.removeMark(editor, def._key)
547
- })
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
548
745
  }
549
- })
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
+ }
550
770
  }
551
- })
552
- Editor.normalize(editor)
553
- editor.onChange()
554
- }
771
+ }
772
+ })
773
+ editor.onChange()
555
774
  },
556
775
  getSelection: (): EditorSelection | null => {
557
776
  let ptRange: EditorSelection = null
@@ -561,7 +780,11 @@ export function createWithEditableAPI(
561
780
  return existing
562
781
  }
563
782
  ptRange = toPortableTextRange(
564
- 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
+ ),
565
788
  editor.selection,
566
789
  types,
567
790
  )
@@ -570,7 +793,11 @@ export function createWithEditableAPI(
570
793
  return ptRange
571
794
  },
572
795
  getValue: () => {
573
- 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
+ )
574
801
  },
575
802
  isCollapsedSelection: () => {
576
803
  return !!editor.selection && Range.isCollapsed(editor.selection)
@@ -585,7 +812,10 @@ export function createWithEditableAPI(
585
812
  getFragment: () => {
586
813
  return fromSlateValue(editor.getFragment(), types.block.name)
587
814
  },
588
- isSelectionsOverlapping: (selectionA: EditorSelection, selectionB: EditorSelection) => {
815
+ isSelectionsOverlapping: (
816
+ selectionA: EditorSelection,
817
+ selectionB: EditorSelection,
818
+ ) => {
589
819
  // Convert the selections to Slate ranges
590
820
  const rangeA = toSlateRange(selectionA, editor)
591
821
  const rangeB = toSlateRange(selectionB, editor)