@portabletext/editor 1.50.4 → 1.50.6

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 (43) hide show
  1. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs +4 -14
  2. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
  3. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +4 -14
  4. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
  5. package/lib/behaviors/index.d.cts +9 -1
  6. package/lib/behaviors/index.d.ts +9 -1
  7. package/lib/index.cjs +271 -205
  8. package/lib/index.cjs.map +1 -1
  9. package/lib/index.d.cts +9 -1
  10. package/lib/index.d.ts +9 -1
  11. package/lib/index.js +280 -214
  12. package/lib/index.js.map +1 -1
  13. package/lib/plugins/index.d.cts +9 -1
  14. package/lib/plugins/index.d.ts +9 -1
  15. package/lib/selectors/index.d.cts +9 -1
  16. package/lib/selectors/index.d.ts +9 -1
  17. package/lib/utils/index.d.cts +9 -1
  18. package/lib/utils/index.d.ts +9 -1
  19. package/package.json +3 -3
  20. package/src/behaviors/behavior.abstract.delete.ts +6 -2
  21. package/src/behaviors/behavior.abstract.ts +1 -1
  22. package/src/editor/create-slate-editor.tsx +2 -0
  23. package/src/editor/editor-selector.ts +10 -4
  24. package/src/editor/editor-snapshot.ts +12 -5
  25. package/src/editor/get-active-annotations.ts +15 -0
  26. package/src/editor/get-active-decorators.ts +23 -9
  27. package/src/editor/plugins/create-with-event-listeners.ts +9 -3
  28. package/src/editor/plugins/createWithEditableAPI.ts +16 -14
  29. package/src/editor/plugins/createWithPortableTextMarkModel.ts +26 -190
  30. package/src/editor/plugins/slate-plugin.update-mark-state.ts +21 -0
  31. package/src/editor/plugins/slate-plugin.update-value.ts +1 -4
  32. package/src/editor/plugins/with-plugins.ts +8 -1
  33. package/src/internal-utils/create-test-snapshot.ts +2 -1
  34. package/src/internal-utils/mark-state.ts +172 -0
  35. package/src/internal-utils/slate-utils.ts +52 -0
  36. package/src/operations/behavior.operation.decorator.add.ts +7 -11
  37. package/src/operations/behavior.operation.insert.text.ts +48 -8
  38. package/src/selectors/selector.get-active-annotations.ts +2 -1
  39. package/src/selectors/selector.is-active-annotation.ts +7 -39
  40. package/src/selectors/selector.is-active-decorator.ts +1 -1
  41. package/src/types/editor.ts +3 -0
  42. package/src/editor/get-value.ts +0 -18
  43. package/src/selectors/selector.get-active-annotations.test.ts +0 -141
@@ -15,6 +15,7 @@ import {isRedoing, isUndoing} from '../../internal-utils/withUndoRedo'
15
15
  import type {BehaviorOperationImplementation} from '../../operations/behavior.operations'
16
16
  import type {PortableTextSlateEditor} from '../../types/editor'
17
17
  import type {EditorActor} from '../editor-machine'
18
+ import {getEditorSnapshot} from '../editor-selector'
18
19
 
19
20
  const debug = debugWithName('plugin:withPortableTextMarkModel')
20
21
 
@@ -262,10 +263,7 @@ export function createWithPortableTextMarkModel(
262
263
  }
263
264
 
