@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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {describe, expect, test} from 'vitest'
|
|
2
|
+
import {compileSchemaDefinition, defineSchema} from '../editor/editor-schema'
|
|
3
|
+
import {applyOperationToPortableText} from './apply-operation-to-portable-text'
|
|
4
|
+
import {createTestKeyGenerator} from './test-key-generator'
|
|
5
|
+
|
|
6
|
+
function createContext() {
|
|
7
|
+
const keyGenerator = createTestKeyGenerator()
|
|
8
|
+
const schema = compileSchemaDefinition(defineSchema({}))
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
keyGenerator,
|
|
12
|
+
schema,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe(applyOperationToPortableText.name, () => {
|
|
17
|
+
test('setting block object properties', () => {
|
|
18
|
+
expect(
|
|
19
|
+
applyOperationToPortableText(
|
|
20
|
+
createContext(),
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
_type: 'image',
|
|
24
|
+
_key: 'k0',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
{
|
|
28
|
+
type: 'set_node',
|
|
29
|
+
path: [0],
|
|
30
|
+
properties: {},
|
|
31
|
+
newProperties: {
|
|
32
|
+
value: {src: 'https://example.com/image.jpg'},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
),
|
|
36
|
+
).toEqual([
|
|
37
|
+
{
|
|
38
|
+
_type: 'image',
|
|
39
|
+
_key: 'k0',
|
|
40
|
+
src: 'https://example.com/image.jpg',
|
|
41
|
+
},
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('updating block object properties', () => {
|
|
46
|
+
expect(
|
|
47
|
+
applyOperationToPortableText(
|
|
48
|
+
createContext(),
|
|
49
|
+
[
|
|
50
|
+
{
|
|
51
|
+
_type: 'image',
|
|
52
|
+
_key: 'k0',
|
|
53
|
+
src: 'https://example.com/image.jpg',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
{
|
|
57
|
+
type: 'set_node',
|
|
58
|
+
path: [0],
|
|
59
|
+
properties: {
|
|
60
|
+
value: {src: 'https://example.com/image.jpg'},
|
|
61
|
+
},
|
|
62
|
+
newProperties: {
|
|
63
|
+
value: {
|
|
64
|
+
src: 'https://example.com/image.jpg',
|
|
65
|
+
alt: 'An image',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
).toEqual([
|
|
71
|
+
{
|
|
72
|
+
_type: 'image',
|
|
73
|
+
_key: 'k0',
|
|
74
|
+
src: 'https://example.com/image.jpg',
|
|
75
|
+
alt: 'An image',
|
|
76
|
+
},
|
|
77
|
+
])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('removing block object properties', () => {
|
|
81
|
+
expect(
|
|
82
|
+
applyOperationToPortableText(
|
|
83
|
+
createContext(),
|
|
84
|
+
[{_type: 'image', _key: 'k0', alt: 'An image'}],
|
|
85
|
+
{
|
|
86
|
+
type: 'set_node',
|
|
87
|
+
path: [0],
|
|
88
|
+
properties: {
|
|
89
|
+
value: {
|
|
90
|
+
alt: 'An image',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
newProperties: {value: {}},
|
|
94
|
+
},
|
|
95
|
+
),
|
|
96
|
+
).toEqual([{_type: 'image', _key: 'k0'}])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('updating block object _key', () => {
|
|
100
|
+
expect(
|
|
101
|
+
applyOperationToPortableText(
|
|
102
|
+
createContext(),
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
_type: 'image',
|
|
106
|
+
_key: 'k0',
|
|
107
|
+
src: 'https://example.com/image.jpg',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
{
|
|
111
|
+
type: 'set_node',
|
|
112
|
+
path: [0],
|
|
113
|
+
properties: {_key: 'k0'},
|
|
114
|
+
newProperties: {_key: 'k1'},
|
|
115
|
+
},
|
|
116
|
+
),
|
|
117
|
+
).toEqual([
|
|
118
|
+
{
|
|
119
|
+
_type: 'image',
|
|
120
|
+
_key: 'k1',
|
|
121
|
+
src: 'https://example.com/image.jpg',
|
|
122
|
+
},
|
|
123
|
+
])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('updating inline object properties', () => {
|
|
127
|
+
expect(
|
|
128
|
+
applyOperationToPortableText(
|
|
129
|
+
createContext(),
|
|
130
|
+
[
|
|
131
|
+
{
|
|
132
|
+
_key: 'k0',
|
|
133
|
+
_type: 'block',
|
|
134
|
+
children: [
|
|
135
|
+
{
|
|
136
|
+
_key: 'k1',
|
|
137
|
+
_type: 'span',
|
|
138
|
+
text: '',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
_key: 'k2',
|
|
142
|
+
_type: 'stock ticker',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
_key: 'k3',
|
|
146
|
+
_type: 'span',
|
|
147
|
+
text: '',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
{
|
|
153
|
+
type: 'set_node',
|
|
154
|
+
path: [0, 1],
|
|
155
|
+
properties: {},
|
|
156
|
+
newProperties: {
|
|
157
|
+
value: {
|
|
158
|
+
symbol: 'AAPL',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
),
|
|
163
|
+
).toEqual([
|
|
164
|
+
{
|
|
165
|
+
_type: 'block',
|
|
166
|
+
_key: 'k0',
|
|
167
|
+
children: [
|
|
168
|
+
{_type: 'span', _key: 'k1', text: ''},
|
|
169
|
+
{_type: 'stock ticker', _key: 'k2', symbol: 'AAPL'},
|
|
170
|
+
{_type: 'span', _key: 'k3', text: ''},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
])
|
|
174
|
+
})
|
|
175
|
+
})
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import {createDraft, finishDraft, type WritableDraft} from 'immer'
|
|
3
|
+
import {Element, Path, type Node, type Operation} from 'slate'
|
|
4
|
+
import type {EditorSchema} from '../editor/editor-schema'
|
|
5
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
6
|
+
import type {OmitFromUnion} from '../type-utils'
|
|
7
|
+
import {
|
|
8
|
+
getBlock,
|
|
9
|
+
getNode,
|
|
10
|
+
getParent,
|
|
11
|
+
getSpan,
|
|
12
|
+
isEditorNode,
|
|
13
|
+
isObjectNode,
|
|
14
|
+
isPartialSpanNode,
|
|
15
|
+
isSpanNode,
|
|
16
|
+
isTextBlockNode,
|
|
17
|
+
type PortableTextNode,
|
|
18
|
+
type SpanNode,
|
|
19
|
+
type TextBlockNode,
|
|
20
|
+
} from './portable-text-node'
|
|
21
|
+
|
|
22
|
+
export function applyOperationToPortableText(
|
|
23
|
+
context: Pick<EditorContext, 'keyGenerator' | 'schema'>,
|
|
24
|
+
value: Array<PortableTextBlock>,
|
|
25
|
+
operation: OmitFromUnion<Operation, 'type', 'set_selection'>,
|
|
26
|
+
) {
|
|
27
|
+
const draft = createDraft({children: value})
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
applyOperationToPortableTextDraft(context, draft, operation)
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error(e)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return finishDraft(draft).children
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function applyOperationToPortableTextDraft(
|
|
39
|
+
context: Pick<EditorContext, 'keyGenerator' | 'schema'>,
|
|
40
|
+
root: WritableDraft<{
|
|
41
|
+
children: Array<PortableTextBlock>
|
|
42
|
+
}>,
|
|
43
|
+
operation: OmitFromUnion<Operation, 'type', 'set_selection'>,
|
|
44
|
+
) {
|
|
45
|
+
switch (operation.type) {
|
|
46
|
+
case 'insert_node': {
|
|
47
|
+
const {path, node: insertedNode} = operation
|
|
48
|
+
const parent = getParent(context, root, path)
|
|
49
|
+
const index = path[path.length - 1]
|
|
50
|
+
|
|
51
|
+
if (!parent) {
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (index > parent.children.length) {
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (path.length === 1) {
|
|
60
|
+
// Inserting block at the root
|
|
61
|
+
|
|
62
|
+
if (isTextBlockNode(context, insertedNode)) {
|
|
63
|
+
// Text blocks can be inserted as is
|
|
64
|
+
|
|
65
|
+
parent.children.splice(index, 0, {
|
|
66
|
+
...insertedNode,
|
|
67
|
+
children: insertedNode.children.map((child) => {
|
|
68
|
+
if ('__inline' in child) {
|
|
69
|
+
// Except for inline object children which need to have their
|
|
70
|
+
// `value` spread onto the block
|
|
71
|
+
return {
|
|
72
|
+
_key: child._key,
|
|
73
|
+
_type: child._type,
|
|
74
|
+
...('value' in child && typeof child.value === 'object'
|
|
75
|
+
? child.value
|
|
76
|
+
: {}),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return child
|
|
81
|
+
}),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
break
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (Element.isElement(insertedNode) && !('__inline' in insertedNode)) {
|
|
88
|
+
// Void blocks have to have their `value` spread onto the block
|
|
89
|
+
|
|
90
|
+
parent.children.splice(index, 0, {
|
|
91
|
+
_key: insertedNode._key,
|
|
92
|
+
_type: insertedNode._type,
|
|
93
|
+
...('value' in insertedNode &&
|
|
94
|
+
typeof insertedNode.value === 'object'
|
|
95
|
+
? insertedNode.value
|
|
96
|
+
: {}),
|
|
97
|
+
})
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (path.length === 2) {
|
|
103
|
+
// Inserting children into blocks
|
|
104
|
+
|
|
105
|
+
if (!isTextBlockNode(context, parent)) {
|
|
106
|
+
// Only text blocks can have children
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isPartialSpanNode(insertedNode)) {
|
|
111
|
+
// Text nodes can be inserted as is
|
|
112
|
+
|
|
113
|
+
parent.children.splice(index, 0, insertedNode)
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if ('__inline' in insertedNode) {
|
|
118
|
+
// Void children have to have their `value` spread onto the block
|
|
119
|
+
|
|
120
|
+
parent.children.splice(index, 0, {
|
|
121
|
+
_key: insertedNode._key,
|
|
122
|
+
_type: insertedNode._type,
|
|
123
|
+
...('value' in insertedNode &&
|
|
124
|
+
typeof insertedNode.value === 'object'
|
|
125
|
+
? insertedNode.value
|
|
126
|
+
: {}),
|
|
127
|
+
})
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case 'insert_text': {
|
|
136
|
+
const {path, offset, text} = operation
|
|
137
|
+
if (text.length === 0) break
|
|
138
|
+
const span = getSpan(context, root, path)
|
|
139
|
+
|
|
140
|
+
if (!span) {
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const before = span.text.slice(0, offset)
|
|
145
|
+
const after = span.text.slice(offset)
|
|
146
|
+
span.text = before + text + after
|
|
147
|
+
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case 'merge_node': {
|
|
152
|
+
const {path} = operation
|
|
153
|
+
const node = getNode(context, root, path)
|
|
154
|
+
const prevPath = Path.previous(path)
|
|
155
|
+
const prev = getNode(context, root, prevPath)
|
|
156
|
+
const parent = getParent(context, root, path)
|
|
157
|
+
|
|
158
|
+
if (!node || !prev || !parent) {
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const index = path[path.length - 1]
|
|
163
|
+
|
|
164
|
+
if (isPartialSpanNode(node) && isPartialSpanNode(prev)) {
|
|
165
|
+
prev.text += node.text
|
|
166
|
+
} else if (
|
|
167
|
+
isTextBlockNode(context, node) &&
|
|
168
|
+
isTextBlockNode(context, prev)
|
|
169
|
+
) {
|
|
170
|
+
prev.children.push(...node.children)
|
|
171
|
+
} else {
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
parent.children.splice(index, 1)
|
|
176
|
+
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case 'move_node': {
|
|
181
|
+
const {path, newPath} = operation
|
|
182
|
+
|
|
183
|
+
if (Path.isAncestor(path, newPath)) {
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const node = getNode(context, root, path)
|
|
188
|
+
const parent = getParent(context, root, path)
|
|
189
|
+
const index = path[path.length - 1]
|
|
190
|
+
|
|
191
|
+
if (!node || !parent) {
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// This is tricky, but since the `path` and `newPath` both refer to
|
|
196
|
+
// the same snapshot in time, there's a mismatch. After either
|
|
197
|
+
// removing the original position, the second step's path can be out
|
|
198
|
+
// of date. So instead of using the `op.newPath` directly, we
|
|
199
|
+
// transform `op.path` to ascertain what the `newPath` would be after
|
|
200
|
+
// the operation was applied.
|
|
201
|
+
parent.children.splice(index, 1)
|
|
202
|
+
const truePath = Path.transform(path, operation)!
|
|
203
|
+
const newParent = getNode(context, root, Path.parent(truePath))
|
|
204
|
+
const newIndex = truePath[truePath.length - 1]
|
|
205
|
+
|
|
206
|
+
if (!newParent) {
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!('children' in newParent)) {
|
|
211
|
+
break
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!Array.isArray(newParent.children)) {
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
newParent.children.splice(newIndex, 0, node)
|
|
219
|
+
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case 'remove_node': {
|
|
224
|
+
const {path} = operation
|
|
225
|
+
const index = path[path.length - 1]
|
|
226
|
+
const parent = getParent(context, root, path)
|
|
227
|
+
parent?.children.splice(index, 1)
|
|
228
|
+
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
case 'remove_text': {
|
|
233
|
+
const {path, offset, text} = operation
|
|
234
|
+
|
|
235
|
+
if (text.length === 0) {
|
|
236
|
+
break
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const span = getSpan(context, root, path)
|
|
240
|
+
|
|
241
|
+
if (!span) {
|
|
242
|
+
break
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const before = span.text.slice(0, offset)
|
|
246
|
+
const after = span.text.slice(offset + text.length)
|
|
247
|
+
span.text = before + after
|
|
248
|
+
|
|
249
|
+
break
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case 'set_node': {
|
|
253
|
+
const {path, properties, newProperties} = operation
|
|
254
|
+
|
|
255
|
+
const node = getNode(context, root, path)
|
|
256
|
+
|
|
257
|
+
if (!node) {
|
|
258
|
+
break
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (isEditorNode(node)) {
|
|
262
|
+
break
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (isObjectNode(context, node)) {
|
|
266
|
+
const valueBefore = (
|
|
267
|
+
'value' in properties && typeof properties.value === 'object'
|
|
268
|
+
? properties.value
|
|
269
|
+
: {}
|
|
270
|
+
) as Partial<Node>
|
|
271
|
+
const valueAfter = (
|
|
272
|
+
'value' in newProperties && typeof newProperties.value === 'object'
|
|
273
|
+
? newProperties.value
|
|
274
|
+
: {}
|
|
275
|
+
) as Partial<Node>
|
|
276
|
+
|
|
277
|
+
for (const key in newProperties) {
|
|
278
|
+
if (key === 'value') {
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const value = newProperties[key as keyof Partial<Node>]
|
|
283
|
+
|
|
284
|
+
if (value == null) {
|
|
285
|
+
delete node[<keyof PortableTextNode<EditorSchema>>key]
|
|
286
|
+
} else {
|
|
287
|
+
node[<keyof PortableTextNode<EditorSchema>>key] = value
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
for (const key in properties) {
|
|
292
|
+
if (key === 'value') {
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!newProperties.hasOwnProperty(key)) {
|
|
297
|
+
delete node[<keyof PortableTextNode<EditorSchema>>key]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const key in valueAfter) {
|
|
302
|
+
const value = valueAfter[key as keyof Partial<Node>]
|
|
303
|
+
|
|
304
|
+
if (value == null) {
|
|
305
|
+
delete node[<keyof PortableTextNode<EditorSchema>>key]
|
|
306
|
+
} else {
|
|
307
|
+
node[<keyof PortableTextNode<EditorSchema>>key] = value
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for (const key in valueBefore) {
|
|
312
|
+
if (!valueAfter.hasOwnProperty(key)) {
|
|
313
|
+
delete node[<keyof PortableTextNode<EditorSchema>>key]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
break
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (isTextBlockNode(context, node)) {
|
|
321
|
+
for (const key in newProperties) {
|
|
322
|
+
if (key === 'children' || key === 'text') {
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const value = newProperties[key as keyof Partial<Node>]
|
|
327
|
+
|
|
328
|
+
if (value == null) {
|
|
329
|
+
delete node[<keyof Partial<Node>>key]
|
|
330
|
+
} else {
|
|
331
|
+
node[<keyof Partial<Node>>key] = value
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// properties that were previously defined, but are now missing, must be deleted
|
|
336
|
+
for (const key in properties) {
|
|
337
|
+
if (!newProperties.hasOwnProperty(key)) {
|
|
338
|
+
delete node[<keyof Partial<Node>>key]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
break
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (isPartialSpanNode(node)) {
|
|
346
|
+
for (const key in newProperties) {
|
|
347
|
+
if (key === 'text') {
|
|
348
|
+
break
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const value = newProperties[key as keyof Partial<Node>]
|
|
352
|
+
|
|
353
|
+
if (value == null) {
|
|
354
|
+
delete node[<keyof PortableTextNode<EditorSchema>>key]
|
|
355
|
+
} else {
|
|
356
|
+
node[<keyof PortableTextNode<EditorSchema>>key] = value
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// properties that were previously defined, but are now missing, must be deleted
|
|
361
|
+
for (const key in properties) {
|
|
362
|
+
if (!newProperties.hasOwnProperty(key)) {
|
|
363
|
+
delete node[<keyof PortableTextNode<EditorSchema>>key]
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
break
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
break
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
case 'split_node': {
|
|
374
|
+
const {path, position, properties} = operation
|
|
375
|
+
|
|
376
|
+
if (path.length === 0) {
|
|
377
|
+
break
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const parent = getParent(context, root, path)
|
|
381
|
+
const index = path[path.length - 1]
|
|
382
|
+
|
|
383
|
+
if (!parent) {
|
|
384
|
+
break
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (isEditorNode(parent)) {
|
|
388
|
+
const block = getBlock(root, path)
|
|
389
|
+
|
|
390
|
+
if (!block || !isTextBlockNode(context, block)) {
|
|
391
|
+
break
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const before = block.children.slice(0, position)
|
|
395
|
+
const after = block.children.slice(position)
|
|
396
|
+
block.children = before
|
|
397
|
+
|
|
398
|
+
// _key is deliberately left out
|
|
399
|
+
const newTextBlockNode = {
|
|
400
|
+
...properties,
|
|
401
|
+
children: after,
|
|
402
|
+
_type: context.schema.block.name,
|
|
403
|
+
} as unknown as TextBlockNode<EditorSchema>
|
|
404
|
+
|
|
405
|
+
parent.children.splice(index + 1, 0, newTextBlockNode)
|
|
406
|
+
|
|
407
|
+
break
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (isTextBlockNode(context, parent)) {
|
|
411
|
+
const node = getNode(context, root, path)
|
|
412
|
+
|
|
413
|
+
if (!node || !isSpanNode(context, node)) {
|
|
414
|
+
break
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const before = node.text.slice(0, position)
|
|
418
|
+
const after = node.text.slice(position)
|
|
419
|
+
node.text = before
|
|
420
|
+
|
|
421
|
+
// _key is deliberately left out
|
|
422
|
+
const newSpanNode = {
|
|
423
|
+
...properties,
|
|
424
|
+
text: after,
|
|
425
|
+
} as unknown as SpanNode<EditorSchema>
|
|
426
|
+
|
|
427
|
+
parent.children.splice(index + 1, 0, newSpanNode)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return root
|
|
435
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
2
|
+
|
|
3
|
+
export function createPlaceholderBlock(
|
|
4
|
+
context: Pick<EditorContext, 'keyGenerator' | 'schema'>,
|
|
5
|
+
) {
|
|
6
|
+
return {
|
|
7
|
+
_type: context.schema.block.name,
|
|
8
|
+
_key: context.keyGenerator(),
|
|
9
|
+
style: context.schema.styles[0].name ?? 'normal',
|
|
10
|
+
markDefs: [],
|
|
11
|
+
children: [
|
|
12
|
+
{
|
|
13
|
+
_type: context.schema.span.name,
|
|
14
|
+
_key: context.keyGenerator(),
|
|
15
|
+
text: '',
|
|
16
|
+
marks: [],
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
}
|
|
20
|
+
}
|