@portabletext/editor 1.50.2 → 1.50.4
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/behaviors/index.d.cts +1 -0
- package/lib/behaviors/index.d.ts +1 -0
- package/lib/index.cjs +577 -286
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +15 -2
- package/lib/index.d.ts +15 -2
- package/lib/index.js +584 -292
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +7 -0
- package/lib/plugins/index.d.ts +7 -0
- package/lib/selectors/index.d.cts +1 -0
- package/lib/selectors/index.d.ts +1 -0
- package/lib/utils/index.d.cts +1 -0
- package/lib/utils/index.d.ts +1 -0
- package/package.json +14 -13
- package/src/editor/PortableTextEditor.tsx +22 -22
- package/src/editor/create-slate-editor.tsx +9 -1
- package/src/editor/editor-selector.ts +1 -5
- package/src/editor/editor-snapshot.ts +1 -3
- package/src/editor/plugins/createWithPatches.ts +37 -75
- package/src/editor/plugins/slate-plugin.update-value.ts +30 -0
- package/src/editor/plugins/with-plugins.ts +8 -4
- package/src/editor/relay-machine.ts +9 -0
- package/src/internal-utils/apply-operation-to-portable-text.test.ts +175 -0
- package/src/internal-utils/apply-operation-to-portable-text.ts +435 -0
- package/src/internal-utils/create-placeholder-block.ts +20 -0
- package/src/internal-utils/{__tests__/operationToPatches.test.ts → operation-to-patches.test.ts} +44 -39
- package/src/internal-utils/operation-to-patches.ts +467 -0
- package/src/internal-utils/portable-text-node.ts +209 -0
- package/src/types/editor.ts +8 -2
- package/src/internal-utils/__tests__/patchToOperations.test.ts +0 -312
- package/src/internal-utils/operationToPatches.ts +0 -489
- package/src/internal-utils/slate-children-to-blocks.ts +0 -49
package/src/internal-utils/{__tests__/operationToPatches.test.ts → operation-to-patches.test.ts}
RENAMED
|
@@ -2,14 +2,21 @@ import type {PortableTextTextBlock} from '@sanity/types'
|
|
|
2
2
|
import {createEditor, type Descendant} from 'slate'
|
|
3
3
|
import {beforeEach, describe, expect, it} from 'vitest'
|
|
4
4
|
import {createActor} from 'xstate'
|
|
5
|
-
import {schemaType} from '
|
|
6
|
-
import {editorMachine} from '
|
|
7
|
-
import {legacySchemaToEditorSchema} from '
|
|
8
|
-
import {defaultKeyGenerator} from '
|
|
9
|
-
import {createLegacySchema} from '
|
|
10
|
-
import {withPlugins} from '
|
|
11
|
-
import {relayMachine} from '
|
|
12
|
-
import {
|
|
5
|
+
import {schemaType} from '../editor/__tests__/PortableTextEditorTester'
|
|
6
|
+
import {editorMachine} from '../editor/editor-machine'
|
|
7
|
+
import {legacySchemaToEditorSchema} from '../editor/editor-schema'
|
|
8
|
+
import {defaultKeyGenerator} from '../editor/key-generator'
|
|
9
|
+
import {createLegacySchema} from '../editor/legacy-schema'
|
|
10
|
+
import {withPlugins} from '../editor/plugins/with-plugins'
|
|
11
|
+
import {relayMachine} from '../editor/relay-machine'
|
|
12
|
+
import {
|
|
13
|
+
insertNodePatch,
|
|
14
|
+
insertTextPatch,
|
|
15
|
+
mergeNodePatch,
|
|
16
|
+
removeNodePatch,
|
|
17
|
+
removeTextPatch,
|
|
18
|
+
splitNodePatch,
|
|
19
|
+
} from './operation-to-patches'
|
|
13
20
|
|
|
14
21
|
const legacySchema = createLegacySchema(schemaType)
|
|
15
22
|
const schemaTypes = legacySchemaToEditorSchema(legacySchema)
|
|
@@ -21,7 +28,6 @@ const editorActor = createActor(editorMachine, {
|
|
|
21
28
|
},
|
|
22
29
|
})
|
|
23
30
|
const relayActor = createActor(relayMachine)
|
|
24
|
-
const operationToPatches = createOperationToPatches(editorActor)
|
|
25
31
|
|
|
26
32
|
const editor = withPlugins(createEditor(), {
|
|
27
33
|
editorActor,
|
|
@@ -58,8 +64,9 @@ describe('operationToPatches', () => {
|
|
|
58
64
|
|
|
59
65
|
it('translates void items correctly when splitting spans', () => {
|
|
60
66
|
expect(
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
splitNodePatch(
|
|
68
|
+
schemaTypes,
|
|
69
|
+
editor.children,
|
|
63
70
|
{
|
|
64
71
|
type: 'split_node',
|
|
65
72
|
path: [0, 0],
|
|
@@ -111,8 +118,9 @@ describe('operationToPatches', () => {
|
|
|
111
118
|
|
|
112
119
|
it('produce correct insert block patch', () => {
|
|
113
120
|
expect(
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
insertNodePatch(
|
|
122
|
+
schemaTypes,
|
|
123
|
+
editor.children,
|
|
116
124
|
{
|
|
117
125
|
type: 'insert_node',
|
|
118
126
|
path: [0],
|
|
@@ -152,8 +160,9 @@ describe('operationToPatches', () => {
|
|
|
152
160
|
editor.children = []
|
|
153
161
|
editor.onChange()
|
|
154
162
|
expect(
|
|
155
|
-
|
|
156
|
-
|
|
163
|
+
insertNodePatch(
|
|
164
|
+
schemaTypes,
|
|
165
|
+
editor.children,
|
|
157
166
|
{
|
|
158
167
|
type: 'insert_node',
|
|
159
168
|
path: [0],
|
|
@@ -194,8 +203,9 @@ describe('operationToPatches', () => {
|
|
|
194
203
|
|
|
195
204
|
it('produce correct insert child patch', () => {
|
|
196
205
|
expect(
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
insertNodePatch(
|
|
207
|
+
schemaTypes,
|
|
208
|
+
editor.children,
|
|
199
209
|
{
|
|
200
210
|
type: 'insert_node',
|
|
201
211
|
path: [0, 3],
|
|
@@ -240,15 +250,15 @@ describe('operationToPatches', () => {
|
|
|
240
250
|
;(editor.children[0] as PortableTextTextBlock).children[2].text = '1'
|
|
241
251
|
editor.onChange()
|
|
242
252
|
expect(
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
insertTextPatch(
|
|
254
|
+
editorActor.getSnapshot().context.schema,
|
|
255
|
+
editor.children,
|
|
245
256
|
{
|
|
246
257
|
type: 'insert_text',
|
|
247
258
|
path: [0, 2],
|
|
248
259
|
text: '1',
|
|
249
260
|
offset: 0,
|
|
250
261
|
},
|
|
251
|
-
|
|
252
262
|
createDefaultValue(),
|
|
253
263
|
),
|
|
254
264
|
).toMatchInlineSnapshot(`
|
|
@@ -277,8 +287,9 @@ describe('operationToPatches', () => {
|
|
|
277
287
|
const before = createDefaultValue()
|
|
278
288
|
;(before[0] as PortableTextTextBlock).children[2].text = '1'
|
|
279
289
|
expect(
|
|
280
|
-
|
|
281
|
-
|
|
290
|
+
removeTextPatch(
|
|
291
|
+
editorActor.getSnapshot().context.schema,
|
|
292
|
+
editor.children,
|
|
282
293
|
{
|
|
283
294
|
type: 'remove_text',
|
|
284
295
|
path: [0, 2],
|
|
@@ -312,8 +323,9 @@ describe('operationToPatches', () => {
|
|
|
312
323
|
|
|
313
324
|
it('produces correct remove child patch', () => {
|
|
314
325
|
expect(
|
|
315
|
-
|
|
316
|
-
|
|
326
|
+
removeNodePatch(
|
|
327
|
+
editorActor.getSnapshot().context.schema,
|
|
328
|
+
createDefaultValue(),
|
|
317
329
|
{
|
|
318
330
|
type: 'remove_node',
|
|
319
331
|
path: [0, 1],
|
|
@@ -325,8 +337,6 @@ describe('operationToPatches', () => {
|
|
|
325
337
|
children: [{_type: 'span', _key: 'bogus', text: '', marks: []}],
|
|
326
338
|
},
|
|
327
339
|
},
|
|
328
|
-
|
|
329
|
-
createDefaultValue(),
|
|
330
340
|
),
|
|
331
341
|
).toMatchInlineSnapshot(`
|
|
332
342
|
[
|
|
@@ -349,16 +359,11 @@ describe('operationToPatches', () => {
|
|
|
349
359
|
it('produce correct remove block patch', () => {
|
|
350
360
|
const val = createDefaultValue()
|
|
351
361
|
expect(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
node: val[0],
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
val,
|
|
361
|
-
),
|
|
362
|
+
removeNodePatch(editorActor.getSnapshot().context.schema, val, {
|
|
363
|
+
type: 'remove_node',
|
|
364
|
+
path: [0],
|
|
365
|
+
node: val[0],
|
|
366
|
+
}),
|
|
362
367
|
).toMatchInlineSnapshot(`
|
|
363
368
|
[
|
|
364
369
|
{
|
|
@@ -386,15 +391,15 @@ describe('operationToPatches', () => {
|
|
|
386
391
|
block.children[2].text = '1234'
|
|
387
392
|
editor.onChange()
|
|
388
393
|
expect(
|
|
389
|
-
|
|
390
|
-
|
|
394
|
+
mergeNodePatch(
|
|
395
|
+
schemaTypes,
|
|
396
|
+
editor.children,
|
|
391
397
|
{
|
|
392
398
|
type: 'merge_node',
|
|
393
399
|
path: [0, 3],
|
|
394
400
|
position: 2,
|
|
395
401
|
properties: {text: '1234'},
|
|
396
402
|
},
|
|
397
|
-
|
|
398
403
|
val,
|
|
399
404
|
),
|
|
400
405
|
).toMatchInlineSnapshot(`
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import {
|
|
2
|
+
diffMatchPatch,
|
|
3
|
+
insert,
|
|
4
|
+
set,
|
|
5
|
+
setIfMissing,
|
|
6
|
+
unset,
|
|
7
|
+
type InsertPosition,
|
|
8
|
+
type Patch,
|
|
9
|
+
} from '@portabletext/patches'
|
|
10
|
+
import type {Path, PortableTextSpan, PortableTextTextBlock} from '@sanity/types'
|
|
11
|
+
import {get, isUndefined, omitBy} from 'lodash'
|
|
12
|
+
import {
|
|
13
|
+
Text,
|
|
14
|
+
type Descendant,
|
|
15
|
+
type InsertNodeOperation,
|
|
16
|
+
type InsertTextOperation,
|
|
17
|
+
type MergeNodeOperation,
|
|
18
|
+
type MoveNodeOperation,
|
|
19
|
+
type RemoveNodeOperation,
|
|
20
|
+
type RemoveTextOperation,
|
|
21
|
+
type SetNodeOperation,
|
|
22
|
+
type SplitNodeOperation,
|
|
23
|
+
} from 'slate'
|
|
24
|
+
import type {EditorSchema} from '../editor/editor-schema'
|
|
25
|
+
import {isSpan, isTextBlock} from './parse-blocks'
|
|
26
|
+
import {fromSlateValue} from './values'
|
|
27
|
+
|
|
28
|
+
export function insertTextPatch(
|
|
29
|
+
schema: EditorSchema,
|
|
30
|
+
children: Descendant[],
|
|
31
|
+
operation: InsertTextOperation,
|
|
32
|
+
beforeValue: Descendant[],
|
|
33
|
+
): Array<Patch> {
|
|
34
|
+
const block =
|
|
35
|
+
isTextBlock({schema}, children[operation.path[0]]) &&
|
|
36
|
+
children[operation.path[0]]
|
|
37
|
+
if (!block) {
|
|
38
|
+
throw new Error('Could not find block')
|
|
39
|
+
}
|
|
40
|
+
const textChild =
|
|
41
|
+
isTextBlock({schema}, block) &&
|
|
42
|
+
isSpan({schema}, block.children[operation.path[1]]) &&
|
|
43
|
+
(block.children[operation.path[1]] as PortableTextSpan)
|
|
44
|
+
if (!textChild) {
|
|
45
|
+
throw new Error('Could not find child')
|
|
46
|
+
}
|
|
47
|
+
const path: Path = [
|
|
48
|
+
{_key: block._key},
|
|
49
|
+
'children',
|
|
50
|
+
{_key: textChild._key},
|
|
51
|
+
'text',
|
|
52
|
+
]
|
|
53
|
+
const prevBlock = beforeValue[operation.path[0]]
|
|
54
|
+
const prevChild =
|
|
55
|
+
isTextBlock({schema}, prevBlock) && prevBlock.children[operation.path[1]]
|
|
56
|
+
const prevText = isSpan({schema}, prevChild) ? prevChild.text : ''
|
|
57
|
+
const patch = diffMatchPatch(prevText, textChild.text, path)
|
|
58
|
+
return patch.value.length ? [patch] : []
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function removeTextPatch(
|
|
62
|
+
schema: EditorSchema,
|
|
63
|
+
children: Descendant[],
|
|
64
|
+
operation: RemoveTextOperation,
|
|
65
|
+
beforeValue: Descendant[],
|
|
66
|
+
): Array<Patch> {
|
|
67
|
+
const block = children[operation.path[0]]
|
|
68
|
+
if (!block) {
|
|
69
|
+
throw new Error('Could not find block')
|
|
70
|
+
}
|
|
71
|
+
const child =
|
|
72
|
+
(isTextBlock({schema}, block) && block.children[operation.path[1]]) ||
|
|
73
|
+
undefined
|
|
74
|
+
const textChild: PortableTextSpan | undefined = isSpan({schema}, child)
|
|
75
|
+
? child
|
|
76
|
+
: undefined
|
|
77
|
+
if (child && !textChild) {
|
|
78
|
+
throw new Error('Expected span')
|
|
79
|
+
}
|
|
80
|
+
if (!textChild) {
|
|
81
|
+
throw new Error('Could not find child')
|
|
82
|
+
}
|
|
83
|
+
const path: Path = [
|
|
84
|
+
{_key: block._key},
|
|
85
|
+
'children',
|
|
86
|
+
{_key: textChild._key},
|
|
87
|
+
'text',
|
|
88
|
+
]
|
|
89
|
+
const beforeBlock = beforeValue[operation.path[0]]
|
|
90
|
+
const prevTextChild =
|
|
91
|
+
isTextBlock({schema}, beforeBlock) &&
|
|
92
|
+
beforeBlock.children[operation.path[1]]
|
|
93
|
+
const prevText = isSpan({schema}, prevTextChild) && prevTextChild.text
|
|
94
|
+
const patch = diffMatchPatch(prevText || '', textChild.text, path)
|
|
95
|
+
return patch.value ? [patch] : []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function setNodePatch(
|
|
99
|
+
schema: EditorSchema,
|
|
100
|
+
children: Descendant[],
|
|
101
|
+
operation: SetNodeOperation,
|
|
102
|
+
): Array<Patch> {
|
|
103
|
+
if (operation.path.length === 1) {
|
|
104
|
+
const block = children[operation.path[0]]
|
|
105
|
+
if (typeof block._key !== 'string') {
|
|
106
|
+
throw new Error('Expected block to have a _key')
|
|
107
|
+
}
|
|
108
|
+
const setNode = omitBy(
|
|
109
|
+
{...children[operation.path[0]], ...operation.newProperties},
|
|
110
|
+
isUndefined,
|
|
111
|
+
) as unknown as Descendant
|
|
112
|
+
return [
|
|
113
|
+
set(fromSlateValue([setNode], schema.block.name)[0], [
|
|
114
|
+
{_key: block._key},
|
|
115
|
+
]),
|
|
116
|
+
]
|
|
117
|
+
} else if (operation.path.length === 2) {
|
|
118
|
+
const block = children[operation.path[0]]
|
|
119
|
+
if (isTextBlock({schema}, block)) {
|
|
120
|
+
const child = block.children[operation.path[1]]
|
|
121
|
+
if (child) {
|
|
122
|
+
const blockKey = block._key
|
|
123
|
+
const childKey = child._key
|
|
124
|
+
const patches: Patch[] = []
|
|
125
|
+
const keys = Object.keys(operation.newProperties)
|
|
126
|
+
keys.forEach((keyName) => {
|
|
127
|
+
// Special case for setting _key on a child. We have to target it by index and not the _key.
|
|
128
|
+
if (keys.length === 1 && keyName === '_key') {
|
|
129
|
+
const val = get(operation.newProperties, keyName)
|
|
130
|
+
patches.push(
|
|
131
|
+
set(val, [
|
|
132
|
+
{_key: blockKey},
|
|
133
|
+
'children',
|
|
134
|
+
block.children.indexOf(child),
|
|
135
|
+
keyName,
|
|
136
|
+
]),
|
|
137
|
+
)
|
|
138
|
+
} else {
|
|
139
|
+
const val = get(operation.newProperties, keyName)
|
|
140
|
+
patches.push(
|
|
141
|
+
set(val, [
|
|
142
|
+
{_key: blockKey},
|
|
143
|
+
'children',
|
|
144
|
+
{_key: childKey},
|
|
145
|
+
keyName,
|
|
146
|
+
]),
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
return patches
|
|
151
|
+
}
|
|
152
|
+
throw new Error('Could not find a valid child')
|
|
153
|
+
}
|
|
154
|
+
throw new Error('Could not find a valid block')
|
|
155
|
+
} else {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Unexpected path encountered: ${JSON.stringify(operation.path)}`,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function insertNodePatch(
|
|
163
|
+
schema: EditorSchema,
|
|
164
|
+
children: Descendant[],
|
|
165
|
+
operation: InsertNodeOperation,
|
|
166
|
+
beforeValue: Descendant[],
|
|
167
|
+
): Array<Patch> {
|
|
168
|
+
const block = beforeValue[operation.path[0]]
|
|
169
|
+
if (operation.path.length === 1) {
|
|
170
|
+
const position = operation.path[0] === 0 ? 'before' : 'after'
|
|
171
|
+
const beforeBlock = beforeValue[operation.path[0] - 1]
|
|
172
|
+
const targetKey = operation.path[0] === 0 ? block?._key : beforeBlock?._key
|
|
173
|
+
if (targetKey) {
|
|
174
|
+
return [
|
|
175
|
+
insert(
|
|
176
|
+
[
|
|
177
|
+
fromSlateValue(
|
|
178
|
+
[operation.node as Descendant],
|
|
179
|
+
schema.block.name,
|
|
180
|
+
)[0],
|
|
181
|
+
],
|
|
182
|
+
position,
|
|
183
|
+
[{_key: targetKey}],
|
|
184
|
+
),
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
return [
|
|
188
|
+
setIfMissing(beforeValue, []),
|
|
189
|
+
insert(
|
|
190
|
+
[fromSlateValue([operation.node as Descendant], schema.block.name)[0]],
|
|
191
|
+
'before',
|
|
192
|
+
[operation.path[0]],
|
|
193
|
+
),
|
|
194
|
+
]
|
|
195
|
+
} else if (
|
|
196
|
+
isTextBlock({schema}, block) &&
|
|
197
|
+
operation.path.length === 2 &&
|
|
198
|
+
children[operation.path[0]]
|
|
199
|
+
) {
|
|
200
|
+
const position =
|
|
201
|
+
block.children.length === 0 || !block.children[operation.path[1] - 1]
|
|
202
|
+
? 'before'
|
|
203
|
+
: 'after'
|
|
204
|
+
const node = {...operation.node} as Descendant
|
|
205
|
+
if (!node._type && Text.isText(node)) {
|
|
206
|
+
node._type = 'span'
|
|
207
|
+
node.marks = []
|
|
208
|
+
}
|
|
209
|
+
const blk = fromSlateValue(
|
|
210
|
+
[
|
|
211
|
+
{
|
|
212
|
+
_key: 'bogus',
|
|
213
|
+
_type: schema.block.name,
|
|
214
|
+
children: [node],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
schema.block.name,
|
|
218
|
+
)[0] as PortableTextTextBlock
|
|
219
|
+
const child = blk.children[0]
|
|
220
|
+
return [
|
|
221
|
+
insert([child], position, [
|
|
222
|
+
{_key: block._key},
|
|
223
|
+
'children',
|
|
224
|
+
block.children.length <= 1 || !block.children[operation.path[1] - 1]
|
|
225
|
+
? 0
|
|
226
|
+
: {_key: block.children[operation.path[1] - 1]._key},
|
|
227
|
+
]),
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
return []
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function splitNodePatch(
|
|
234
|
+
schema: EditorSchema,
|
|
235
|
+
children: Descendant[],
|
|
236
|
+
operation: SplitNodeOperation,
|
|
237
|
+
beforeValue: Descendant[],
|
|
238
|
+
): Array<Patch> {
|
|
239
|
+
const patches: Patch[] = []
|
|
240
|
+
const splitBlock = children[operation.path[0]]
|
|
241
|
+
if (!isTextBlock({schema}, splitBlock)) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Block with path ${JSON.stringify(
|
|
244
|
+
operation.path[0],
|
|
245
|
+
)} is not a text block and can't be split`,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
if (operation.path.length === 1) {
|
|
249
|
+
const oldBlock = beforeValue[operation.path[0]]
|
|
250
|
+
if (isTextBlock({schema}, oldBlock)) {
|
|
251
|
+
const targetValue = fromSlateValue(
|
|
252
|
+
[children[operation.path[0] + 1]],
|
|
253
|
+
schema.block.name,
|
|
254
|
+
)[0]
|
|
255
|
+
if (targetValue) {
|
|
256
|
+
patches.push(insert([targetValue], 'after', [{_key: splitBlock._key}]))
|
|
257
|
+
const spansToUnset = oldBlock.children.slice(operation.position)
|
|
258
|
+
spansToUnset.forEach((span) => {
|
|
259
|
+
const path = [{_key: oldBlock._key}, 'children', {_key: span._key}]
|
|
260
|
+
patches.push(unset(path))
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return patches
|
|
265
|
+
}
|
|
266
|
+
if (operation.path.length === 2) {
|
|
267
|
+
const splitSpan = splitBlock.children[operation.path[1]]
|
|
268
|
+
if (isSpan({schema}, splitSpan)) {
|
|
269
|
+
const targetSpans = (
|
|
270
|
+
fromSlateValue(
|
|
271
|
+
[
|
|
272
|
+
{
|
|
273
|
+
...splitBlock,
|
|
274
|
+
children: splitBlock.children.slice(
|
|
275
|
+
operation.path[1] + 1,
|
|
276
|
+
operation.path[1] + 2,
|
|
277
|
+
),
|
|
278
|
+
} as Descendant,
|
|
279
|
+
],
|
|
280
|
+
schema.block.name,
|
|
281
|
+
)[0] as PortableTextTextBlock
|
|
282
|
+
).children
|
|
283
|
+
|
|
284
|
+
patches.push(
|
|
285
|
+
insert(targetSpans, 'after', [
|
|
286
|
+
{_key: splitBlock._key},
|
|
287
|
+
'children',
|
|
288
|
+
{_key: splitSpan._key},
|
|
289
|
+
]),
|
|
290
|
+
)
|
|
291
|
+
patches.push(
|
|
292
|
+
set(splitSpan.text, [
|
|
293
|
+
{_key: splitBlock._key},
|
|
294
|
+
'children',
|
|
295
|
+
{_key: splitSpan._key},
|
|
296
|
+
'text',
|
|
297
|
+
]),
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
return patches
|
|
301
|
+
}
|
|
302
|
+
return patches
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function removeNodePatch(
|
|
306
|
+
schema: EditorSchema,
|
|
307
|
+
beforeValue: Descendant[],
|
|
308
|
+
operation: RemoveNodeOperation,
|
|
309
|
+
): Array<Patch> {
|
|
310
|
+
const block = beforeValue[operation.path[0]]
|
|
311
|
+
if (operation.path.length === 1) {
|
|
312
|
+
// Remove a single block
|
|
313
|
+
if (block && block._key) {
|
|
314
|
+
return [unset([{_key: block._key}])]
|
|
315
|
+
}
|
|
316
|
+
throw new Error('Block not found')
|
|
317
|
+
} else if (isTextBlock({schema}, block) && operation.path.length === 2) {
|
|
318
|
+
const spanToRemove = block.children[operation.path[1]]
|
|
319
|
+
|
|
320
|
+
if (spanToRemove) {
|
|
321
|
+
const spansMatchingKey = block.children.filter(
|
|
322
|
+
(span) => span._key === operation.node._key,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
if (spansMatchingKey.length > 1) {
|
|
326
|
+
console.warn(
|
|
327
|
+
`Multiple spans have \`_key\` ${operation.node._key}. It's ambiguous which one to remove.`,
|
|
328
|
+
JSON.stringify(block, null, 2),
|
|
329
|
+
)
|
|
330
|
+
return []
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return [
|
|
334
|
+
unset([{_key: block._key}, 'children', {_key: spanToRemove._key}]),
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
return []
|
|
338
|
+
} else {
|
|
339
|
+
return []
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function mergeNodePatch(
|
|
344
|
+
schema: EditorSchema,
|
|
345
|
+
children: Descendant[],
|
|
346
|
+
operation: MergeNodeOperation,
|
|
347
|
+
beforeValue: Descendant[],
|
|
348
|
+
): Array<Patch> {
|
|
349
|
+
const patches: Patch[] = []
|
|
350
|
+
|
|
351
|
+
const block = beforeValue[operation.path[0]]
|
|
352
|
+
const updatedBlock = children[operation.path[0]]
|
|
353
|
+
|
|
354
|
+
if (operation.path.length === 1) {
|
|
355
|
+
if (block?._key) {
|
|
356
|
+
const newBlock = fromSlateValue(
|
|
357
|
+
[children[operation.path[0] - 1]],
|
|
358
|
+
schema.block.name,
|
|
359
|
+
)[0]
|
|
360
|
+
patches.push(set(newBlock, [{_key: newBlock._key}]))
|
|
361
|
+
patches.push(unset([{_key: block._key}]))
|
|
362
|
+
} else {
|
|
363
|
+
throw new Error('Target key not found!')
|
|
364
|
+
}
|
|
365
|
+
} else if (
|
|
366
|
+
isTextBlock({schema}, block) &&
|
|
367
|
+
isTextBlock({schema}, updatedBlock) &&
|
|
368
|
+
operation.path.length === 2
|
|
369
|
+
) {
|
|
370
|
+
const updatedSpan =
|
|
371
|
+
updatedBlock.children[operation.path[1] - 1] &&
|
|
372
|
+
isSpan({schema}, updatedBlock.children[operation.path[1] - 1])
|
|
373
|
+
? updatedBlock.children[operation.path[1] - 1]
|
|
374
|
+
: undefined
|
|
375
|
+
const removedSpan =
|
|
376
|
+
block.children[operation.path[1]] &&
|
|
377
|
+
isSpan({schema}, block.children[operation.path[1]])
|
|
378
|
+
? block.children[operation.path[1]]
|
|
379
|
+
: undefined
|
|
380
|
+
|
|
381
|
+
if (updatedSpan) {
|
|
382
|
+
const spansMatchingKey = block.children.filter(
|
|
383
|
+
(span) => span._key === updatedSpan._key,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
if (spansMatchingKey.length === 1) {
|
|
387
|
+
patches.push(
|
|
388
|
+
set(updatedSpan.text, [
|
|
389
|
+
{_key: block._key},
|
|
390
|
+
'children',
|
|
391
|
+
{_key: updatedSpan._key},
|
|
392
|
+
'text',
|
|
393
|
+
]),
|
|
394
|
+
)
|
|
395
|
+
} else {
|
|
396
|
+
console.warn(
|
|
397
|
+
`Multiple spans have \`_key\` ${updatedSpan._key}. It's ambiguous which one to update.`,
|
|
398
|
+
JSON.stringify(block, null, 2),
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (removedSpan) {
|
|
404
|
+
const spansMatchingKey = block.children.filter(
|
|
405
|
+
(span) => span._key === removedSpan._key,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if (spansMatchingKey.length === 1) {
|
|
409
|
+
patches.push(
|
|
410
|
+
unset([{_key: block._key}, 'children', {_key: removedSpan._key}]),
|
|
411
|
+
)
|
|
412
|
+
} else {
|
|
413
|
+
console.warn(
|
|
414
|
+
`Multiple spans have \`_key\` ${removedSpan._key}. It's ambiguous which one to remove.`,
|
|
415
|
+
JSON.stringify(block, null, 2),
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return patches
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function moveNodePatch(
|
|
424
|
+
schema: EditorSchema,
|
|
425
|
+
beforeValue: Descendant[],
|
|
426
|
+
operation: MoveNodeOperation,
|
|
427
|
+
): Array<Patch> {
|
|
428
|
+
const patches: Patch[] = []
|
|
429
|
+
const block = beforeValue[operation.path[0]]
|
|
430
|
+
const targetBlock = beforeValue[operation.newPath[0]]
|
|
431
|
+
|
|
432
|
+
if (!targetBlock) {
|
|
433
|
+
return patches
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (operation.path.length === 1) {
|
|
437
|
+
const position: InsertPosition =
|
|
438
|
+
operation.path[0] > operation.newPath[0] ? 'before' : 'after'
|
|
439
|
+
patches.push(unset([{_key: block._key}]))
|
|
440
|
+
patches.push(
|
|
441
|
+
insert([fromSlateValue([block], schema.block.name)[0]], position, [
|
|
442
|
+
{_key: targetBlock._key},
|
|
443
|
+
]),
|
|
444
|
+
)
|
|
445
|
+
} else if (
|
|
446
|
+
operation.path.length === 2 &&
|
|
447
|
+
isTextBlock({schema}, block) &&
|
|
448
|
+
isTextBlock({schema}, targetBlock)
|
|
449
|
+
) {
|
|
450
|
+
const child = block.children[operation.path[1]]
|
|
451
|
+
const targetChild = targetBlock.children[operation.newPath[1]]
|
|
452
|
+
const position =
|
|
453
|
+
operation.newPath[1] === targetBlock.children.length ? 'after' : 'before'
|
|
454
|
+
const childToInsert = (
|
|
455
|
+
fromSlateValue([block], schema.block.name)[0] as PortableTextTextBlock
|
|
456
|
+
).children[operation.path[1]]
|
|
457
|
+
patches.push(unset([{_key: block._key}, 'children', {_key: child._key}]))
|
|
458
|
+
patches.push(
|
|
459
|
+
insert([childToInsert], position, [
|
|
460
|
+
{_key: targetBlock._key},
|
|
461
|
+
'children',
|
|
462
|
+
{_key: targetChild._key},
|
|
463
|
+
]),
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
return patches
|
|
467
|
+
}
|