264
265
  if (op.type === 'set_selection') {
265
- const marks = Editor.marks(editor)
266
-
267
266
  if (
268
- marks &&
269
267
  op.properties &&
270
268
  op.newProperties &&
271
269
  op.properties.anchor &&
@@ -316,12 +314,10 @@ export function createWithPortableTextMarkModel(
316
314
  op.properties.focus.offset === 0 &&
317
315
  newFocusSpan.text.length === op.newProperties.focus.offset
318
316
 
319
- // If the editor has marks and we are not visually moving the
320
- // selection then we just abort. Otherwise the marks would be
321
- // cleared and we can't use them for the possible subsequent insert
322
- // operation.
323
- if (movedToNextSpan || movedToPreviousSpan) {
324
- return
317
+ // We only want to clear the decorator state if the caret is visually
318
+ // moving
319
+ if (!movedToNextSpan && !movedToPreviousSpan) {
320
+ editor.decoratorState = {}
325
321
  }
326
322
  }
327
323
  }
@@ -416,141 +412,24 @@ export function createWithPortableTextMarkModel(
416
412
  }
417
413
 
418
414
  if (op.type === 'insert_text') {
419
- const {selection} = editor
420
- const collapsedSelection = selection
421
- ? Range.isCollapsed(selection)
422
- : false
423
-
424
- if (selection && collapsedSelection) {
425
- const [_block, blockPath] = Editor.node(editor, selection, {
426
- depth: 1,
427
- })
428
-
429
- const [span, spanPath] =
430
- Array.from(
431
- Editor.nodes(editor, {
432
- mode: 'lowest',
433
- at: selection.focus,
434
- match: (n) => editor.isTextSpan(n),
435
- voids: false,
436
- }),
437
- )[0] ?? ([undefined, undefined] as const)
438
-
439
- const marks = span.marks ?? []
440
- const marksWithoutAnnotations = marks.filter((mark) =>
441
- decorators.includes(mark),
442
- )
443
- const spanHasAnnotations =
444
- marks.length > marksWithoutAnnotations.length
445
-
446
- const spanIsEmpty = span.text.length === 0
447
-
448
- const atTheBeginningOfSpan = selection.anchor.offset === 0
449
- const atTheEndOfSpan = selection.anchor.offset === span.text.length
450
-
451
- const previousSpan = getPreviousSpan({editor, blockPath, spanPath})
452
- const nextSpan = getNextSpan({editor, blockPath, spanPath})
453
- const nextSpanAnnotations =
454
- nextSpan?.marks?.filter((mark) => !decorators.includes(mark)) ?? []
455
- const spanAnnotations = marks.filter(
456
- (mark) => !decorators.includes(mark),
457
- )
458
-
459
- const previousSpanHasAnnotations = previousSpan
460
- ? previousSpan.marks?.some((mark) => !decorators.includes(mark))
461
- : false
462
- const previousSpanHasSameAnnotations = previousSpan
463
- ? previousSpan.marks
464
- ?.filter((mark) => !decorators.includes(mark))
465
- .every((mark) => marks.includes(mark))
466
- : false
467
- const previousSpanHasSameAnnotation = previousSpan
468
- ? previousSpan.marks?.some(
469
- (mark) => !decorators.includes(mark) && marks.includes(mark),
470
- )
471
- : false
472
-
473
- const previousSpanHasSameMarks = previousSpan
474
- ? previousSpan.marks?.every((mark) => marks.includes(mark))
475
- : false
476
- const nextSpanSharesSomeAnnotations = spanAnnotations.some((mark) =>
477
- nextSpanAnnotations?.includes(mark),
478
- )
415
+ if (!editor.markState) {
416
+ apply(op)
417
+ return
418
+ }
479
419
 
480
- if (spanHasAnnotations && !spanIsEmpty) {
481
- if (atTheBeginningOfSpan) {
482
- if (previousSpanHasSameMarks) {
483
- Transforms.insertNodes(editor, {
484
- _type: 'span',
485
- _key: editorActor.getSnapshot().context.keyGenerator(),
486
- text: op.text,
487
- marks: previousSpan?.marks ?? [],
488
- })
489
- return
490
- } else if (previousSpanHasSameAnnotations) {
491
- Transforms.insertNodes(editor, {
492
- _type: 'span',
493
- _key: editorActor.getSnapshot().context.keyGenerator(),
494
- text: op.text,
495
- marks: previousSpan?.marks ?? [],
496
- })
497
- return
498
- } else if (previousSpanHasSameAnnotation) {
499
- apply(op)
500
- return
501
- } else if (!previousSpan) {
502
- Transforms.insertNodes(editor, {
503
- _type: 'span',
504
- _key: editorActor.getSnapshot().context.keyGenerator(),
505
- text: op.text,
506
- marks: [],
507
- })
508
- return
509
- }
510
- }
420
+ if (editor.markState.state === 'unchanged') {
421
+ apply(op)
422
+ return
423
+ }
511
424
 
512
- if (atTheEndOfSpan) {
513
- if (
514
- (nextSpan &&
515
- nextSpanSharesSomeAnnotations &&
516
- nextSpanAnnotations.length < spanAnnotations.length) ||
517
- !nextSpanSharesSomeAnnotations
518
- ) {
519
- Transforms.insertNodes(editor, {
520
- _type: 'span',
521
- _key: editorActor.getSnapshot().context.keyGenerator(),
522
- text: op.text,
523
- marks: nextSpan?.marks ?? [],
524
- })
525
- return
526
- }
527
-
528
- if (!nextSpan) {
529
- Transforms.insertNodes(editor, {
530
- _type: 'span',
531
- _key: editorActor.getSnapshot().context.keyGenerator(),
532
- text: op.text,
533
- marks: [],
534
- })
535
- return
536
- }
537
- }
538
- }
425
+ Transforms.insertNodes(editor, {
426
+ _type: 'span',
427
+ _key: editorActor.getSnapshot().context.keyGenerator(),
428
+ text: op.text,
429
+ marks: editor.markState.marks,
430
+ })
539
431
 
540
- if (atTheBeginningOfSpan && !spanIsEmpty && !!previousSpan) {
541
- Transforms.insertNodes(editor, {
542
- _type: 'span',
543
- _key: editorActor.getSnapshot().context.keyGenerator(),
544
- text: op.text,
545
- marks: previousSpanHasAnnotations
546
- ? []
547
- : (previousSpan.marks ?? []).filter((mark) =>
548
- decorators.includes(mark),
549
- ),
550
- })
551
- return
552
- }
553
- }
432
+ return
554
433
  }
555
434
 
556
435
  if (op.type === 'remove_text') {
@@ -600,17 +479,16 @@ export function createWithPortableTextMarkModel(
600
479
  !previousSpanHasSameAnnotation &&
601
480
  !nextSpanHasSameAnnotation
602
481
  ) {
603
- const marksWithoutAnnotationMarks: string[] = (
604
- {
605
- ...(Editor.marks(editor) || {}),
606
- }.marks || []
607
- ).filter((mark) => decorators.includes(mark))
482
+ const snapshot = getEditorSnapshot({
483
+ editorActorSnapshot: editorActor.getSnapshot(),
484
+ slateEditorInstance: editor,
485
+ })
608
486
 
609
487
  Editor.withoutNormalizing(editor, () => {
610
488
  apply(op)
611
489
  Transforms.setNodes(
612
490
  editor,
613
- {marks: marksWithoutAnnotationMarks},
491
+ {marks: snapshot.beta.activeDecorators},
614
492
  {at: op.path},
615
493
  )
616
494
  })
@@ -727,15 +605,7 @@ export const removeDecoratorOperationImplementation: BehaviorOperationImplementa
727
605
  },
728
606
  )
729
607
  } else {
730
- const existingMarks: string[] =
731
- {
732
- ...(Editor.marks(editor) || {}),
733
- }.marks || []
734
- const marks = {
735
- ...(Editor.marks(editor) || {}),
736
- marks: existingMarks.filter((eMark) => eMark !== mark),
737
- } as Text
738
- editor.marks = {marks: marks.marks, _type: 'span'} as Text
608
+ editor.decoratorState[mark] = false
739
609
  }
740
610
  }
