@portabletext/editor 3.2.5 → 3.3.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.js +289 -146
- package/lib/index.js.map +1 -1
- package/package.json +4 -4
- package/src/behaviors/behavior.abstract.deserialize.ts +68 -84
- package/src/converters/converter.text-markdown.ts +67 -0
- package/src/converters/converters.core.ts +2 -0
- package/src/internal-utils/apply-operation-to-portable-text.test.ts +1806 -120
- package/src/internal-utils/apply-operation-to-portable-text.ts +277 -106
- package/src/internal-utils/portable-text-node.ts +2 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
-
import {createDraft, finishDraft, type WritableDraft} from 'immer'
|
|
3
2
|
import {Element, Path, type Node, type Operation} from 'slate'
|
|
4
3
|
import type {EditorSchema} from '../editor/editor-schema'
|
|
5
4
|
import type {EditorContext} from '../editor/editor-snapshot'
|
|
@@ -14,34 +13,37 @@ import {
|
|
|
14
13
|
isPartialSpanNode,
|
|
15
14
|
isSpanNode,
|
|
16
15
|
isTextBlockNode,
|
|
17
|
-
type
|
|
16
|
+
type EditorNode,
|
|
17
|
+
type ObjectNode,
|
|
18
18
|
type SpanNode,
|
|
19
19
|
type TextBlockNode,
|
|
20
20
|
} from './portable-text-node'
|
|
21
21
|
|
|
22
22
|
export function applyOperationToPortableText(
|
|
23
|
-
context: Pick<EditorContext, '
|
|
23
|
+
context: Pick<EditorContext, 'schema'>,
|
|
24
24
|
value: Array<PortableTextBlock>,
|
|
25
25
|
operation: OmitFromUnion<Operation, 'type', 'set_selection'>,
|
|
26
|
-
) {
|
|
27
|
-
const
|
|
26
|
+
): Array<PortableTextBlock> {
|
|
27
|
+
const root = {children: value} as EditorNode<EditorSchema>
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
|
-
|
|
30
|
+
const newRoot = applyOperationToPortableTextImmutable(
|
|
31
|
+
context,
|
|
32
|
+
root,
|
|
33
|
+
operation,
|
|
34
|
+
)
|
|
35
|
+
return newRoot.children as Array<PortableTextBlock>
|
|
31
36
|
} catch (e) {
|
|
32
37
|
console.error(e)
|
|
38
|
+
return value
|
|
33
39
|
}
|
|
34
|
-
|
|
35
|
-
return finishDraft(draft).children
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
function
|
|
39
|
-
context: Pick<EditorContext, '
|
|
40
|
-
root:
|
|
41
|
-
children: Array<PortableTextBlock>
|
|
42
|
-
}>,
|
|
42
|
+
function applyOperationToPortableTextImmutable(
|
|
43
|
+
context: Pick<EditorContext, 'schema'>,
|
|
44
|
+
root: EditorNode<EditorSchema>,
|
|
43
45
|
operation: OmitFromUnion<Operation, 'type', 'set_selection'>,
|
|
44
|
-
) {
|
|
46
|
+
): EditorNode<EditorSchema> {
|
|
45
47
|
switch (operation.type) {
|
|
46
48
|
case 'insert_node': {
|
|
47
49
|
const {path, node: insertedNode} = operation
|
|
@@ -49,11 +51,11 @@ function applyOperationToPortableTextDraft(
|
|
|
49
51
|
const index = path[path.length - 1]
|
|
50
52
|
|
|
51
53
|
if (!parent) {
|
|
52
|
-
|
|
54
|
+
return root
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
if (index > parent.children.length) {
|
|
56
|
-
|
|
58
|
+
return root
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
if (path.length === 1) {
|
|
@@ -61,8 +63,7 @@ function applyOperationToPortableTextDraft(
|
|
|
61
63
|
|
|
62
64
|
if (isTextBlockNode(context, insertedNode)) {
|
|
63
65
|
// Text blocks can be inserted as is
|
|
64
|
-
|
|
65
|
-
parent.children.splice(index, 0, {
|
|
66
|
+
const newBlock = {
|
|
66
67
|
...insertedNode,
|
|
67
68
|
children: insertedNode.children.map((child) => {
|
|
68
69
|
if ('__inline' in child) {
|
|
@@ -79,73 +80,88 @@ function applyOperationToPortableTextDraft(
|
|
|
79
80
|
|
|
80
81
|
return child
|
|
81
82
|
}),
|
|
82
|
-
}
|
|
83
|
+
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
return {
|
|
86
|
+
...root,
|
|
87
|
+
children: insertChildren(root.children, index, newBlock),
|
|
88
|
+
}
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
if (Element.isElement(insertedNode) && !('__inline' in insertedNode)) {
|
|
88
92
|
// Void blocks have to have their `value` spread onto the block
|
|
89
|
-
|
|
90
|
-
parent.children.splice(index, 0, {
|
|
93
|
+
const newBlock = {
|
|
91
94
|
_key: insertedNode._key,
|
|
92
95
|
_type: insertedNode._type,
|
|
93
96
|
...('value' in insertedNode &&
|
|
94
97
|
typeof insertedNode.value === 'object'
|
|
95
98
|
? insertedNode.value
|
|
96
99
|
: {}),
|
|
97
|
-
}
|
|
98
|
-
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...root,
|
|
104
|
+
children: insertChildren(root.children, index, newBlock),
|
|
105
|
+
}
|
|
99
106
|
}
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
if (path.length === 2) {
|
|
103
110
|
// Inserting children into blocks
|
|
111
|
+
const blockIndex = path[0]
|
|
104
112
|
|
|
105
113
|
if (!isTextBlockNode(context, parent)) {
|
|
106
114
|
// Only text blocks can have children
|
|
107
|
-
|
|
115
|
+
return root
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
let newChild: SpanNode<EditorSchema> | ObjectNode | undefined
|
|
119
|
+
|
|
110
120
|
if (isPartialSpanNode(insertedNode)) {
|
|
111
121
|
// Text nodes can be inserted as is
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
break
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if ('__inline' in insertedNode) {
|
|
122
|
+
newChild = insertedNode
|
|
123
|
+
} else if ('__inline' in insertedNode) {
|
|
118
124
|
// Void children have to have their `value` spread onto the block
|
|
119
|
-
|
|
120
|
-
parent.children.splice(index, 0, {
|
|
125
|
+
newChild = {
|
|
121
126
|
_key: insertedNode._key,
|
|
122
127
|
_type: insertedNode._type,
|
|
123
128
|
...('value' in insertedNode &&
|
|
124
129
|
typeof insertedNode.value === 'object'
|
|
125
130
|
? insertedNode.value
|
|
126
131
|
: {}),
|
|
127
|
-
}
|
|
128
|
-
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
return root
|
|
129
135
|
}
|
|
136
|
+
|
|
137
|
+
return updateTextBlockAtIndex(context, root, blockIndex, (block) => ({
|
|
138
|
+
...block,
|
|
139
|
+
children: insertChildren(block.children, index, newChild),
|
|
140
|
+
}))
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
|
|
143
|
+
return root
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
case 'insert_text': {
|
|
136
147
|
const {path, offset, text} = operation
|
|
137
|
-
if (text.length === 0)
|
|
138
|
-
const span = getSpan(context, root, path)
|
|
148
|
+
if (text.length === 0) return root
|
|
139
149
|
|
|
150
|
+
const span = getSpan(context, root, path)
|
|
140
151
|
if (!span) {
|
|
141
|
-
|
|
152
|
+
return root
|
|
142
153
|
}
|
|
143
154
|
|
|
155
|
+
const blockIndex = path[0]
|
|
156
|
+
const childIndex = path[1]
|
|
144
157
|
const before = span.text.slice(0, offset)
|
|
145
158
|
const after = span.text.slice(offset)
|
|
146
|
-
span
|
|
159
|
+
const newSpan = {...span, text: before + text + after}
|
|
147
160
|
|
|
148
|
-
|
|
161
|
+
return updateTextBlockAtIndex(context, root, blockIndex, (block) => ({
|
|
162
|
+
...block,
|
|
163
|
+
children: replaceChild(block.children, childIndex, newSpan),
|
|
164
|
+
}))
|
|
149
165
|
}
|
|
150
166
|
|
|
151
167
|
case 'merge_node': {
|
|
@@ -156,32 +172,50 @@ function applyOperationToPortableTextDraft(
|
|
|
156
172
|
const parent = getParent(context, root, path)
|
|
157
173
|
|
|
158
174
|
if (!node || !prev || !parent) {
|
|
159
|
-
|
|
175
|
+
return root
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
const index = path[path.length - 1]
|
|
163
179
|
|
|
164
180
|
if (isPartialSpanNode(node) && isPartialSpanNode(prev)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
// Merging spans
|
|
182
|
+
const blockIndex = path[0]
|
|
183
|
+
const newPrev = {...prev, text: prev.text + node.text}
|
|
184
|
+
|
|
185
|
+
return updateTextBlockAtIndex(context, root, blockIndex, (block) => {
|
|
186
|
+
const newChildren = replaceChild(
|
|
187
|
+
block.children,
|
|
188
|
+
index - 1,
|
|
189
|
+
newPrev as never,
|
|
190
|
+
)
|
|
191
|
+
return {
|
|
192
|
+
...block,
|
|
193
|
+
children: removeChildren(newChildren, index),
|
|
194
|
+
}
|
|
195
|
+
})
|
|
173
196
|
}
|
|
174
197
|
|
|
175
|
-
|
|
198
|
+
if (isTextBlockNode(context, node) && isTextBlockNode(context, prev)) {
|
|
199
|
+
// Merging blocks
|
|
200
|
+
const newPrev = {
|
|
201
|
+
...prev,
|
|
202
|
+
children: [...prev.children, ...node.children],
|
|
203
|
+
}
|
|
204
|
+
const newChildren = replaceChild(root.children, index - 1, newPrev)
|
|
205
|
+
return {
|
|
206
|
+
...root,
|
|
207
|
+
children: removeChildren(newChildren, index),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
176
210
|
|
|
177
|
-
|
|
211
|
+
return root
|
|
178
212
|
}
|
|
179
213
|
|
|
180
214
|
case 'move_node': {
|
|
181
215
|
const {path, newPath} = operation
|
|
182
216
|
|
|
183
217
|
if (Path.isAncestor(path, newPath)) {
|
|
184
|
-
|
|
218
|
+
return root
|
|
185
219
|
}
|
|
186
220
|
|
|
187
221
|
const node = getNode(context, root, path)
|
|
@@ -189,7 +223,32 @@ function applyOperationToPortableTextDraft(
|
|
|
189
223
|
const index = path[path.length - 1]
|
|
190
224
|
|
|
191
225
|
if (!node || !parent) {
|
|
192
|
-
|
|
226
|
+
return root
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// First, remove the node from its current position
|
|
230
|
+
let newRoot: EditorNode<EditorSchema>
|
|
231
|
+
|
|
232
|
+
if (path.length === 1) {
|
|
233
|
+
// Removing block from root
|
|
234
|
+
newRoot = {
|
|
235
|
+
...root,
|
|
236
|
+
children: removeChildren(root.children, index),
|
|
237
|
+
}
|
|
238
|
+
} else if (path.length === 2) {
|
|
239
|
+
// Removing child from block
|
|
240
|
+
const blockIndex = path[0]
|
|
241
|
+
newRoot = updateTextBlockAtIndex(
|
|
242
|
+
context,
|
|
243
|
+
root,
|
|
244
|
+
blockIndex,
|
|
245
|
+
(block) => ({
|
|
246
|
+
...block,
|
|
247
|
+
children: removeChildren(block.children, index),
|
|
248
|
+
}),
|
|
249
|
+
)
|
|
250
|
+
} else {
|
|
251
|
+
return root
|
|
193
252
|
}
|
|
194
253
|
|
|
195
254
|
// This is tricky, but since the `path` and `newPath` both refer to
|
|
@@ -198,55 +257,92 @@ function applyOperationToPortableTextDraft(
|
|
|
198
257
|
// of date. So instead of using the `op.newPath` directly, we
|
|
199
258
|
// transform `op.path` to ascertain what the `newPath` would be after
|
|
200
259
|
// the operation was applied.
|
|
201
|
-
parent.children.splice(index, 1)
|
|
202
260
|
const truePath = Path.transform(path, operation)!
|
|
203
|
-
const newParent = getNode(context, root, Path.parent(truePath))
|
|
204
261
|
const newIndex = truePath[truePath.length - 1]
|
|
205
262
|
|
|
206
|
-
if (
|
|
207
|
-
|
|
263
|
+
if (truePath.length === 1) {
|
|
264
|
+
// Inserting block at root
|
|
265
|
+
return {
|
|
266
|
+
...newRoot,
|
|
267
|
+
children: insertChildren(newRoot.children, newIndex, node as never),
|
|
268
|
+
}
|
|
208
269
|
}
|
|
209
270
|
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
271
|
+
if (truePath.length === 2) {
|
|
272
|
+
// Inserting child into block
|
|
273
|
+
const newBlockIndex = truePath[0]
|
|
274
|
+
const newParent = newRoot.children[newBlockIndex]
|
|
213
275
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
276
|
+
if (!newParent || !isTextBlockNode(context, newParent)) {
|
|
277
|
+
return root
|
|
278
|
+
}
|
|
217
279
|
|
|
218
|
-
|
|
280
|
+
return updateTextBlockAtIndex(
|
|
281
|
+
context,
|
|
282
|
+
newRoot,
|
|
283
|
+
newBlockIndex,
|
|
284
|
+
(block) => ({
|
|
285
|
+
...block,
|
|
286
|
+
children: insertChildren(block.children, newIndex, node as never),
|
|
287
|
+
}),
|
|
288
|
+
)
|
|
289
|
+
}
|
|
219
290
|
|
|
220
|
-
|
|
291
|
+
return root
|
|
221
292
|
}
|
|
222
293
|
|
|
223
294
|
case 'remove_node': {
|
|
224
295
|
const {path} = operation
|
|
225
296
|
const index = path[path.length - 1]
|
|
226
297
|
const parent = getParent(context, root, path)
|
|
227
|
-
parent?.children.splice(index, 1)
|
|
228
298
|
|
|
229
|
-
|
|
299
|
+
if (!parent) {
|
|
300
|
+
return root
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (path.length === 1) {
|
|
304
|
+
// Removing block from root
|
|
305
|
+
return {
|
|
306
|
+
...root,
|
|
307
|
+
children: removeChildren(root.children, index),
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (path.length === 2) {
|
|
312
|
+
// Removing child from block
|
|
313
|
+
const blockIndex = path[0]
|
|
314
|
+
return updateTextBlockAtIndex(context, root, blockIndex, (block) => ({
|
|
315
|
+
...block,
|
|
316
|
+
children: removeChildren(block.children, index),
|
|
317
|
+
}))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return root
|
|
230
321
|
}
|
|
231
322
|
|
|
232
323
|
case 'remove_text': {
|
|
233
324
|
const {path, offset, text} = operation
|
|
234
325
|
|
|
235
326
|
if (text.length === 0) {
|
|
236
|
-
|
|
327
|
+
return root
|
|
237
328
|
}
|
|
238
329
|
|
|
239
330
|
const span = getSpan(context, root, path)
|
|
240
331
|
|
|
241
332
|
if (!span) {
|
|
242
|
-
|
|
333
|
+
return root
|
|
243
334
|
}
|
|
244
335
|
|
|
336
|
+
const blockIndex = path[0]
|
|
337
|
+
const childIndex = path[1]
|
|
245
338
|
const before = span.text.slice(0, offset)
|
|
246
339
|
const after = span.text.slice(offset + text.length)
|
|
247
|
-
span
|
|
340
|
+
const newSpan = {...span, text: before + after}
|
|
248
341
|
|
|
249
|
-
|
|
342
|
+
return updateTextBlockAtIndex(context, root, blockIndex, (block) => ({
|
|
343
|
+
...block,
|
|
344
|
+
children: replaceChild(block.children, childIndex, newSpan as never),
|
|
345
|
+
}))
|
|
250
346
|
}
|
|
251
347
|
|
|
252
348
|
case 'set_node': {
|
|
@@ -255,11 +351,11 @@ function applyOperationToPortableTextDraft(
|
|
|
255
351
|
const node = getNode(context, root, path)
|
|
256
352
|
|
|
257
353
|
if (!node) {
|
|
258
|
-
|
|
354
|
+
return root
|
|
259
355
|
}
|
|
260
356
|
|
|
261
357
|
if (isEditorNode(node)) {
|
|
262
|
-
|
|
358
|
+
return root
|
|
263
359
|
}
|
|
264
360
|
|
|
265
361
|
if (isObjectNode(context, node)) {
|
|
@@ -274,6 +370,8 @@ function applyOperationToPortableTextDraft(
|
|
|
274
370
|
: {}
|
|
275
371
|
) as Partial<Node>
|
|
276
372
|
|
|
373
|
+
const newNode = {...node}
|
|
374
|
+
|
|
277
375
|
for (const key in newProperties) {
|
|
278
376
|
if (key === 'value') {
|
|
279
377
|
continue
|
|
@@ -282,9 +380,9 @@ function applyOperationToPortableTextDraft(
|
|
|
282
380
|
const value = newProperties[key as keyof Partial<Node>]
|
|
283
381
|
|
|
284
382
|
if (value == null) {
|
|
285
|
-
delete
|
|
383
|
+
delete newNode[key]
|
|
286
384
|
} else {
|
|
287
|
-
|
|
385
|
+
newNode[key] = value
|
|
288
386
|
}
|
|
289
387
|
}
|
|
290
388
|
|
|
@@ -294,7 +392,7 @@ function applyOperationToPortableTextDraft(
|
|
|
294
392
|
}
|
|
295
393
|
|
|
296
394
|
if (!newProperties.hasOwnProperty(key)) {
|
|
297
|
-
delete
|
|
395
|
+
delete newNode[key]
|
|
298
396
|
}
|
|
299
397
|
}
|
|
300
398
|
|
|
@@ -302,134 +400,207 @@ function applyOperationToPortableTextDraft(
|
|
|
302
400
|
const value = valueAfter[key as keyof Partial<Node>]
|
|
303
401
|
|
|
304
402
|
if (value == null) {
|
|
305
|
-
delete
|
|
403
|
+
delete newNode[key]
|
|
306
404
|
} else {
|
|
307
|
-
|
|
405
|
+
newNode[key] = value
|
|
308
406
|
}
|
|
309
407
|
}
|
|
310
408
|
|
|
311
409
|
for (const key in valueBefore) {
|
|
312
410
|
if (!valueAfter.hasOwnProperty(key)) {
|
|
313
|
-
delete
|
|
411
|
+
delete newNode[key]
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (path.length === 1) {
|
|
416
|
+
return {
|
|
417
|
+
...root,
|
|
418
|
+
children: replaceChild(root.children, path[0], newNode),
|
|
314
419
|
}
|
|
315
420
|
}
|
|
316
421
|
|
|
317
|
-
|
|
422
|
+
if (path.length === 2) {
|
|
423
|
+
return updateTextBlockAtIndex(context, root, path[0], (block) => ({
|
|
424
|
+
...block,
|
|
425
|
+
children: replaceChild(block.children, path[1], newNode),
|
|
426
|
+
}))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return root
|
|
318
430
|
}
|
|
319
431
|
|
|
320
432
|
if (isTextBlockNode(context, node)) {
|
|
433
|
+
const newNode = {...node}
|
|
434
|
+
|
|
321
435
|
for (const key in newProperties) {
|
|
322
436
|
if (key === 'children' || key === 'text') {
|
|
323
|
-
|
|
437
|
+
continue
|
|
324
438
|
}
|
|
325
439
|
|
|
326
440
|
const value = newProperties[key as keyof Partial<Node>]
|
|
327
441
|
|
|
328
442
|
if (value == null) {
|
|
329
|
-
delete
|
|
443
|
+
delete newNode[key]
|
|
330
444
|
} else {
|
|
331
|
-
|
|
445
|
+
newNode[key] = value
|
|
332
446
|
}
|
|
333
447
|
}
|
|
334
448
|
|
|
335
449
|
// properties that were previously defined, but are now missing, must be deleted
|
|
336
450
|
for (const key in properties) {
|
|
337
451
|
if (!newProperties.hasOwnProperty(key)) {
|
|
338
|
-
delete
|
|
452
|
+
delete newNode[key]
|
|
339
453
|
}
|
|
340
454
|
}
|
|
341
455
|
|
|
342
|
-
|
|
456
|
+
return {
|
|
457
|
+
...root,
|
|
458
|
+
children: replaceChild(root.children, path[0], newNode),
|
|
459
|
+
}
|
|
343
460
|
}
|
|
344
461
|
|
|
345
462
|
if (isPartialSpanNode(node)) {
|
|
463
|
+
const newNode = {...node}
|
|
464
|
+
|
|
346
465
|
for (const key in newProperties) {
|
|
347
466
|
if (key === 'text') {
|
|
348
|
-
|
|
467
|
+
continue
|
|
349
468
|
}
|
|
350
469
|
|
|
351
470
|
const value = newProperties[key as keyof Partial<Node>]
|
|
352
471
|
|
|
353
472
|
if (value == null) {
|
|
354
|
-
delete
|
|
473
|
+
delete newNode[key]
|
|
355
474
|
} else {
|
|
356
|
-
|
|
475
|
+
newNode[key] = value
|
|
357
476
|
}
|
|
358
477
|
}
|
|
359
478
|
|
|
360
479
|
// properties that were previously defined, but are now missing, must be deleted
|
|
361
480
|
for (const key in properties) {
|
|
362
481
|
if (!newProperties.hasOwnProperty(key)) {
|
|
363
|
-
delete
|
|
482
|
+
delete newNode[key]
|
|
364
483
|
}
|
|
365
484
|
}
|
|
366
485
|
|
|
367
|
-
|
|
486
|
+
return updateTextBlockAtIndex(context, root, path[0], (block) => ({
|
|
487
|
+
...block,
|
|
488
|
+
children: replaceChild(block.children, path[1], newNode),
|
|
489
|
+
}))
|
|
368
490
|
}
|
|
369
491
|
|
|
370
|
-
|
|
492
|
+
return root
|
|
371
493
|
}
|
|
372
494
|
|
|
373
495
|
case 'split_node': {
|
|
374
496
|
const {path, position, properties} = operation
|
|
375
497
|
|
|
376
498
|
if (path.length === 0) {
|
|
377
|
-
|
|
499
|
+
return root
|
|
378
500
|
}
|
|
379
501
|
|
|
380
502
|
const parent = getParent(context, root, path)
|
|
381
503
|
const index = path[path.length - 1]
|
|
382
504
|
|
|
383
505
|
if (!parent) {
|
|
384
|
-
|
|
506
|
+
return root
|
|
385
507
|
}
|
|
386
508
|
|
|
387
509
|
if (isEditorNode(parent)) {
|
|
388
510
|
const block = getBlock(root, path)
|
|
389
511
|
|
|
390
512
|
if (!block || !isTextBlockNode(context, block)) {
|
|
391
|
-
|
|
513
|
+
return root
|
|
392
514
|
}
|
|
393
515
|
|
|
394
516
|
const before = block.children.slice(0, position)
|
|
395
517
|
const after = block.children.slice(position)
|
|
396
|
-
block
|
|
518
|
+
const updatedTextBlockNode = {...block, children: before}
|
|
397
519
|
|
|
398
520
|
// _key is deliberately left out
|
|
399
521
|
const newTextBlockNode = {
|
|
400
522
|
...properties,
|
|
401
523
|
children: after,
|
|
402
524
|
_type: context.schema.block.name,
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
parent.children.splice(index + 1, 0, newTextBlockNode)
|
|
525
|
+
}
|
|
406
526
|
|
|
407
|
-
|
|
527
|
+
return {
|
|
528
|
+
...root,
|
|
529
|
+
children: insertChildren(
|
|
530
|
+
replaceChild(root.children, index, updatedTextBlockNode),
|
|
531
|
+
index + 1,
|
|
532
|
+
newTextBlockNode,
|
|
533
|
+
),
|
|
534
|
+
}
|
|
408
535
|
}
|
|
409
536
|
|
|
410
537
|
if (isTextBlockNode(context, parent)) {
|
|
411
538
|
const node = getNode(context, root, path)
|
|
412
539
|
|
|
413
540
|
if (!node || !isSpanNode(context, node)) {
|
|
414
|
-
|
|
541
|
+
return root
|
|
415
542
|
}
|
|
416
543
|
|
|
544
|
+
const blockIndex = path[0]
|
|
417
545
|
const before = node.text.slice(0, position)
|
|
418
546
|
const after = node.text.slice(position)
|
|
419
|
-
node
|
|
547
|
+
const updatedSpanNode = {...node, text: before}
|
|
420
548
|
|
|
421
549
|
// _key is deliberately left out
|
|
422
550
|
const newSpanNode = {
|
|
423
551
|
...properties,
|
|
424
552
|
text: after,
|
|
425
|
-
}
|
|
553
|
+
}
|
|
426
554
|
|
|
427
|
-
|
|
555
|
+
return updateTextBlockAtIndex(context, root, blockIndex, (block) => {
|
|
556
|
+
return {
|
|
557
|
+
...block,
|
|
558
|
+
children: insertChildren(
|
|
559
|
+
replaceChild(block.children, index, updatedSpanNode),
|
|
560
|
+
index + 1,
|
|
561
|
+
newSpanNode,
|
|
562
|
+
),
|
|
563
|
+
}
|
|
564
|
+
})
|
|
428
565
|
}
|
|
429
566
|
|
|
430
|
-
|
|
567
|
+
return root
|
|
431
568
|
}
|
|
432
569
|
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function insertChildren<T>(children: T[], index: number, ...nodes: T[]): T[] {
|
|
573
|
+
return [...children.slice(0, index), ...nodes, ...children.slice(index)]
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function removeChildren<T>(children: T[], index: number, count = 1): T[] {
|
|
577
|
+
return [...children.slice(0, index), ...children.slice(index + count)]
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function replaceChild<T>(children: T[], index: number, newChild: T): T[] {
|
|
581
|
+
return [...children.slice(0, index), newChild, ...children.slice(index + 1)]
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function updateTextBlockAtIndex(
|
|
585
|
+
context: Pick<EditorContext, 'schema'>,
|
|
586
|
+
root: EditorNode<EditorSchema>,
|
|
587
|
+
blockIndex: number,
|
|
588
|
+
updater: (block: TextBlockNode<EditorSchema>) => TextBlockNode<EditorSchema>,
|
|
589
|
+
): EditorNode<EditorSchema> {
|
|
590
|
+
const block = root.children.at(blockIndex)
|
|
591
|
+
|
|
592
|
+
if (!block) {
|
|
593
|
+
return root
|
|
594
|
+
}
|
|
433
595
|
|
|
434
|
-
|
|
596
|
+
if (!isTextBlockNode(context, block)) {
|
|
597
|
+
return root
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const newBlock = updater(block)
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
...root,
|
|
604
|
+
children: replaceChild(root.children, blockIndex, newBlock),
|
|
605
|
+
}
|
|
435
606
|
}
|
|
@@ -31,7 +31,7 @@ export function isEditorNode<TEditorSchema extends EditorSchema>(
|
|
|
31
31
|
//////////
|
|
32
32
|
|
|
33
33
|
export type TextBlockNode<TEditorSchema extends EditorSchema> = {
|
|
34
|
-
_key
|
|
34
|
+
_key?: string
|
|
35
35
|
_type: TEditorSchema['block']['name']
|
|
36
36
|
children: Array<SpanNode<TEditorSchema> | ObjectNode>
|
|
37
37
|
[other: string]: unknown
|
|
@@ -47,7 +47,7 @@ export function isTextBlockNode<TEditorSchema extends EditorSchema>(
|
|
|
47
47
|
//////////
|
|
48
48
|
|
|
49
49
|
export type SpanNode<TEditorSchema extends EditorSchema> = {
|
|
50
|
-
_key
|
|
50
|
+
_key?: string
|
|
51
51
|
_type?: TEditorSchema['span']['name']
|
|
52
52
|
text: string
|
|
53
53
|
[other: string]: unknown
|