@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.
- package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs +4 -14
- package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
- package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +4 -14
- package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
- package/lib/behaviors/index.d.cts +9 -1
- package/lib/behaviors/index.d.ts +9 -1
- package/lib/index.cjs +271 -205
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +9 -1
- package/lib/index.d.ts +9 -1
- package/lib/index.js +280 -214
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +9 -1
- package/lib/plugins/index.d.ts +9 -1
- package/lib/selectors/index.d.cts +9 -1
- package/lib/selectors/index.d.ts +9 -1
- package/lib/utils/index.d.cts +9 -1
- package/lib/utils/index.d.ts +9 -1
- package/package.json +3 -3
- package/src/behaviors/behavior.abstract.delete.ts +6 -2
- package/src/behaviors/behavior.abstract.ts +1 -1
- package/src/editor/create-slate-editor.tsx +2 -0
- package/src/editor/editor-selector.ts +10 -4
- package/src/editor/editor-snapshot.ts +12 -5
- package/src/editor/get-active-annotations.ts +15 -0
- package/src/editor/get-active-decorators.ts +23 -9
- package/src/editor/plugins/create-with-event-listeners.ts +9 -3
- package/src/editor/plugins/createWithEditableAPI.ts +16 -14
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +26 -190
- package/src/editor/plugins/slate-plugin.update-mark-state.ts +21 -0
- package/src/editor/plugins/slate-plugin.update-value.ts +1 -4
- package/src/editor/plugins/with-plugins.ts +8 -1
- package/src/internal-utils/create-test-snapshot.ts +2 -1
- package/src/internal-utils/mark-state.ts +172 -0
- package/src/internal-utils/slate-utils.ts +52 -0
- package/src/operations/behavior.operation.decorator.add.ts +7 -11
- package/src/operations/behavior.operation.insert.text.ts +48 -8
- package/src/selectors/selector.get-active-annotations.ts +2 -1
- package/src/selectors/selector.is-active-annotation.ts +7 -39
- package/src/selectors/selector.is-active-decorator.ts +1 -1
- package/src/types/editor.ts +3 -0
- package/src/editor/get-value.ts +0 -18
- 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
|
-
//
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
|
|
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
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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(
|
|
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: {
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
}
|