@portabletext/editor 1.1.12 → 1.2.1
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 +155 -27
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +149 -21
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +155 -27
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/editor/behavior/behavior.core.block-objects.ts +106 -0
- package/src/editor/behavior/behavior.core.lists.ts +76 -0
- package/src/editor/behavior/behavior.core.ts +4 -102
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +161 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"@portabletext/toolkit": "^2.0.15",
|
|
61
61
|
"@sanity/block-tools": "^3.62.3",
|
|
62
62
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
63
|
-
"@sanity/pkg-utils": "^6.11.
|
|
63
|
+
"@sanity/pkg-utils": "^6.11.8",
|
|
64
64
|
"@sanity/schema": "^3.62.3",
|
|
65
65
|
"@sanity/types": "^3.62.3",
|
|
66
|
-
"@sanity/ui": "^2.8.
|
|
66
|
+
"@sanity/ui": "^2.8.17",
|
|
67
67
|
"@sanity/util": "^3.62.3",
|
|
68
68
|
"@testing-library/dom": "^10.4.0",
|
|
69
69
|
"@testing-library/jest-dom": "^6.6.2",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {isPortableTextTextBlock} from '@sanity/types'
|
|
2
|
+
import {defineBehavior} from './behavior.types'
|
|
3
|
+
import {
|
|
4
|
+
getFocusBlockObject,
|
|
5
|
+
getFocusTextBlock,
|
|
6
|
+
getNextBlock,
|
|
7
|
+
getPreviousBlock,
|
|
8
|
+
isEmptyTextBlock,
|
|
9
|
+
selectionIsCollapsed,
|
|
10
|
+
} from './behavior.utils'
|
|
11
|
+
|
|
12
|
+
const breakingVoidBlock = defineBehavior({
|
|
13
|
+
on: 'insert break',
|
|
14
|
+
guard: ({context}) => {
|
|
15
|
+
const focusBlockObject = getFocusBlockObject(context)
|
|
16
|
+
|
|
17
|
+
return !!focusBlockObject
|
|
18
|
+
},
|
|
19
|
+
actions: [() => [{type: 'insert text block', decorators: []}]],
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
|
|
23
|
+
on: 'delete backward',
|
|
24
|
+
guard: ({context}) => {
|
|
25
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
26
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
27
|
+
const previousBlock = getPreviousBlock(context)
|
|
28
|
+
|
|
29
|
+
if (!focusTextBlock || !selectionCollapsed || !previousBlock) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
isEmptyTextBlock(focusTextBlock.node) &&
|
|
35
|
+
!isPortableTextTextBlock(previousBlock.node)
|
|
36
|
+
) {
|
|
37
|
+
return {focusTextBlock, previousBlock}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false
|
|
41
|
+
},
|
|
42
|
+
actions: [
|
|
43
|
+
(_, {focusTextBlock, previousBlock}) => [
|
|
44
|
+
{
|
|
45
|
+
type: 'delete',
|
|
46
|
+
selection: {
|
|
47
|
+
anchor: {path: focusTextBlock.path, offset: 0},
|
|
48
|
+
focus: {path: focusTextBlock.path, offset: 0},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'select',
|
|
53
|
+
selection: {
|
|
54
|
+
anchor: {path: previousBlock.path, offset: 0},
|
|
55
|
+
focus: {path: previousBlock.path, offset: 0},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
],
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
|
|
63
|
+
on: 'delete forward',
|
|
64
|
+
guard: ({context}) => {
|
|
65
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
66
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
67
|
+
const nextBlock = getNextBlock(context)
|
|
68
|
+
|
|
69
|
+
if (!focusTextBlock || !selectionCollapsed || !nextBlock) {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
isEmptyTextBlock(focusTextBlock.node) &&
|
|
75
|
+
!isPortableTextTextBlock(nextBlock.node)
|
|
76
|
+
) {
|
|
77
|
+
return {focusTextBlock, nextBlock}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return false
|
|
81
|
+
},
|
|
82
|
+
actions: [
|
|
83
|
+
(_, {focusTextBlock, nextBlock}) => [
|
|
84
|
+
{
|
|
85
|
+
type: 'delete',
|
|
86
|
+
selection: {
|
|
87
|
+
anchor: {path: focusTextBlock.path, offset: 0},
|
|
88
|
+
focus: {path: focusTextBlock.path, offset: 0},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'select',
|
|
93
|
+
selection: {
|
|
94
|
+
anchor: {path: nextBlock.path, offset: 0},
|
|
95
|
+
focus: {path: nextBlock.path, offset: 0},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
export const coreBlockObjectBehaviors = [
|
|
103
|
+
breakingVoidBlock,
|
|
104
|
+
deletingEmptyTextBlockAfterBlockObject,
|
|
105
|
+
deletingEmptyTextBlockBeforeBlockObject,
|
|
106
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {defineBehavior} from './behavior.types'
|
|
2
|
+
import {
|
|
3
|
+
getFocusSpan,
|
|
4
|
+
getFocusTextBlock,
|
|
5
|
+
selectionIsCollapsed,
|
|
6
|
+
} from './behavior.utils'
|
|
7
|
+
|
|
8
|
+
const clearListOnBackspace = defineBehavior({
|
|
9
|
+
on: 'delete backward',
|
|
10
|
+
guard: ({context}) => {
|
|
11
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
12
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
13
|
+
const focusSpan = getFocusSpan(context)
|
|
14
|
+
|
|
15
|
+
if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const atTheBeginningOfBLock =
|
|
20
|
+
focusTextBlock.node.children[0]._key === focusSpan.node._key &&
|
|
21
|
+
context.selection.focus.offset === 0
|
|
22
|
+
|
|
23
|
+
if (atTheBeginningOfBLock && focusTextBlock.node.level === 1) {
|
|
24
|
+
return {focusTextBlock}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return false
|
|
28
|
+
},
|
|
29
|
+
actions: [
|
|
30
|
+
(_, {focusTextBlock}) => [
|
|
31
|
+
{
|
|
32
|
+
type: 'unset block',
|
|
33
|
+
props: ['listItem', 'level'],
|
|
34
|
+
paths: [focusTextBlock.path],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const unindentListOnBackspace = defineBehavior({
|
|
41
|
+
on: 'delete backward',
|
|
42
|
+
guard: ({context}) => {
|
|
43
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
44
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
45
|
+
const focusSpan = getFocusSpan(context)
|
|
46
|
+
|
|
47
|
+
if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const atTheBeginningOfBLock =
|
|
52
|
+
focusTextBlock.node.children[0]._key === focusSpan.node._key &&
|
|
53
|
+
context.selection.focus.offset === 0
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
atTheBeginningOfBLock &&
|
|
57
|
+
focusTextBlock.node.level !== undefined &&
|
|
58
|
+
focusTextBlock.node.level > 1
|
|
59
|
+
) {
|
|
60
|
+
return {focusTextBlock, level: focusTextBlock.node.level - 1}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false
|
|
64
|
+
},
|
|
65
|
+
actions: [
|
|
66
|
+
(_, {focusTextBlock, level}) => [
|
|
67
|
+
{
|
|
68
|
+
type: 'set block',
|
|
69
|
+
level,
|
|
70
|
+
paths: [focusTextBlock.path],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
],
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
export const coreListBehaviors = [clearListOnBackspace, unindentListOnBackspace]
|
|
@@ -1,112 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {coreBlockObjectBehaviors} from './behavior.core.block-objects'
|
|
2
|
+
import {coreListBehaviors} from './behavior.core.lists'
|
|
2
3
|
import {defineBehavior} from './behavior.types'
|
|
3
|
-
import {
|
|
4
|
-
getFocusBlockObject,
|
|
5
|
-
getFocusTextBlock,
|
|
6
|
-
getNextBlock,
|
|
7
|
-
getPreviousBlock,
|
|
8
|
-
isEmptyTextBlock,
|
|
9
|
-
selectionIsCollapsed,
|
|
10
|
-
} from './behavior.utils'
|
|
11
4
|
|
|
12
5
|
const softReturn = defineBehavior({
|
|
13
6
|
on: 'insert soft break',
|
|
14
7
|
actions: [() => [{type: 'insert text', text: '\n'}]],
|
|
15
8
|
})
|
|
16
9
|
|
|
17
|
-
const breakingVoidBlock = defineBehavior({
|
|
18
|
-
on: 'insert break',
|
|
19
|
-
guard: ({context}) => {
|
|
20
|
-
const focusBlockObject = getFocusBlockObject(context)
|
|
21
|
-
|
|
22
|
-
return !!focusBlockObject
|
|
23
|
-
},
|
|
24
|
-
actions: [() => [{type: 'insert text block', decorators: []}]],
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
|
|
28
|
-
on: 'delete backward',
|
|
29
|
-
guard: ({context}) => {
|
|
30
|
-
const focusTextBlock = getFocusTextBlock(context)
|
|
31
|
-
const selectionCollapsed = selectionIsCollapsed(context)
|
|
32
|
-
const previousBlock = getPreviousBlock(context)
|
|
33
|
-
|
|
34
|
-
if (!focusTextBlock || !selectionCollapsed || !previousBlock) {
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
isEmptyTextBlock(focusTextBlock.node) &&
|
|
40
|
-
!isPortableTextTextBlock(previousBlock.node)
|
|
41
|
-
) {
|
|
42
|
-
return {focusTextBlock, previousBlock}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return false
|
|
46
|
-
},
|
|
47
|
-
actions: [
|
|
48
|
-
(_, {focusTextBlock, previousBlock}) => [
|
|
49
|
-
{
|
|
50
|
-
type: 'delete',
|
|
51
|
-
selection: {
|
|
52
|
-
anchor: {path: focusTextBlock.path, offset: 0},
|
|
53
|
-
focus: {path: focusTextBlock.path, offset: 0},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
type: 'select',
|
|
58
|
-
selection: {
|
|
59
|
-
anchor: {path: previousBlock.path, offset: 0},
|
|
60
|
-
focus: {path: previousBlock.path, offset: 0},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
],
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
|
|
68
|
-
on: 'delete forward',
|
|
69
|
-
guard: ({context}) => {
|
|
70
|
-
const focusTextBlock = getFocusTextBlock(context)
|
|
71
|
-
const selectionCollapsed = selectionIsCollapsed(context)
|
|
72
|
-
const nextBlock = getNextBlock(context)
|
|
73
|
-
|
|
74
|
-
if (!focusTextBlock || !selectionCollapsed || !nextBlock) {
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
isEmptyTextBlock(focusTextBlock.node) &&
|
|
80
|
-
!isPortableTextTextBlock(nextBlock.node)
|
|
81
|
-
) {
|
|
82
|
-
return {focusTextBlock, nextBlock}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return false
|
|
86
|
-
},
|
|
87
|
-
actions: [
|
|
88
|
-
(_, {focusTextBlock, nextBlock}) => [
|
|
89
|
-
{
|
|
90
|
-
type: 'delete',
|
|
91
|
-
selection: {
|
|
92
|
-
anchor: {path: focusTextBlock.path, offset: 0},
|
|
93
|
-
focus: {path: focusTextBlock.path, offset: 0},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
type: 'select',
|
|
98
|
-
selection: {
|
|
99
|
-
anchor: {path: nextBlock.path, offset: 0},
|
|
100
|
-
focus: {path: nextBlock.path, offset: 0},
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
],
|
|
105
|
-
})
|
|
106
|
-
|
|
107
10
|
export const coreBehaviors = [
|
|
108
11
|
softReturn,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
deletingEmptyTextBlockBeforeBlockObject,
|
|
12
|
+
...coreBlockObjectBehaviors,
|
|
13
|
+
...coreListBehaviors,
|
|
112
14
|
]
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {isPortableTextBlock} from '@portabletext/toolkit'
|
|
8
|
-
import type {PortableTextObject} from '@sanity/types'
|
|
7
|
+
import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
|
|
8
|
+
import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
|
|
9
9
|
import {isEqual, uniq} from 'lodash'
|
|
10
10
|
import {Editor, Element, Node, Path, Range, Text, Transforms} from 'slate'
|
|
11
11
|
import type {
|
|
@@ -281,6 +281,139 @@ export function createWithPortableTextMarkModel(
|
|
|
281
281
|
return
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
if (op.type === 'set_selection') {
|
|
285
|
+
const marks = Editor.marks(editor)
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
marks &&
|
|
289
|
+
op.properties &&
|
|
290
|
+
op.newProperties &&
|
|
291
|
+
op.properties.anchor &&
|
|
292
|
+
op.properties.focus &&
|
|
293
|
+
op.newProperties.anchor &&
|
|
294
|
+
op.newProperties.focus
|
|
295
|
+
) {
|
|
296
|
+
const previousSelectionIsCollapsed = Range.isCollapsed({
|
|
297
|
+
anchor: op.properties.anchor,
|
|
298
|
+
focus: op.properties.focus,
|
|
299
|
+
})
|
|
300
|
+
const newSelectionIsCollapsed = Range.isCollapsed({
|
|
301
|
+
anchor: op.newProperties.anchor,
|
|
302
|
+
focus: op.newProperties.focus,
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
if (previousSelectionIsCollapsed && newSelectionIsCollapsed) {
|
|
306
|
+
const focusSpan: PortableTextSpan | undefined = Array.from(
|
|
307
|
+
Editor.nodes(editor, {
|
|
308
|
+
mode: 'lowest',
|
|
309
|
+
at: op.properties.focus,
|
|
310
|
+
match: (n) => editor.isTextSpan(n),
|
|
311
|
+
voids: false,
|
|
312
|
+
}),
|
|
313
|
+
)[0]?.[0]
|
|
314
|
+
const newFocusSpan: PortableTextSpan | undefined = Array.from(
|
|
315
|
+
Editor.nodes(editor, {
|
|
316
|
+
mode: 'lowest',
|
|
317
|
+
at: op.newProperties.focus,
|
|
318
|
+
match: (n) => editor.isTextSpan(n),
|
|
319
|
+
voids: false,
|
|
320
|
+
}),
|
|
321
|
+
)[0]?.[0]
|
|
322
|
+
const movedToNextSpan =
|
|
323
|
+
focusSpan &&
|
|
324
|
+
newFocusSpan &&
|
|
325
|
+
op.newProperties.focus.path[0] === op.properties.focus.path[0] &&
|
|
326
|
+
op.newProperties.focus.path[1] ===
|
|
327
|
+
op.properties.focus.path[1] + 1 &&
|
|
328
|
+
focusSpan.text.length === op.properties.focus.offset &&
|
|
329
|
+
op.newProperties.focus.offset === 0
|
|
330
|
+
const movedToPreviousSpan =
|
|
331
|
+
focusSpan &&
|
|
332
|
+
newFocusSpan &&
|
|
333
|
+
op.newProperties.focus.path[0] === op.properties.focus.path[0] &&
|
|
334
|
+
op.newProperties.focus.path[1] ===
|
|
335
|
+
op.properties.focus.path[1] - 1 &&
|
|
336
|
+
op.properties.focus.offset === 0 &&
|
|
337
|
+
newFocusSpan.text.length === op.newProperties.focus.offset
|
|
338
|
+
|
|
339
|
+
// If the editor has marks and we are not visually moving the
|
|
340
|
+
// selection then we just abort. Otherwise the marks would be
|
|
341
|
+
// cleared and we can't use them for the possible subsequent insert
|
|
342
|
+
// operation.
|
|
343
|
+
if (movedToNextSpan || movedToPreviousSpan) {
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (op.type === 'insert_node') {
|
|
351
|
+
const {selection} = editor
|
|
352
|
+
|
|
353
|
+
if (selection) {
|
|
354
|
+
const [_block, blockPath] = Editor.node(editor, selection, {depth: 1})
|
|
355
|
+
const previousSpan = getPreviousSpan({
|
|
356
|
+
editor,
|
|
357
|
+
blockPath,
|
|
358
|
+
spanPath: op.path,
|
|
359
|
+
})
|
|
360
|
+
const previousSpanAnnotations = previousSpan
|
|
361
|
+
? previousSpan.marks?.filter((mark) => !decorators.includes(mark))
|
|
362
|
+
: []
|
|
363
|
+
|
|
364
|
+
const nextSpan = getNextSpan({
|
|
365
|
+
editor,
|
|
366
|
+
blockPath,
|
|
367
|
+
spanPath: [op.path[0], op.path[1] - 1],
|
|
368
|
+
})
|
|
369
|
+
const nextSpanAnnotations = nextSpan
|
|
370
|
+
? nextSpan.marks?.filter((mark) => !decorators.includes(mark))
|
|
371
|
+
: []
|
|
372
|
+
|
|
373
|
+
const annotationsEnding =
|
|
374
|
+
previousSpanAnnotations?.filter(
|
|
375
|
+
(annotation) => !nextSpanAnnotations?.includes(annotation),
|
|
376
|
+
) ?? []
|
|
377
|
+
const atTheEndOfAnnotation = annotationsEnding.length > 0
|
|
378
|
+
|
|
379
|
+
if (
|
|
380
|
+
atTheEndOfAnnotation &&
|
|
381
|
+
isPortableTextSpan(op.node) &&
|
|
382
|
+
op.node.marks?.some((mark) => annotationsEnding.includes(mark))
|
|
383
|
+
) {
|
|
384
|
+
Transforms.insertNodes(editor, {
|
|
385
|
+
...op.node,
|
|
386
|
+
marks:
|
|
387
|
+
op.node.marks?.filter(
|
|
388
|
+
(mark) => !annotationsEnding.includes(mark),
|
|
389
|
+
) ?? [],
|
|
390
|
+
})
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const annotationsStarting =
|
|
395
|
+
nextSpanAnnotations?.filter(
|
|
396
|
+
(annotation) => !previousSpanAnnotations?.includes(annotation),
|
|
397
|
+
) ?? []
|
|
398
|
+
const atTheStartOfAnnotation = annotationsStarting.length > 0
|
|
399
|
+
|
|
400
|
+
if (
|
|
401
|
+
atTheStartOfAnnotation &&
|
|
402
|
+
isPortableTextSpan(op.node) &&
|
|
403
|
+
op.node.marks?.some((mark) => annotationsStarting.includes(mark))
|
|
404
|
+
) {
|
|
405
|
+
Transforms.insertNodes(editor, {
|
|
406
|
+
...op.node,
|
|
407
|
+
marks:
|
|
408
|
+
op.node.marks?.filter(
|
|
409
|
+
(mark) => !annotationsStarting.includes(mark),
|
|
410
|
+
) ?? [],
|
|
411
|
+
})
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
284
417
|
if (op.type === 'insert_text') {
|
|
285
418
|
const {selection} = editor
|
|
286
419
|
const collapsedSelection = selection
|
|
@@ -322,11 +455,20 @@ export function createWithPortableTextMarkModel(
|
|
|
322
455
|
(mark) => !decorators.includes(mark),
|
|
323
456
|
)
|
|
324
457
|
|
|
458
|
+
const previousSpanHasAnnotations = previousSpan
|
|
459
|
+
? previousSpan.marks?.some((mark) => !decorators.includes(mark))
|
|
460
|
+
: false
|
|
461
|
+
const previousSpanHasSameAnnotations = previousSpan
|
|
462
|
+
? previousSpan.marks
|
|
463
|
+
?.filter((mark) => !decorators.includes(mark))
|
|
464
|
+
.every((mark) => marks.includes(mark))
|
|
465
|
+
: false
|
|
325
466
|
const previousSpanHasSameAnnotation = previousSpan
|
|
326
467
|
? previousSpan.marks?.some(
|
|
327
468
|
(mark) => !decorators.includes(mark) && marks.includes(mark),
|
|
328
469
|
)
|
|
329
470
|
: false
|
|
471
|
+
|
|
330
472
|
const previousSpanHasSameMarks = previousSpan
|
|
331
473
|
? previousSpan.marks?.every((mark) => marks.includes(mark))
|
|
332
474
|
: false
|
|
@@ -343,17 +485,27 @@ export function createWithPortableTextMarkModel(
|
|
|
343
485
|
text: op.text,
|
|
344
486
|
marks: previousSpan?.marks ?? [],
|
|
345
487
|
})
|
|
488
|
+
return
|
|
489
|
+
} else if (previousSpanHasSameAnnotations) {
|
|
490
|
+
Transforms.insertNodes(editor, {
|
|
491
|
+
_type: 'span',
|
|
492
|
+
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
493
|
+
text: op.text,
|
|
494
|
+
marks: previousSpan?.marks ?? [],
|
|
495
|
+
})
|
|
496
|
+
return
|
|
346
497
|
} else if (previousSpanHasSameAnnotation) {
|
|
347
498
|
apply(op)
|
|
348
|
-
|
|
499
|
+
return
|
|
500
|
+
} else if (!previousSpan) {
|
|
349
501
|
Transforms.insertNodes(editor, {
|
|
350
502
|
_type: 'span',
|
|
351
503
|
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
352
504
|
text: op.text,
|
|
353
505
|
marks: [],
|
|
354
506
|
})
|
|
507
|
+
return
|
|
355
508
|
}
|
|
356
|
-
return
|
|
357
509
|
}
|
|
358
510
|
|
|
359
511
|
if (atTheEndOfSpan) {
|
|
@@ -389,9 +541,11 @@ export function createWithPortableTextMarkModel(
|
|
|
389
541
|
_type: 'span',
|
|
390
542
|
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
391
543
|
text: op.text,
|
|
392
|
-
marks:
|
|
393
|
-
|
|
394
|
-
|
|
544
|
+
marks: previousSpanHasAnnotations
|
|
545
|
+
? []
|
|
546
|
+
: (previousSpan.marks ?? []).filter((mark) =>
|
|
547
|
+
decorators.includes(mark),
|
|
548
|
+
),
|
|
395
549
|
})
|
|
396
550
|
return
|
|
397
551
|
}
|