@portabletext/editor 1.0.18 → 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.
- package/lib/index.d.mts +140 -66
- package/lib/index.d.ts +140 -66
- package/lib/index.esm.js +1164 -410
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1164 -410
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1164 -410
- package/lib/index.mjs.map +1 -1
- package/package.json +8 -4
- package/src/editor/Editable.tsx +107 -36
- package/src/editor/PortableTextEditor.tsx +47 -12
- package/src/editor/__tests__/PortableTextEditor.test.tsx +42 -15
- package/src/editor/__tests__/PortableTextEditorTester.tsx +50 -38
- package/src/editor/__tests__/RangeDecorations.test.tsx +0 -1
- package/src/editor/__tests__/handleClick.test.tsx +28 -9
- package/src/editor/__tests__/insert-block.test.tsx +22 -6
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +30 -62
- package/src/editor/__tests__/utils.ts +10 -3
- package/src/editor/components/DraggableBlock.tsx +36 -13
- package/src/editor/components/Element.tsx +59 -17
- package/src/editor/components/Leaf.tsx +106 -68
- package/src/editor/components/SlateContainer.tsx +12 -5
- package/src/editor/components/Synchronizer.tsx +5 -2
- package/src/editor/hooks/usePortableTextEditor.ts +2 -2
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +9 -3
- package/src/editor/hooks/useSyncValue.test.tsx +9 -4
- package/src/editor/hooks/useSyncValue.ts +199 -130
- package/src/editor/nodes/DefaultAnnotation.tsx +6 -3
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +25 -7
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +26 -9
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +15 -5
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +60 -19
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +4 -2
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +4 -2
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +61 -550
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +6 -3
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +30 -13
- package/src/editor/plugins/createWithEditableAPI.ts +354 -115
- package/src/editor/plugins/createWithHotKeys.ts +41 -121
- package/src/editor/plugins/createWithInsertBreak.ts +166 -27
- package/src/editor/plugins/createWithInsertData.ts +60 -23
- package/src/editor/plugins/createWithMaxBlocks.ts +5 -2
- package/src/editor/plugins/createWithObjectKeys.ts +7 -3
- package/src/editor/plugins/createWithPatches.ts +60 -16
- package/src/editor/plugins/createWithPlaceholderBlock.ts +7 -3
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +17 -7
- package/src/editor/plugins/createWithPortableTextLists.ts +21 -8
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +301 -155
- package/src/editor/plugins/createWithPortableTextSelections.ts +4 -2
- package/src/editor/plugins/createWithSchemaTypes.ts +25 -9
- package/src/editor/plugins/createWithUndoRedo.ts +107 -24
- package/src/editor/plugins/createWithUtils.ts +32 -10
- package/src/editor/plugins/index.ts +31 -10
- package/src/types/editor.ts +44 -15
- package/src/types/options.ts +4 -2
- package/src/types/slate.ts +2 -2
- package/src/utils/__tests__/dmpToOperations.test.ts +38 -13
- package/src/utils/__tests__/operationToPatches.test.ts +3 -2
- package/src/utils/__tests__/patchToOperations.test.ts +15 -4
- package/src/utils/__tests__/ranges.test.ts +8 -3
- package/src/utils/__tests__/valueNormalization.test.tsx +12 -4
- package/src/utils/__tests__/values.test.ts +0 -1
- package/src/utils/applyPatch.ts +71 -20
- package/src/utils/getPortableTextMemberSchemaTypes.ts +30 -15
- package/src/utils/operationToPatches.ts +126 -43
- package/src/utils/paths.ts +24 -7
- package/src/utils/ranges.ts +12 -5
- package/src/utils/selection.ts +19 -7
- package/src/utils/validateValue.ts +118 -45
- package/src/utils/values.ts +31 -10
- package/src/utils/weakMaps.ts +18 -8
- package/src/utils/withChanges.ts +4 -2
- package/src/editor/plugins/__tests__/withHotkeys.test.tsx +0 -212
- package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +0 -220
- package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +0 -133
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
|
|
10
|
+
import {type PortableTextObject} from '@sanity/types'
|
|
10
11
|
import {isEqual, uniq} from 'lodash'
|
|
11
12
|
import {type Subject} from 'rxjs'
|
|
12
|
-
import {
|
|
13
|
-
|
|
13
|
+
import {
|
|
14
|
+
Editor,
|
|
15
|
+
Element,
|
|
16
|
+
Node,
|
|
17
|
+
Path,
|
|
18
|
+
Range,
|
|
19
|
+
Text,
|
|
20
|
+
Transforms,
|
|
21
|
+
type Descendant,
|
|
22
|
+
} from 'slate'
|
|
14
23
|
import {
|
|
15
24
|
type EditorChange,
|
|
16
25
|
type PortableTextMemberSchemaTypes,
|
|
@@ -18,7 +27,6 @@ import {
|
|
|
18
27
|
} from '../../types/editor'
|
|
19
28
|
import {debugWithName} from '../../utils/debug'
|
|
20
29
|
import {toPortableTextRange} from '../../utils/ranges'
|
|
21
|
-
import {EMPTY_MARKS} from '../../utils/values'
|
|
22
30
|
import {isChangingRemotely} from '../../utils/withChanges'
|
|
23
31
|
import {isRedoing, isUndoing} from '../../utils/withUndoRedo'
|
|
24
32
|
|
|
@@ -48,7 +56,11 @@ export function createWithPortableTextMarkModel(
|
|
|
48
56
|
Transforms.select(editor, {...editor.selection})
|
|
49
57
|
editor.selection = {...editor.selection} // Ensure new object
|
|
50
58
|
}
|
|
51
|
-
const ptRange = toPortableTextRange(
|
|
59
|
+
const ptRange = toPortableTextRange(
|
|
60
|
+
editor.children,
|
|
61
|
+
editor.selection,
|
|
62
|
+
types,
|
|
63
|
+
)
|
|
52
64
|
change$.next({type: 'selection', selection: ptRange})
|
|
53
65
|
}
|
|
54
66
|
|
|
@@ -56,9 +68,6 @@ export function createWithPortableTextMarkModel(
|
|
|
56
68
|
editor.normalizeNode = (nodeEntry) => {
|
|
57
69
|
const [node, path] = nodeEntry
|
|
58
70
|
|
|
59
|
-
const isSpan = Text.isText(node) && node._type === types.span.name
|
|
60
|
-
const isTextBlock = editor.isTextBlock(node)
|
|
61
|
-
|
|
62
71
|
if (editor.isTextBlock(node)) {
|
|
63
72
|
const children = Node.children(editor, path)
|
|
64
73
|
|
|
@@ -75,148 +84,160 @@ export function createWithPortableTextMarkModel(
|
|
|
75
84
|
JSON.stringify(child, null, 2),
|
|
76
85
|
JSON.stringify(nextNode, null, 2),
|
|
77
86
|
)
|
|
78
|
-
Transforms.mergeNodes(editor, {
|
|
87
|
+
Transforms.mergeNodes(editor, {
|
|
88
|
+
at: [childPath[0], childPath[1] + 1],
|
|
89
|
+
voids: true,
|
|
90
|
+
})
|
|
79
91
|
return
|
|
80
92
|
}
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Add missing .markDefs to block nodes
|
|
98
|
+
*/
|
|
99
|
+
if (editor.isTextBlock(node) && !Array.isArray(node.markDefs)) {
|
|
100
|
+
debug('Adding .markDefs to block node')
|
|
101
|
+
Transforms.setNodes(editor, {markDefs: []}, {at: path})
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Add missing .marks to span nodes
|
|
107
|
+
*/
|
|
108
|
+
if (editor.isTextSpan(node) && !Array.isArray(node.marks)) {
|
|
109
|
+
debug('Adding .marks to span node')
|
|
110
|
+
Transforms.setNodes(editor, {marks: []}, {at: path})
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Remove annotations from empty spans
|
|
116
|
+
*/
|
|
117
|
+
if (editor.isTextSpan(node)) {
|
|
118
|
+
const blockPath = Path.parent(path)
|
|
119
|
+
const [block] = Editor.node(editor, blockPath)
|
|
120
|
+
const decorators = types.decorators.map((decorator) => decorator.value)
|
|
121
|
+
const annotations = node.marks?.filter(
|
|
122
|
+
(mark) => !decorators.includes(mark),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if (editor.isTextBlock(block)) {
|
|
126
|
+
if (node.text === '' && annotations && annotations.length > 0) {
|
|
127
|
+
debug('Removing annotations from empty span node')
|
|
128
|
+
Transforms.setNodes(
|
|
129
|
+
editor,
|
|
130
|
+
{marks: node.marks?.filter((mark) => decorators.includes(mark))},
|
|
131
|
+
{at: path},
|
|
132
|
+
)
|
|
133
|
+
return
|
|
114
134
|
}
|
|
115
135
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Make sure markDefs are copied over to new block when splitting a block.
|
|
141
|
-
if (
|
|
142
|
-
op.type === 'split_node' &&
|
|
143
|
-
op.path.length === 1 &&
|
|
144
|
-
Element.isElementProps(op.properties) &&
|
|
145
|
-
op.properties._type === types.block.name &&
|
|
146
|
-
'markDefs' in op.properties &&
|
|
147
|
-
Array.isArray(op.properties.markDefs) &&
|
|
148
|
-
op.properties.markDefs.length > 0 &&
|
|
149
|
-
op.path[0] + 1 < editor.children.length
|
|
150
|
-
) {
|
|
151
|
-
const [targetBlock, targetPath] = Editor.node(editor, [op.path[0] + 1])
|
|
152
|
-
debug(`Copying markDefs over to split block`, op)
|
|
153
|
-
if (editor.isTextBlock(targetBlock)) {
|
|
154
|
-
const oldDefs = (Array.isArray(targetBlock.markDefs) && targetBlock.markDefs) || []
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Remove orphaned annotations from child spans of block nodes
|
|
140
|
+
*/
|
|
141
|
+
if (editor.isTextBlock(node)) {
|
|
142
|
+
const decorators = types.decorators.map((decorator) => decorator.value)
|
|
143
|
+
|
|
144
|
+
for (const [child, childPath] of Node.children(editor, path)) {
|
|
145
|
+
if (editor.isTextSpan(child)) {
|
|
146
|
+
const marks = child.marks ?? []
|
|
147
|
+
const orphanedAnnotations = marks.filter((mark) => {
|
|
148
|
+
return (
|
|
149
|
+
!decorators.includes(mark) &&
|
|
150
|
+
!node.markDefs?.find((def) => def._key === mark)
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (orphanedAnnotations.length > 0) {
|
|
155
|
+
debug('Removing orphaned annotations from span node')
|
|
155
156
|
Transforms.setNodes(
|
|
156
157
|
editor,
|
|
157
|
-
{
|
|
158
|
-
|
|
158
|
+
{
|
|
159
|
+
marks: marks.filter(
|
|
160
|
+
(mark) => !orphanedAnnotations.includes(mark),
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
{at: childPath},
|
|
159
164
|
)
|
|
160
165
|
return
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Remove orphaned annotations from span nodes
|
|
173
|
+
*/
|
|
174
|
+
if (editor.isTextSpan(node)) {
|
|
175
|
+
const blockPath = Path.parent(path)
|
|
176
|
+
const [block] = Editor.node(editor, blockPath)
|
|
177
|
+
|
|
178
|
+
if (editor.isTextBlock(block)) {
|
|
179
|
+
const decorators = types.decorators.map(
|
|
180
|
+
(decorator) => decorator.value,
|
|
181
|
+
)
|
|
182
|
+
const marks = node.marks ?? []
|
|
183
|
+
const orphanedAnnotations = marks.filter((mark) => {
|
|
184
|
+
return (
|
|
185
|
+
!decorators.includes(mark) &&
|
|
186
|
+
!block.markDefs?.find((def) => def._key === mark)
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
if (orphanedAnnotations.length > 0) {
|
|
191
|
+
debug('Removing orphaned annotations from span node')
|
|
192
|
+
Transforms.setNodes(
|
|
193
|
+
editor,
|
|
194
|
+
{
|
|
195
|
+
marks: marks.filter(
|
|
196
|
+
(mark) => !orphanedAnnotations.includes(mark),
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
{at: path},
|
|
200
|
+
)
|
|
201
|
+
return
|
|
183
202
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
block.markDefs &&
|
|
198
|
-
block.markDefs.length > 0 &&
|
|
199
|
-
Text.isText(block.children[0]) &&
|
|
200
|
-
block.children[0].text === '' &&
|
|
201
|
-
(!block.children[0].marks || block.children[0].marks.length === 0)
|
|
202
|
-
) {
|
|
203
|
-
Transforms.setNodes(editor, {markDefs: []}, {at: blockPath})
|
|
204
|
-
return
|
|
205
|
-
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Remove duplicate markDefs
|
|
207
|
+
if (editor.isTextBlock(node)) {
|
|
208
|
+
const markDefs = node.markDefs ?? []
|
|
209
|
+
const markDefKeys = new Set<string>()
|
|
210
|
+
const newMarkDefs: Array<PortableTextObject> = []
|
|
211
|
+
|
|
212
|
+
for (const markDef of markDefs) {
|
|
213
|
+
if (!markDefKeys.has(markDef._key)) {
|
|
214
|
+
markDefKeys.add(markDef._key)
|
|
215
|
+
newMarkDefs.push(markDef)
|
|
206
216
|
}
|
|
207
217
|
}
|
|
218
|
+
|
|
219
|
+
if (markDefs.length !== newMarkDefs.length) {
|
|
220
|
+
debug('Removing duplicate markDefs')
|
|
221
|
+
Transforms.setNodes(editor, {markDefs: newMarkDefs}, {at: path})
|
|
222
|
+
}
|
|
208
223
|
}
|
|
224
|
+
|
|
209
225
|
// Check consistency of markDefs (unless we are merging two nodes)
|
|
210
226
|
if (
|
|
211
227
|
editor.isTextBlock(node) &&
|
|
212
228
|
!editor.operations.some(
|
|
213
|
-
(op) =>
|
|
229
|
+
(op) =>
|
|
230
|
+
op.type === 'merge_node' &&
|
|
231
|
+
'markDefs' in op.properties &&
|
|
232
|
+
op.path.length === 1,
|
|
214
233
|
)
|
|
215
234
|
) {
|
|
216
235
|
const newMarkDefs = (node.markDefs || []).filter((def) => {
|
|
217
236
|
return node.children.find((child) => {
|
|
218
237
|
return (
|
|
219
|
-
Text.isText(child) &&
|
|
238
|
+
Text.isText(child) &&
|
|
239
|
+
Array.isArray(child.marks) &&
|
|
240
|
+
child.marks.includes(def._key)
|
|
220
241
|
)
|
|
221
242
|
})
|
|
222
243
|
})
|
|
@@ -261,13 +282,16 @@ export function createWithPortableTextMarkModel(
|
|
|
261
282
|
if (
|
|
262
283
|
selection &&
|
|
263
284
|
Range.isCollapsed(selection) &&
|
|
264
|
-
Editor.marks(editor)?.marks?.some(
|
|
285
|
+
Editor.marks(editor)?.marks?.some(
|
|
286
|
+
(mark) => !decorators.includes(mark),
|
|
287
|
+
)
|
|
265
288
|
) {
|
|
266
289
|
const [node] = Array.from(
|
|
267
290
|
Editor.nodes(editor, {
|
|
268
291
|
mode: 'lowest',
|
|
269
292
|
at: selection.focus,
|
|
270
|
-
match: (n) =>
|
|
293
|
+
match: (n) =>
|
|
294
|
+
(n as unknown as Descendant)._type === types.span.name,
|
|
271
295
|
voids: false,
|
|
272
296
|
}),
|
|
273
297
|
)[0] || [undefined]
|
|
@@ -307,15 +331,25 @@ export function createWithPortableTextMarkModel(
|
|
|
307
331
|
const blockEntry = Editor.node(editor, Path.parent(op.path))
|
|
308
332
|
const block = blockEntry[0]
|
|
309
333
|
|
|
310
|
-
if (
|
|
334
|
+
if (
|
|
335
|
+
node &&
|
|
336
|
+
isPortableTextSpan(node) &&
|
|
337
|
+
block &&
|
|
338
|
+
isPortableTextBlock(block)
|
|
339
|
+
) {
|
|
311
340
|
const markDefs = block.markDefs ?? []
|
|
312
341
|
const nodeHasAnnotations = (node.marks ?? []).some((mark) =>
|
|
313
342
|
markDefs.find((markDef) => markDef._key === mark),
|
|
314
343
|
)
|
|
315
344
|
const deletingPartOfTheNode = op.offset !== 0
|
|
316
|
-
const deletingFromTheEnd =
|
|
345
|
+
const deletingFromTheEnd =
|
|
346
|
+
op.offset + op.text.length === node.text.length
|
|
317
347
|
|
|
318
|
-
if (
|
|
348
|
+
if (
|
|
349
|
+
nodeHasAnnotations &&
|
|
350
|
+
deletingPartOfTheNode &&
|
|
351
|
+
deletingFromTheEnd
|
|
352
|
+
) {
|
|
319
353
|
Editor.withoutNormalizing(editor, () => {
|
|
320
354
|
Transforms.splitNodes(editor, {
|
|
321
355
|
match: Text.isText,
|
|
@@ -339,7 +373,11 @@ export function createWithPortableTextMarkModel(
|
|
|
339
373
|
|
|
340
374
|
Editor.withoutNormalizing(editor, () => {
|
|
341
375
|
apply(op)
|
|
342
|
-
Transforms.setNodes(
|
|
376
|
+
Transforms.setNodes(
|
|
377
|
+
editor,
|
|
378
|
+
{marks: marksWithoutAnnotationMarks},
|
|
379
|
+
{at: op.path},
|
|
380
|
+
)
|
|
343
381
|
})
|
|
344
382
|
|
|
345
383
|
editor.onChange()
|
|
@@ -360,6 +398,36 @@ export function createWithPortableTextMarkModel(
|
|
|
360
398
|
}
|
|
361
399
|
}
|
|
362
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Copy over markDefs when merging blocks
|
|
403
|
+
*/
|
|
404
|
+
if (
|
|
405
|
+
op.type === 'merge_node' &&
|
|
406
|
+
op.path.length === 1 &&
|
|
407
|
+
'markDefs' in op.properties &&
|
|
408
|
+
op.properties._type === types.block.name &&
|
|
409
|
+
Array.isArray(op.properties.markDefs) &&
|
|
410
|
+
op.properties.markDefs.length > 0 &&
|
|
411
|
+
op.path[0] - 1 >= 0
|
|
412
|
+
) {
|
|
413
|
+
const [targetBlock, targetPath] = Editor.node(editor, [op.path[0] - 1])
|
|
414
|
+
|
|
415
|
+
if (editor.isTextBlock(targetBlock)) {
|
|
416
|
+
const oldDefs =
|
|
417
|
+
(Array.isArray(targetBlock.markDefs) && targetBlock.markDefs) || []
|
|
418
|
+
const newMarkDefs = uniq([...oldDefs, ...op.properties.markDefs])
|
|
419
|
+
|
|
420
|
+
debug(`Copying markDefs over to merged block`, op)
|
|
421
|
+
Transforms.setNodes(
|
|
422
|
+
editor,
|
|
423
|
+
{markDefs: newMarkDefs},
|
|
424
|
+
{at: targetPath, voids: false},
|
|
425
|
+
)
|
|
426
|
+
apply(op)
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
363
431
|
apply(op)
|
|
364
432
|
}
|
|
365
433
|
|
|
@@ -369,10 +437,19 @@ export function createWithPortableTextMarkModel(
|
|
|
369
437
|
if (Range.isExpanded(editor.selection)) {
|
|
370
438
|
Editor.withoutNormalizing(editor, () => {
|
|
371
439
|
// Split if needed
|
|
372
|
-
Transforms.setNodes(
|
|
440
|
+
Transforms.setNodes(
|
|
441
|
+
editor,
|
|
442
|
+
{},
|
|
443
|
+
{match: Text.isText, split: true, hanging: true},
|
|
444
|
+
)
|
|
373
445
|
// Use new selection
|
|
374
446
|
const splitTextNodes = Range.isRange(editor.selection)
|
|
375
|
-
? [
|
|
447
|
+
? [
|
|
448
|
+
...Editor.nodes(editor, {
|
|
449
|
+
at: editor.selection,
|
|
450
|
+
match: Text.isText,
|
|
451
|
+
}),
|
|
452
|
+
]
|
|
376
453
|
: []
|
|
377
454
|
const shouldRemoveMark =
|
|
378
455
|
splitTextNodes.length > 1 &&
|
|
@@ -397,17 +474,49 @@ export function createWithPortableTextMarkModel(
|
|
|
397
474
|
}
|
|
398
475
|
})
|
|
399
476
|
} else {
|
|
400
|
-
const
|
|
401
|
-
|
|
477
|
+
const [block, blockPath] = Editor.node(editor, editor.selection, {
|
|
478
|
+
depth: 1,
|
|
479
|
+
})
|
|
480
|
+
const lonelyEmptySpan =
|
|
481
|
+
editor.isTextBlock(block) &&
|
|
482
|
+
block.children.length === 1 &&
|
|
483
|
+
editor.isTextSpan(block.children[0]) &&
|
|
484
|
+
block.children[0].text === ''
|
|
485
|
+
? block.children[0]
|
|
486
|
+
: undefined
|
|
487
|
+
|
|
488
|
+
if (lonelyEmptySpan) {
|
|
489
|
+
const existingMarks = lonelyEmptySpan.marks ?? []
|
|
490
|
+
const existingMarksWithoutDecorator = existingMarks.filter(
|
|
491
|
+
(existingMark) => existingMark !== mark,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
Transforms.setNodes(
|
|
495
|
+
editor,
|
|
496
|
+
{
|
|
497
|
+
marks:
|
|
498
|
+
existingMarks.length === existingMarksWithoutDecorator.length
|
|
499
|
+
? [...existingMarks, mark]
|
|
500
|
+
: existingMarksWithoutDecorator,
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
at: blockPath,
|
|
504
|
+
match: (node) => editor.isTextSpan(node),
|
|
505
|
+
},
|
|
506
|
+
)
|
|
507
|
+
} else {
|
|
508
|
+
const existingMarks: string[] =
|
|
509
|
+
{
|
|
510
|
+
...(Editor.marks(editor) || {}),
|
|
511
|
+
}.marks || []
|
|
512
|
+
const marks = {
|
|
402
513
|
...(Editor.marks(editor) || {}),
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
514
|
+
marks: [...existingMarks, mark],
|
|
515
|
+
}
|
|
516
|
+
editor.marks = marks as Text
|
|
517
|
+
forceNewSelection()
|
|
518
|
+
return editor
|
|
407
519
|
}
|
|
408
|
-
editor.marks = marks as Text
|
|
409
|
-
forceNewSelection()
|
|
410
|
-
return editor
|
|
411
520
|
}
|
|
412
521
|
editor.onChange()
|
|
413
522
|
forceNewSelection()
|
|
@@ -422,10 +531,17 @@ export function createWithPortableTextMarkModel(
|
|
|
422
531
|
if (Range.isExpanded(selection)) {
|
|
423
532
|
Editor.withoutNormalizing(editor, () => {
|
|
424
533
|
// Split if needed
|
|
425
|
-
Transforms.setNodes(
|
|
534
|
+
Transforms.setNodes(
|
|
535
|
+
editor,
|
|
536
|
+
{},
|
|
537
|
+
{match: Text.isText, split: true, hanging: true},
|
|
538
|
+
)
|
|
426
539
|
if (editor.selection) {
|
|
427
540
|
const splitTextNodes = [
|
|
428
|
-
...Editor.nodes(editor, {
|
|
541
|
+
...Editor.nodes(editor, {
|
|
542
|
+
at: editor.selection,
|
|
543
|
+
match: Text.isText,
|
|
544
|
+
}),
|
|
429
545
|
]
|
|
430
546
|
splitTextNodes.forEach(([node, path]) => {
|
|
431
547
|
const block = editor.children[path[0]]
|
|
@@ -433,9 +549,10 @@ export function createWithPortableTextMarkModel(
|
|
|
433
549
|
Transforms.setNodes(
|
|
434
550
|
editor,
|
|
435
551
|
{
|
|
436
|
-
marks: (Array.isArray(node.marks)
|
|
437
|
-
|
|
438
|
-
|
|
552
|
+
marks: (Array.isArray(node.marks)
|
|
553
|
+
? node.marks
|
|
554
|
+
: []
|
|
555
|
+
).filter((eMark: string) => eMark !== mark),
|
|
439
556
|
_type: 'span',
|
|
440
557
|
},
|
|
441
558
|
{at: path},
|
|
@@ -446,17 +563,46 @@ export function createWithPortableTextMarkModel(
|
|
|
446
563
|
})
|
|
447
564
|
Editor.normalize(editor)
|
|
448
565
|
} else {
|
|
449
|
-
const
|
|
450
|
-
|
|
566
|
+
const [block, blockPath] = Editor.node(editor, selection, {
|
|
567
|
+
depth: 1,
|
|
568
|
+
})
|
|
569
|
+
const lonelyEmptySpan =
|
|
570
|
+
editor.isTextBlock(block) &&
|
|
571
|
+
block.children.length === 1 &&
|
|
572
|
+
editor.isTextSpan(block.children[0]) &&
|
|
573
|
+
block.children[0].text === ''
|
|
574
|
+
? block.children[0]
|
|
575
|
+
: undefined
|
|
576
|
+
|
|
577
|
+
if (lonelyEmptySpan) {
|
|
578
|
+
const existingMarks = lonelyEmptySpan.marks ?? []
|
|
579
|
+
const existingMarksWithoutDecorator = existingMarks.filter(
|
|
580
|
+
(existingMark) => existingMark !== mark,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
Transforms.setNodes(
|
|
584
|
+
editor,
|
|
585
|
+
{
|
|
586
|
+
marks: existingMarksWithoutDecorator,
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
at: blockPath,
|
|
590
|
+
match: (node) => editor.isTextSpan(node),
|
|
591
|
+
},
|
|
592
|
+
)
|
|
593
|
+
} else {
|
|
594
|
+
const existingMarks: string[] =
|
|
595
|
+
{
|
|
596
|
+
...(Editor.marks(editor) || {}),
|
|
597
|
+
}.marks || []
|
|
598
|
+
const marks = {
|
|
451
599
|
...(Editor.marks(editor) || {}),
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
forceNewSelection()
|
|
459
|
-
return editor
|
|
600
|
+
marks: existingMarks.filter((eMark) => eMark !== mark),
|
|
601
|
+
} as Text
|
|
602
|
+
editor.marks = {marks: marks.marks, _type: 'span'} as Text
|
|
603
|
+
forceNewSelection()
|
|
604
|
+
return editor
|
|
605
|
+
}
|
|
460
606
|
}
|
|
461
607
|
editor.onChange()
|
|
462
608
|
forceNewSelection()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {type Subject} from 'rxjs'
|
|
2
2
|
import {type BaseRange} from 'slate'
|
|
3
|
-
|
|
4
3
|
import {
|
|
5
4
|
type EditorChange,
|
|
6
5
|
type EditorSelection,
|
|
@@ -8,7 +7,10 @@ import {
|
|
|
8
7
|
type PortableTextSlateEditor,
|
|
9
8
|
} from '../../types/editor'
|
|
10
9
|
import {debugWithName} from '../../utils/debug'
|
|
11
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
toPortableTextRange,
|
|
12
|
+
type ObjectWithKeyAndType,
|
|
13
|
+
} from '../../utils/ranges'
|
|
12
14
|
import {SLATE_TO_PORTABLE_TEXT_RANGE} from '../../utils/weakMaps'
|
|
13
15
|
|
|
14
16
|
const debug = debugWithName('plugin:withPortableTextSelections')
|