741
611
 
@@ -746,37 +616,3 @@ export const removeDecoratorOperationImplementation: BehaviorOperationImplementa
746
616
  }
747
617
  }
748
618
  }
749
-
750
- export function isDecoratorActive({
751
- editor,
752
- decorator,
753
- }: {
754
- editor: PortableTextSlateEditor
755
- decorator: string
756
- }) {
757
- if (!editor.selection) {
758
- return false
759
- }
760
-
761
- const selectedTextNodes = Array.from(
762
- Editor.nodes(editor, {match: Text.isText, at: editor.selection}),
763
- )
764
-
765
- if (selectedTextNodes.length === 0) {
766
- return false
767
- }
768
-
769
- if (Range.isExpanded(editor.selection)) {
770
- return selectedTextNodes.every((n) => {
771
- const [node] = n
772
-
773
- return node.marks?.includes(decorator)
774
- })
775
- }
776
-
777
- return (
778
- {
779
- ...(Editor.marks(editor) || {}),
780
- }.marks || []
781
- ).includes(decorator)
782
- }
@@ -0,0 +1,21 @@
1
+ import {getMarkState} from '../../internal-utils/mark-state'
2
+ import type {PortableTextSlateEditor} from '../../types/editor'
3
+ import type {EditorContext} from '../editor-snapshot'
4
+
5
+ export function pluginUpdateMarkState(
6
+ context: Pick<EditorContext, 'schema'>,
7
+ editor: PortableTextSlateEditor,
8
+ ) {
9
+ const {apply} = editor
10
+
11
+ editor.apply = (operation) => {
12
+ apply(operation)
13
+
14
+ editor.markState = getMarkState({
15
+ editor,
16
+ schema: context.schema,
17
+ })
18
+ }
19
+
20
+ return editor
21
+ }
@@ -15,10 +15,7 @@ export function pluginUpdateValue(
15
15
  }
