@portabletext/editor 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.esm.js +208 -216
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +204 -212
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +208 -216
- package/lib/index.mjs.map +1 -1
- package/package.json +19 -17
- package/src/editor/__tests__/PortableTextEditor.test.tsx +15 -1
- package/src/editor/__tests__/handleClick.test.tsx +40 -20
- package/src/editor/__tests__/utils.ts +14 -15
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +62 -20
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +19 -0
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +259 -134
- package/src/editor/plugins/__tests__/withHotkeys.test.tsx +1 -1
- package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +18 -2
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +176 -154
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +17 -0
- package/src/editor/plugins/createWithObjectKeys.ts +23 -8
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +68 -14
- package/src/editor/plugins/createWithUndoRedo.ts +5 -7
- package/src/editor/plugins/index.ts +5 -1
- package/src/utils/__tests__/values.test.ts +1 -0
- package/src/utils/operationToPatches.ts +0 -1
- package/src/utils/withPreserveKeys.ts +7 -0
|
@@ -79,15 +79,21 @@ describe('plugin:withPortableTextMarksModel', () => {
|
|
|
79
79
|
]
|
|
80
80
|
|
|
81
81
|
const onChange = jest.fn()
|
|
82
|
+
|
|
83
|
+
render(
|
|
84
|
+
<PortableTextEditorTester
|
|
85
|
+
onChange={onChange}
|
|
86
|
+
ref={editorRef}
|
|
87
|
+
schemaType={schemaType}
|
|
88
|
+
value={initialValue}
|
|
89
|
+
/>,
|
|
90
|
+
)
|
|
91
|
+
|
|
82
92
|
await waitFor(() => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
schemaType={schemaType}
|
|
88
|
-
value={initialValue}
|
|
89
|
-
/>,
|
|
90
|
-
)
|
|
93
|
+
if (editorRef.current) {
|
|
94
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
95
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
96
|
+
}
|
|
91
97
|
})
|
|
92
98
|
|
|
93
99
|
await waitFor(() => {
|
|
@@ -382,6 +388,7 @@ Array [
|
|
|
382
388
|
}
|
|
383
389
|
})
|
|
384
390
|
})
|
|
391
|
+
|
|
385
392
|
it('toggles marks on children with annotation marks correctly', async () => {
|
|
386
393
|
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
387
394
|
const initialValue = [
|
|
@@ -413,61 +420,65 @@ Array [
|
|
|
413
420
|
},
|
|
414
421
|
]
|
|
415
422
|
const onChange = jest.fn()
|
|
423
|
+
|
|
424
|
+
render(
|
|
425
|
+
<PortableTextEditorTester
|
|
426
|
+
onChange={onChange}
|
|
427
|
+
ref={editorRef}
|
|
428
|
+
schemaType={schemaType}
|
|
429
|
+
value={initialValue}
|
|
430
|
+
/>,
|
|
431
|
+
)
|
|
432
|
+
|
|
416
433
|
await waitFor(() => {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
schemaType={schemaType}
|
|
422
|
-
value={initialValue}
|
|
423
|
-
/>,
|
|
424
|
-
)
|
|
434
|
+
if (editorRef.current) {
|
|
435
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
436
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
437
|
+
}
|
|
425
438
|
})
|
|
426
|
-
const editor = editorRef.current!
|
|
427
|
-
expect(editor).toBeDefined()
|
|
428
439
|
|
|
429
440
|
await waitFor(() => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
441
|
+
if (editorRef.current) {
|
|
442
|
+
PortableTextEditor.focus(editorRef.current)
|
|
443
|
+
PortableTextEditor.select(editorRef.current, {
|
|
444
|
+
focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 0},
|
|
445
|
+
anchor: {path: [{_key: 'a'}, 'children', {_key: 'a2'}], offset: 12},
|
|
446
|
+
})
|
|
447
|
+
PortableTextEditor.toggleMark(editorRef.current, 'strong')
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
await waitFor(() => {
|
|
452
|
+
if (editorRef.current) {
|
|
453
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
454
|
+
{
|
|
455
|
+
_key: 'a',
|
|
456
|
+
_type: 'myTestBlockType',
|
|
457
|
+
children: [
|
|
458
|
+
{
|
|
459
|
+
_key: 'a1',
|
|
460
|
+
_type: 'span',
|
|
461
|
+
marks: ['abc', 'strong'],
|
|
462
|
+
text: 'A link',
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
_key: 'a2',
|
|
466
|
+
_type: 'span',
|
|
467
|
+
marks: ['strong'],
|
|
468
|
+
text: ', not a link',
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
markDefs: [
|
|
472
|
+
{
|
|
473
|
+
_type: 'link',
|
|
474
|
+
_key: 'abc',
|
|
475
|
+
href: 'http://www.link.com',
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
style: 'normal',
|
|
479
|
+
},
|
|
480
|
+
])
|
|
481
|
+
}
|
|
471
482
|
})
|
|
472
483
|
})
|
|
473
484
|
|
|
@@ -605,11 +616,11 @@ Array [
|
|
|
605
616
|
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
606
617
|
const initialValue = [
|
|
607
618
|
{
|
|
608
|
-
_key: '
|
|
619
|
+
_key: 'ba',
|
|
609
620
|
_type: 'myTestBlockType',
|
|
610
621
|
children: [
|
|
611
622
|
{
|
|
612
|
-
_key: '
|
|
623
|
+
_key: 'sa',
|
|
613
624
|
_type: 'span',
|
|
614
625
|
marks: [],
|
|
615
626
|
text: '1',
|
|
@@ -619,19 +630,19 @@ Array [
|
|
|
619
630
|
style: 'normal',
|
|
620
631
|
},
|
|
621
632
|
{
|
|
622
|
-
_key: '
|
|
633
|
+
_key: 'bb',
|
|
623
634
|
_type: 'myTestBlockType',
|
|
624
635
|
children: [
|
|
625
636
|
{
|
|
626
|
-
_key: '
|
|
637
|
+
_key: 'sb',
|
|
627
638
|
_type: 'span',
|
|
628
|
-
marks: ['
|
|
639
|
+
marks: ['aa'],
|
|
629
640
|
text: '2',
|
|
630
641
|
},
|
|
631
642
|
],
|
|
632
643
|
markDefs: [
|
|
633
644
|
{
|
|
634
|
-
_key: '
|
|
645
|
+
_key: 'aa',
|
|
635
646
|
_type: 'link',
|
|
636
647
|
href: 'http://www.123.com',
|
|
637
648
|
},
|
|
@@ -640,82 +651,87 @@ Array [
|
|
|
640
651
|
},
|
|
641
652
|
]
|
|
642
653
|
const sel: EditorSelection = {
|
|
643
|
-
focus: {path: [{_key: '
|
|
644
|
-
anchor: {path: [{_key: '
|
|
654
|
+
focus: {path: [{_key: 'bb'}, 'children', {_key: 'sb'}], offset: 0},
|
|
655
|
+
anchor: {path: [{_key: 'bb'}, 'children', {_key: 'sb'}], offset: 0},
|
|
645
656
|
}
|
|
646
657
|
const onChange = jest.fn()
|
|
658
|
+
|
|
659
|
+
render(
|
|
660
|
+
<PortableTextEditorTester
|
|
661
|
+
onChange={onChange}
|
|
662
|
+
ref={editorRef}
|
|
663
|
+
schemaType={schemaType}
|
|
664
|
+
value={initialValue}
|
|
665
|
+
/>,
|
|
666
|
+
)
|
|
667
|
+
|
|
647
668
|
await waitFor(() => {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
schemaType={schemaType}
|
|
653
|
-
value={initialValue}
|
|
654
|
-
/>,
|
|
655
|
-
)
|
|
669
|
+
if (editorRef.current) {
|
|
670
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
671
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
672
|
+
}
|
|
656
673
|
})
|
|
657
674
|
|
|
658
|
-
|
|
659
|
-
|
|
675
|
+
await waitFor(() => {
|
|
676
|
+
if (editorRef.current) {
|
|
677
|
+
PortableTextEditor.select(editorRef.current, sel)
|
|
678
|
+
PortableTextEditor.insertBreak(editorRef.current)
|
|
679
|
+
}
|
|
680
|
+
})
|
|
660
681
|
|
|
661
682
|
await waitFor(() => {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
],
|
|
715
|
-
"style": "normal",
|
|
716
|
-
},
|
|
717
|
-
]
|
|
718
|
-
`)
|
|
683
|
+
if (editorRef.current) {
|
|
684
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
685
|
+
{
|
|
686
|
+
_key: 'ba',
|
|
687
|
+
_type: 'myTestBlockType',
|
|
688
|
+
children: [
|
|
689
|
+
{
|
|
690
|
+
_key: 'sa',
|
|
691
|
+
_type: 'span',
|
|
692
|
+
marks: [],
|
|
693
|
+
text: '1',
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
markDefs: [],
|
|
697
|
+
style: 'normal',
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
_key: '3',
|
|
701
|
+
_type: 'myTestBlockType',
|
|
702
|
+
children: [
|
|
703
|
+
{
|
|
704
|
+
_key: '2',
|
|
705
|
+
_type: 'span',
|
|
706
|
+
marks: [],
|
|
707
|
+
text: '',
|
|
708
|
+
},
|
|
709
|
+
],
|
|
710
|
+
markDefs: [],
|
|
711
|
+
style: 'normal',
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
_key: 'bb',
|
|
715
|
+
_type: 'myTestBlockType',
|
|
716
|
+
children: [
|
|
717
|
+
{
|
|
718
|
+
_key: 'sb',
|
|
719
|
+
_type: 'span',
|
|
720
|
+
marks: ['aa'],
|
|
721
|
+
text: '2',
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
markDefs: [
|
|
725
|
+
{
|
|
726
|
+
_key: 'aa',
|
|
727
|
+
_type: 'link',
|
|
728
|
+
href: 'http://www.123.com',
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
style: 'normal',
|
|
732
|
+
},
|
|
733
|
+
])
|
|
734
|
+
}
|
|
719
735
|
})
|
|
720
736
|
})
|
|
721
737
|
})
|
|
@@ -724,11 +740,11 @@ Array [
|
|
|
724
740
|
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
725
741
|
const initialValue = [
|
|
726
742
|
{
|
|
727
|
-
_key: '
|
|
743
|
+
_key: 'ba',
|
|
728
744
|
_type: 'myTestBlockType',
|
|
729
745
|
children: [
|
|
730
746
|
{
|
|
731
|
-
_key: '
|
|
747
|
+
_key: 'sa',
|
|
732
748
|
_type: 'span',
|
|
733
749
|
marks: [],
|
|
734
750
|
text: '',
|
|
@@ -740,32 +756,38 @@ Array [
|
|
|
740
756
|
]
|
|
741
757
|
const onChange = jest.fn()
|
|
742
758
|
|
|
759
|
+
render(
|
|
760
|
+
<PortableTextEditorTester
|
|
761
|
+
onChange={onChange}
|
|
762
|
+
ref={editorRef}
|
|
763
|
+
schemaType={schemaType}
|
|
764
|
+
value={initialValue}
|
|
765
|
+
/>,
|
|
766
|
+
)
|
|
767
|
+
|
|
743
768
|
await waitFor(() => {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
schemaType={schemaType}
|
|
749
|
-
value={initialValue}
|
|
750
|
-
/>,
|
|
751
|
-
)
|
|
769
|
+
if (editorRef.current) {
|
|
770
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
771
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
772
|
+
}
|
|
752
773
|
})
|
|
753
774
|
|
|
754
|
-
const editor = editorRef.current!
|
|
755
|
-
expect(editor).toBeDefined()
|
|
756
|
-
|
|
757
775
|
await waitFor(() => {
|
|
758
|
-
|
|
776
|
+
if (editorRef.current) {
|
|
777
|
+
PortableTextEditor.focus(editorRef.current)
|
|
778
|
+
}
|
|
759
779
|
})
|
|
760
|
-
const currentSelectionObject = PortableTextEditor.getSelection(editor)
|
|
761
780
|
|
|
762
781
|
await waitFor(() => {
|
|
763
|
-
|
|
782
|
+
if (editorRef.current) {
|
|
783
|
+
const currentSelectionObject = PortableTextEditor.getSelection(editorRef.current)
|
|
784
|
+
PortableTextEditor.toggleMark(editorRef.current, 'strong')
|
|
785
|
+
const nextSelectionObject = PortableTextEditor.getSelection(editorRef.current)
|
|
786
|
+
expect(currentSelectionObject).toEqual(nextSelectionObject)
|
|
787
|
+
expect(currentSelectionObject === nextSelectionObject).toBe(false)
|
|
788
|
+
expect(onChange).toHaveBeenCalledWith({type: 'selection', selection: nextSelectionObject})
|
|
789
|
+
}
|
|
764
790
|
})
|
|
765
|
-
const nextSelectionObject = PortableTextEditor.getSelection(editor)
|
|
766
|
-
expect(currentSelectionObject).toEqual(nextSelectionObject)
|
|
767
|
-
expect(currentSelectionObject === nextSelectionObject).toBe(false)
|
|
768
|
-
expect(onChange).toHaveBeenCalledWith({type: 'selection', selection: nextSelectionObject})
|
|
769
791
|
})
|
|
770
792
|
|
|
771
793
|
it('should return active marks that cover the whole selection', async () => {
|
|
@@ -53,6 +53,14 @@ describe('plugin:withUndoRedo', () => {
|
|
|
53
53
|
value={initialValue}
|
|
54
54
|
/>,
|
|
55
55
|
)
|
|
56
|
+
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
if (editorRef.current) {
|
|
59
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
60
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
56
64
|
await waitFor(() => {
|
|
57
65
|
if (editorRef.current) {
|
|
58
66
|
PortableTextEditor.focus(editorRef.current)
|
|
@@ -88,6 +96,7 @@ describe('plugin:withUndoRedo', () => {
|
|
|
88
96
|
it('preserves the keys when redoing ', async () => {
|
|
89
97
|
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
90
98
|
const onChange = jest.fn()
|
|
99
|
+
|
|
91
100
|
render(
|
|
92
101
|
<PortableTextEditorTester
|
|
93
102
|
onChange={onChange}
|
|
@@ -96,6 +105,14 @@ describe('plugin:withUndoRedo', () => {
|
|
|
96
105
|
value={initialValue}
|
|
97
106
|
/>,
|
|
98
107
|
)
|
|
108
|
+
|
|
109
|
+
await waitFor(() => {
|
|
110
|
+
if (editorRef.current) {
|
|
111
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
112
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
99
116
|
await waitFor(() => {
|
|
100
117
|
if (editorRef.current) {
|
|
101
118
|
PortableTextEditor.focus(editorRef.current)
|
|
@@ -23,23 +23,38 @@ export function createWithObjectKeys(
|
|
|
23
23
|
editor.apply = (operation) => {
|
|
24
24
|
if (operation.type === 'split_node') {
|
|
25
25
|
const withNewKey = !isPreservingKeys(editor) || !('_key' in operation.properties)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
...
|
|
29
|
-
|
|
26
|
+
|
|
27
|
+
apply({
|
|
28
|
+
...operation,
|
|
29
|
+
properties: {
|
|
30
|
+
...operation.properties,
|
|
31
|
+
...(withNewKey ? {_key: keyGenerator()} : {}),
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return
|
|
30
36
|
}
|
|
37
|
+
|
|
31
38
|
if (operation.type === 'insert_node') {
|
|
32
39
|
// Must be given a new key or adding/removing marks while typing gets in trouble (duped keys)!
|
|
33
40
|
const withNewKey = !isPreservingKeys(editor) || !('_key' in operation.node)
|
|
41
|
+
|
|
34
42
|
if (!Editor.isEditor(operation.node)) {
|
|
35
|
-
|
|
36
|
-
...operation
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
apply({
|
|
44
|
+
...operation,
|
|
45
|
+
node: {
|
|
46
|
+
...operation.node,
|
|
47
|
+
...(withNewKey ? {_key: keyGenerator()} : {}),
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return
|
|
39
52
|
}
|
|
40
53
|
}
|
|
54
|
+
|
|
41
55
|
apply(operation)
|
|
42
56
|
}
|
|
57
|
+
|
|
43
58
|
editor.normalizeNode = (entry) => {
|
|
44
59
|
const [node, path] = entry
|
|
45
60
|
if (Element.isElement(node) && node._type === schemaTypes.block.name) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
|
|
9
10
|
import {isEqual, uniq} from 'lodash'
|
|
10
11
|
import {type Subject} from 'rxjs'
|
|
11
12
|
import {type Descendant, Editor, Element, Path, Range, Text, Transforms} from 'slate'
|
|
@@ -18,12 +19,14 @@ import {
|
|
|
18
19
|
import {debugWithName} from '../../utils/debug'
|
|
19
20
|
import {toPortableTextRange} from '../../utils/ranges'
|
|
20
21
|
import {EMPTY_MARKS} from '../../utils/values'
|
|
22
|
+
import {withoutPreserveKeys} from '../../utils/withPreserveKeys'
|
|
21
23
|
|
|
22
24
|
const debug = debugWithName('plugin:withPortableTextMarkModel')
|
|
23
25
|
|
|
24
26
|
export function createWithPortableTextMarkModel(
|
|
25
27
|
types: PortableTextMemberSchemaTypes,
|
|
26
28
|
change$: Subject<EditorChange>,
|
|
29
|
+
keyGenerator: () => string,
|
|
27
30
|
): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
|
|
28
31
|
return function withPortableTextMarkModel(editor: PortableTextSlateEditor) {
|
|
29
32
|
const {apply, normalizeNode} = editor
|
|
@@ -230,8 +233,8 @@ export function createWithPortableTextMarkModel(
|
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
// Special hook before inserting text at the end of an annotation.
|
|
234
236
|
editor.apply = (op) => {
|
|
237
|
+
// Special hook before inserting text at the end of an annotation.
|
|
235
238
|
if (op.type === 'insert_text') {
|
|
236
239
|
const {selection} = editor
|
|
237
240
|
if (
|
|
@@ -253,26 +256,70 @@ export function createWithPortableTextMarkModel(
|
|
|
253
256
|
Array.isArray(node.marks) &&
|
|
254
257
|
node.marks.length > 0
|
|
255
258
|
) {
|
|
256
|
-
apply(op)
|
|
257
|
-
Transforms.splitNodes(editor, {
|
|
258
|
-
match: Text.isText,
|
|
259
|
-
at: {...selection.focus, offset: selection.focus.offset},
|
|
260
|
-
})
|
|
261
259
|
const marksWithoutAnnotationMarks: string[] = (
|
|
262
260
|
{
|
|
263
261
|
...(Editor.marks(editor) || {}),
|
|
264
262
|
}.marks || []
|
|
265
263
|
).filter((mark) => decorators.includes(mark))
|
|
266
|
-
Transforms.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
264
|
+
Transforms.insertNodes(editor, {
|
|
265
|
+
_type: 'span',
|
|
266
|
+
_key: keyGenerator(),
|
|
267
|
+
text: op.text,
|
|
268
|
+
marks: marksWithoutAnnotationMarks,
|
|
269
|
+
})
|
|
271
270
|
debug('Inserting text at end of annotation')
|
|
272
271
|
return
|
|
273
272
|
}
|
|
274
273
|
}
|
|
275
274
|
}
|
|
275
|
+
|
|
276
|
+
if (op.type === 'remove_text') {
|
|
277
|
+
const nodeEntry = Array.from(
|
|
278
|
+
Editor.nodes(editor, {
|
|
279
|
+
mode: 'lowest',
|
|
280
|
+
at: {path: op.path, offset: op.offset},
|
|
281
|
+
match: (n) => n._type === types.span.name,
|
|
282
|
+
voids: false,
|
|
283
|
+
}),
|
|
284
|
+
)[0]
|
|
285
|
+
const node = nodeEntry[0]
|
|
286
|
+
const blockEntry = Editor.node(editor, Path.parent(op.path))
|
|
287
|
+
const block = blockEntry[0]
|
|
288
|
+
|
|
289
|
+
if (node && isPortableTextSpan(node) && block && isPortableTextBlock(block)) {
|
|
290
|
+
const markDefs = block.markDefs ?? []
|
|
291
|
+
const nodeHasAnnotations = (node.marks ?? []).some((mark) =>
|
|
292
|
+
markDefs.find((markDef) => markDef._key === mark),
|
|
293
|
+
)
|
|
294
|
+
const deletingPartOfTheNode = op.offset !== 0
|
|
295
|
+
const deletingFromTheEnd = op.offset + op.text.length === node.text.length
|
|
296
|
+
|
|
297
|
+
if (nodeHasAnnotations && deletingPartOfTheNode && deletingFromTheEnd) {
|
|
298
|
+
/**
|
|
299
|
+
* If all of these conditions match then override the ordinary
|
|
300
|
+
* `remove_text` operation and turn it into `split_nodes` followed
|
|
301
|
+
* by `remove_nodes`. This is so if the operation can be properly
|
|
302
|
+
* undone. Undoing a `remove_text` results in an `insert_text` and
|
|
303
|
+
* we want to bail out of that in this exact scenario to make sure
|
|
304
|
+
* the inserted text is annotated. (See custom logic regarding
|
|
305
|
+
* `insert_text`)
|
|
306
|
+
*/
|
|
307
|
+
Editor.withoutNormalizing(editor, () => {
|
|
308
|
+
withoutPreserveKeys(editor, () => {
|
|
309
|
+
Transforms.splitNodes(editor, {
|
|
310
|
+
match: Text.isText,
|
|
311
|
+
at: {path: op.path, offset: op.offset},
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
Transforms.removeNodes(editor, {at: Path.next(op.path)})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
editor.onChange()
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
276
323
|
apply(op)
|
|
277
324
|
}
|
|
278
325
|
|
|
@@ -419,17 +466,24 @@ export function createWithPortableTextMarkModel(
|
|
|
419
466
|
*/
|
|
420
467
|
function mergeSpans(editor: PortableTextSlateEditor) {
|
|
421
468
|
const {selection} = editor
|
|
469
|
+
|
|
422
470
|
if (selection) {
|
|
423
|
-
|
|
471
|
+
const textNodesInSelection = Array.from(
|
|
424
472
|
Editor.nodes(editor, {
|
|
425
473
|
at: Editor.range(editor, [selection.anchor.path[0]], [selection.focus.path[0]]),
|
|
474
|
+
match: Text.isText,
|
|
475
|
+
reverse: true,
|
|
426
476
|
}),
|
|
427
|
-
)
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
for (const [node, path] of textNodesInSelection) {
|
|
428
480
|
const [parent] = path.length > 1 ? Editor.node(editor, Path.parent(path)) : [undefined]
|
|
429
481
|
const nextPath = [path[0], path[1] + 1]
|
|
482
|
+
|
|
430
483
|
if (editor.isTextBlock(parent)) {
|
|
431
484
|
const nextNode = parent.children[nextPath[1]]
|
|
432
|
-
|
|
485
|
+
|
|
486
|
+
if (Text.isText(nextNode) && isEqual(nextNode.marks, node.marks)) {
|
|
433
487
|
debug('Merging spans')
|
|
434
488
|
Transforms.mergeNodes(editor, {at: nextPath, voids: true})
|
|
435
489
|
editor.onChange()
|
|
@@ -146,17 +146,15 @@ export function createWithUndoRedo(
|
|
|
146
146
|
),
|
|
147
147
|
)
|
|
148
148
|
})
|
|
149
|
+
const reversedOperations = transformedOperations.map(Operation.inverse).reverse()
|
|
150
|
+
|
|
149
151
|
try {
|
|
150
152
|
Editor.withoutNormalizing(editor, () => {
|
|
151
153
|
withPreserveKeys(editor, () => {
|
|
152
154
|
withoutSaving(editor, () => {
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
// eslint-disable-next-line max-nested-callbacks
|
|
157
|
-
.forEach((op) => {
|
|
158
|
-
editor.apply(op)
|
|
159
|
-
})
|
|
155
|
+
reversedOperations.forEach((op) => {
|
|
156
|
+
editor.apply(op)
|
|
157
|
+
})
|
|
160
158
|
})
|
|
161
159
|
})
|
|
162
160
|
})
|