16
16
 
17
17
  editor.value = applyOperationToPortableText(
18
- {
19
- keyGenerator: context.keyGenerator,
20
- schema: context.schema,
21
- },
18
+ context,
22
19
  editor.value,
23
20
  operation,
24
21
  )
@@ -13,6 +13,7 @@ import {createWithPortableTextSelections} from './createWithPortableTextSelectio
13
13
  import {createWithSchemaTypes} from './createWithSchemaTypes'
14
14
  import {createWithUndoRedo} from './createWithUndoRedo'
15
15
  import {createWithUtils} from './createWithUtils'
16
+ import {pluginUpdateMarkState} from './slate-plugin.update-mark-state'
16
17
  import {pluginUpdateValue} from './slate-plugin.update-value'
17
18
 
18
19
  export interface OriginalEditorFunctions {
@@ -72,7 +73,13 @@ export const withPlugins = <T extends Editor>(
72
73
  withUndoRedo(
73
74
  withPatches(
74
75
  withPortableTextSelections(
75
- pluginUpdateValue(editorActor.getSnapshot().context, e),
76
+ pluginUpdateValue(
77
+ editorActor.getSnapshot().context,
78
+ pluginUpdateMarkState(
79
+ editorActor.getSnapshot().context,
80
+ e,
81
+ ),
82
+ ),
76
83
  ),
77
84
  ),
78
85
  ),
@@ -12,12 +12,13 @@ export function createTestSnapshot(snapshot: {
12
12
  schema:
13
13
  snapshot.context?.schema ?? compileSchemaDefinition(defineSchema({})),
14
14
  keyGenerator: snapshot.context?.keyGenerator ?? createTestKeyGenerator(),
15
- activeDecorators: snapshot.context?.activeDecorators ?? [],
16
15
  readOnly: snapshot.context?.readOnly ?? false,
17
16
  value: snapshot.context?.value ?? [],
18
17
  selection: snapshot.context?.selection ?? null,
19
18
  },
20
19
  beta: {
20
+ activeAnnotations: snapshot.beta?.activeAnnotations ?? [],
21
+ activeDecorators: snapshot.beta?.activeDecorators ?? [],
21
22
  hasTag: snapshot.beta?.hasTag ?? (() => false),
22
23
  internalDrag: undefined,
23
24
  },
@@ -0,0 +1,172 @@
1
+ import {Range} from 'slate'
2
+ import type {EditorSchema} from '../editor/editor-schema'
3
+ import type {PortableTextSlateEditor} from '../types/editor'
4
+ import {getNextSpan, getPreviousSpan} from './sibling-utils'
5
+ import {getFocusBlock, getFocusSpan, getSelectedSpans} from './slate-utils'
6
+
7
+ export type MarkState = {
8
+ state: 'changed' | 'unchanged'
9
+ marks: Array<string>
10
+ }
11
+
12
+ /**
13
+ * Given that text is inserted at the current position, what marks should
14
+ * be applied?
15
+ */
16
+ export function getMarkState({
17
+ schema,
18
+ editor,
19
+ }: {
20
+ schema: EditorSchema
21
+ editor: PortableTextSlateEditor
22
+ }): MarkState | undefined {
23
+ if (!editor.selection) {
24
+ return undefined
25
+ }
26
+
27
+ const [block, blockPath] = getFocusBlock({
28
+ editor,
29
+ })
30
+ const [span, spanPath] = getFocusSpan({
31
+ editor,
32
+ })
33
+
34
+ if (!block || !editor.isTextBlock(block) || !span) {
35
+ return undefined
36
+ }
37
+
38
+ if (Range.isExpanded(editor.selection)) {
39
+ const selectedSpans = getSelectedSpans({editor})
40
+
41
+ let index = 0
42
+ let marks: Array<string> = []
43
+
44
+ for (const [span] of selectedSpans) {
45
+ if (index === 0) {
46
+ marks = span.marks ?? []
47
+ } else {
48
+ if (
49
+ span.marks?.length === 0 ||
50
+ (span.marks ?? [])?.some((mark) => !marks.includes(mark))
51
+ ) {
52
+ marks = []
53
+ }
54
+ }
55
+
56
+ index++
57
+ }
58
+
59
+ return {
60
+ state: 'unchanged',
61
+ marks,
62
+ }
63
+ }
64
+
65
+ const decorators = schema.decorators.map((decorator) => decorator.name)
66
+ const marks = span.marks ?? []
67
+ const marksWithoutAnnotations = marks.filter((mark) =>
68
+ decorators.includes(mark),
69
+ )
70
+
71
+ const spanHasAnnotations = marks.length > marksWithoutAnnotations.length
72
+
73
+ const spanIsEmpty = span.text.length === 0
74
+
75
+ const atTheBeginningOfSpan = editor.selection.anchor.offset === 0
76
+ const atTheEndOfSpan = editor.selection.anchor.offset === span.text.length
77
+
78
+ const previousSpan = getPreviousSpan({editor, blockPath, spanPath})
79
+ const nextSpan = getNextSpan({editor, blockPath, spanPath})
80
+ const nextSpanAnnotations =
81
+ nextSpan?.marks?.filter((mark) => !decorators.includes(mark)) ?? []
82
+ const spanAnnotations = marks.filter((mark) => !decorators.includes(mark))
83
+
84
+ const previousSpanHasAnnotations = previousSpan
85
+ ? previousSpan.marks?.some((mark) => !decorators.includes(mark))
86
+ : false
87
+ const previousSpanHasSameAnnotations = previousSpan
88
+ ? previousSpan.marks
89
+ ?.filter((mark) => !decorators.includes(mark))
90
+ .every((mark) => marks.includes(mark))
91
+ : false
92
+ const previousSpanHasSameAnnotation = previousSpan
93
+ ? previousSpan.marks?.some(
94
+ (mark) => !decorators.includes(mark) && marks.includes(mark),
95
+ )
96
+ : false
97
+
98
+ const previousSpanHasSameMarks = previousSpan
99
+ ? previousSpan.marks?.every((mark) => marks.includes(mark))
100
+ : false
101
+ const nextSpanSharesSomeAnnotations = spanAnnotations.some((mark) =>
102
+ nextSpanAnnotations?.includes(mark),
103
+ )
104
+
105
+ if (spanHasAnnotations && !spanIsEmpty) {
106
+ if (atTheBeginningOfSpan) {
107
+ if (previousSpanHasSameMarks) {
108
+ return {
109
+ state: 'changed',
110
+ marks: previousSpan?.marks ?? [],
111
+ }
112
+ } else if (previousSpanHasSameAnnotations) {
113
+ return {
114
+ state: 'changed',
115
+ marks: previousSpan?.marks ?? [],
116
+ }
117
+ } else if (previousSpanHasSameAnnotation) {
118
+ return {
119
+ state: 'unchanged',
120
+ marks: span.marks ?? [],
121
+ }
122
+ } else if (!previousSpan) {
123
+ return {
124
+ state: 'changed',
125
+ marks: [],
126
+ }
127
+ }
128
+ }
129
+
130
+ if (atTheEndOfSpan) {
131
+ if (
132
+ (nextSpan &&
133
+ nextSpanSharesSomeAnnotations &&
134
+ nextSpanAnnotations.length < spanAnnotations.length) ||
135
+ !nextSpanSharesSomeAnnotations
136
+ ) {
137
+ return {
138
+ state: 'changed',
139
+ marks: nextSpan?.marks ?? [],
140
+ }
141
+ }
142
+
143
+ if (!nextSpan) {
144
+ return {
145
+ state: 'changed',
146
+ marks: [],
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ if (atTheBeginningOfSpan && !spanIsEmpty && !!previousSpan) {
153
+ if (previousSpanHasAnnotations) {
154
+ return {
155
+ state: 'changed',
156
+ marks: [],
157
+ }
158
+ } else {
159
+ return {
160
+ state: 'changed',
161
+ marks: (previousSpan?.marks ?? []).filter((mark) =>
162
+ decorators.includes(mark),
163
+ ),
164
+ }
165
+ }
166
+ }
167
+
168
+ return {
169
+ state: 'unchanged',
170
+ marks: span.marks ?? [],
171
+ }
172
+ }
@@ -1,3 +1,4 @@
1
+ import type {PortableTextSpan} from '@sanity/types'
1
2
  import {Editor, Element, Node, Range, type Path, type Point} from 'slate'
2
3
  import type {EditorSchema} from '../editor/editor-schema'
3
4
  import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
@@ -45,6 +46,49 @@ export function getFocusBlock({
45
46
  }
46
47
  }
47
48
 
49
+ export function getFocusSpan({
50
+ editor,
51
+ }: {
52
+ editor: PortableTextSlateEditor
53
+ }): [node: PortableTextSpan, path: Path] | [undefined, undefined] {
54
+ if (!editor.selection) {
55
+ return [undefined, undefined]
56
+ }
57
+
58
+ try {
59
+ const [node, path] = Editor.node(editor, editor.selection.focus.path)
60
+
61
+ if (editor.isTextSpan(node)) {
62
+ return [node, path]
63
+ }
64
+ } catch {
65
+ return [undefined, undefined]
66
+ }
67
+
68
+ return [undefined, undefined]
69
+ }
70
+
71
+ export function getSelectedSpans({
72
+ editor,
73
+ }: {
74
+ editor: PortableTextSlateEditor
75
+ }): Array<[node: PortableTextSpan, path: Path]> {
76
+ if (!editor.selection) {
77
+ return []
78
+ }
79
+
80
+ try {
81
+ return Array.from(
82
+ Editor.nodes(editor, {
83
+ at: editor.selection,
84
+ match: (node) => editor.isTextSpan(node),
85
+ }),
86
+ )
87
+ } catch {
88
+ return []
89
+ }
90
+ }
91
+
48
92
  export function getSelectionStartBlock({
49
93
  editor,
50
94
  }: {
@@ -144,6 +188,10 @@ export function getFirstBlock({
144
188
  }: {
145
189
  editor: PortableTextSlateEditor
146
190
  }): [node: Node, path: Path] | [undefined, undefined] {
191
+ if (editor.children.length === 0) {
192
+ return [undefined, undefined]
193
+ }
194
+
147
195
  const firstPoint = Editor.start(editor, [])
148
196
  const firstBlockPath = firstPoint.path.at(0)
149
197
 
@@ -161,6 +209,10 @@ export function getLastBlock({
161
209
  }: {
162
210
  editor: PortableTextSlateEditor
163
211
  }): [node: Node, path: Path] | [undefined, undefined] {
212
+ if (editor.children.length === 0) {
213
+ return [undefined, undefined]
214
+ }
215
+
164
216
  const lastPoint = Editor.end(editor, [])
165
217
  const lastBlockPath = lastPoint.path.at(0)
166
218
 
@@ -108,9 +108,13 @@ export const decoratorAddOperationImplementation: BehaviorOperationImplementatio
108
108
  })
109
109
 
110
110
  const trimmedSelection = selectors.getTrimmedSelection({
111
- beta: {hasTag: () => false, internalDrag: undefined},
112
- context: {
111
+ beta: {
112
+ activeAnnotations: [],
113
113
  activeDecorators: [],
114
+ hasTag: () => false,
115
+ internalDrag: undefined,
116
+ },
117
+ context: {
114
118
  converters: [],
115
119
  keyGenerator: context.keyGenerator,
116
120
  readOnly: false,
@@ -196,15 +200,7 @@ export const decoratorAddOperationImplementation: BehaviorOperationImplementatio
196
200
  },
197
201
  )
198
202
  } else {
199
- const existingMarks: string[] =
200
- {
201
- ...(Editor.marks(editor) || {}),
202
- }.marks || []
203
- const marks = {
204
- ...(Editor.marks(editor) || {}),
205
- marks: [...existingMarks, mark],
206
- }
207
- editor.marks = marks as Text
203
+ editor.decoratorState[mark] = true
208
204
  }
209
205
  }
210
206
 
@@ -1,17 +1,57 @@
1
1
  import {Transforms} from 'slate'
2
+ import {getActiveAnnotations} from '../editor/get-active-annotations'
3
+ import {getActiveDecorators} from '../editor/get-active-decorators'
4
+ import {getFocusSpan} from '../internal-utils/slate-utils'
2
5
  import type {BehaviorOperationImplementation} from './behavior.operations'
3
6
 
4
7
  export const insertTextOperationImplementation: BehaviorOperationImplementation<
5
8
  'insert.text'
6
- > = ({operation}) => {
7
- if (operation.editor.marks) {
8
- Transforms.insertNodes(operation.editor, {
9
- text: operation.text,
10
- ...operation.editor.marks,
11
- })
12
- } else {
9
+ > = ({context, operation}) => {
10
+ const activeDecorators = getActiveDecorators({
11
+ decoratorState: operation.editor.decoratorState,
12
+ markState: operation.editor.markState,
13
+ schema: context.schema,
14
+ })
15
+ const activeAnnotations = getActiveAnnotations({
16
+ markState: operation.editor.markState,
17
+ schema: context.schema,
18
+ })
19
+
20
+ const [focusSpan] = getFocusSpan({
21
+ editor: operation.editor,
22
+ })
23
+
24
+ if (!focusSpan) {
13
25
  Transforms.insertText(operation.editor, operation.text)
26
+ return
27
+ }
28
+
29
+ if (
30
+ operation.editor.markState &&
31
+ operation.editor.markState.state === 'unchanged'
32
+ ) {
33
+ const markStateDecorators = (operation.editor.markState.marks ?? []).filter(
34
+ (mark) =>
35
+ context.schema.decorators
36
+ .map((decorator) => decorator.name)
37
+ .includes(mark),
38
+ )
39
+
40
+ if (
41
+ markStateDecorators.length === activeDecorators.length &&
42
+ markStateDecorators.every((mark) => activeDecorators.includes(mark))
43
+ ) {
44
+ Transforms.insertText(operation.editor, operation.text)
45
+ return
46
+ }
14
47
  }
15
48
 
16
- operation.editor.marks = null
49
+ Transforms.insertNodes(operation.editor, {
50
+ _type: focusSpan._type,
51
+ _key: context.keyGenerator(),
52
+ text: operation.text,
53
+ marks: [...activeDecorators, ...activeAnnotations],
54
+ })
55
+
56
+ operation.editor.decoratorState = {}
17
57
  }