@portabletext/editor 1.49.7 → 1.49.8
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 +24 -272
- package/lib/behaviors/index.d.ts +24 -272
- package/lib/index.cjs +3049 -3097
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +31 -277
- package/lib/index.d.ts +31 -277
- package/lib/index.js +3127 -3175
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +31 -277
- package/lib/plugins/index.d.ts +31 -277
- package/lib/selectors/index.d.cts +24 -272
- package/lib/selectors/index.d.ts +24 -272
- package/lib/utils/index.d.cts +24 -272
- package/lib/utils/index.d.ts +24 -272
- package/package.json +1 -1
- package/src/editor/PortableTextEditor.tsx +12 -101
- package/src/editor/create-editor.ts +132 -2
- package/src/editor/editor-machine.ts +2 -9
- package/src/editor/editor-provider.tsx +1 -6
- package/src/editor/route-events-to-changes.tsx +81 -0
- package/src/editor/sync-machine.ts +31 -0
- package/src/editor.ts +7 -1
- package/src/internal-utils/text-selection.ts +3 -1
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +0 -364
- package/src/editor/components/Synchronizer.tsx +0 -134
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
-
import {render, waitFor} from '@testing-library/react'
|
|
3
|
-
import {createRef, type RefObject} from 'react'
|
|
4
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
-
import {createTestKeyGenerator} from '../../internal-utils/test-key-generator'
|
|
6
|
-
import {PortableTextEditor} from '../PortableTextEditor'
|
|
7
|
-
import {PortableTextEditorTester, schemaType} from './PortableTextEditorTester'
|
|
8
|
-
|
|
9
|
-
describe('when PTE would display warnings, instead it self solves', () => {
|
|
10
|
-
it('when child at index is missing required _key in block with _key', async () => {
|
|
11
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
12
|
-
const initialValue = [
|
|
13
|
-
{
|
|
14
|
-
_key: 'abc',
|
|
15
|
-
_type: 'myTestBlockType',
|
|
16
|
-
children: [
|
|
17
|
-
{
|
|
18
|
-
_type: 'span',
|
|
19
|
-
marks: [],
|
|
20
|
-
text: 'Hello with a new key',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
markDefs: [],
|
|
24
|
-
style: 'normal',
|
|
25
|
-
},
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
const onChange = vi.fn()
|
|
29
|
-
render(
|
|
30
|
-
<PortableTextEditorTester
|
|
31
|
-
keyGenerator={createTestKeyGenerator()}
|
|
32
|
-
onChange={onChange}
|
|
33
|
-
ref={editorRef}
|
|
34
|
-
schemaType={schemaType}
|
|
35
|
-
value={initialValue}
|
|
36
|
-
/>,
|
|
37
|
-
)
|
|
38
|
-
await waitFor(() => {
|
|
39
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
40
|
-
type: 'value',
|
|
41
|
-
value: initialValue,
|
|
42
|
-
})
|
|
43
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
44
|
-
})
|
|
45
|
-
await waitFor(() => {
|
|
46
|
-
if (editorRef.current) {
|
|
47
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
48
|
-
{
|
|
49
|
-
_key: 'abc',
|
|
50
|
-
_type: 'myTestBlockType',
|
|
51
|
-
children: [
|
|
52
|
-
{
|
|
53
|
-
_key: 'k3',
|
|
54
|
-
_type: 'span',
|
|
55
|
-
text: 'Hello with a new key',
|
|
56
|
-
marks: [],
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
markDefs: [],
|
|
60
|
-
style: 'normal',
|
|
61
|
-
},
|
|
62
|
-
])
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('self-solves missing .markDefs', async () => {
|
|
68
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
69
|
-
const initialValue = [
|
|
70
|
-
{
|
|
71
|
-
_key: 'abc',
|
|
72
|
-
_type: 'myTestBlockType',
|
|
73
|
-
children: [
|
|
74
|
-
{
|
|
75
|
-
_key: 'def',
|
|
76
|
-
_type: 'span',
|
|
77
|
-
marks: [],
|
|
78
|
-
text: 'No markDefs',
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
style: 'normal',
|
|
82
|
-
},
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
const onChange = vi.fn()
|
|
86
|
-
render(
|
|
87
|
-
<PortableTextEditorTester
|
|
88
|
-
keyGenerator={createTestKeyGenerator()}
|
|
89
|
-
onChange={onChange}
|
|
90
|
-
ref={editorRef}
|
|
91
|
-
schemaType={schemaType}
|
|
92
|
-
value={initialValue}
|
|
93
|
-
/>,
|
|
94
|
-
)
|
|
95
|
-
await waitFor(() => {
|
|
96
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
97
|
-
type: 'value',
|
|
98
|
-
value: initialValue,
|
|
99
|
-
})
|
|
100
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
101
|
-
})
|
|
102
|
-
await waitFor(() => {
|
|
103
|
-
if (editorRef.current) {
|
|
104
|
-
PortableTextEditor.focus(editorRef.current)
|
|
105
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
106
|
-
{
|
|
107
|
-
_key: 'abc',
|
|
108
|
-
_type: 'myTestBlockType',
|
|
109
|
-
children: [
|
|
110
|
-
{
|
|
111
|
-
_key: 'def',
|
|
112
|
-
_type: 'span',
|
|
113
|
-
text: 'No markDefs',
|
|
114
|
-
marks: [],
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
markDefs: [],
|
|
118
|
-
style: 'normal',
|
|
119
|
-
},
|
|
120
|
-
])
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('adds missing .children', async () => {
|
|
126
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
127
|
-
const initialValue = [
|
|
128
|
-
{
|
|
129
|
-
_key: 'abc',
|
|
130
|
-
_type: 'myTestBlockType',
|
|
131
|
-
style: 'normal',
|
|
132
|
-
markDefs: [],
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
_key: 'def',
|
|
136
|
-
_type: 'myTestBlockType',
|
|
137
|
-
style: 'normal',
|
|
138
|
-
children: [],
|
|
139
|
-
markDefs: [],
|
|
140
|
-
},
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
const onChange = vi.fn()
|
|
144
|
-
render(
|
|
145
|
-
<PortableTextEditorTester
|
|
146
|
-
keyGenerator={createTestKeyGenerator()}
|
|
147
|
-
onChange={onChange}
|
|
148
|
-
ref={editorRef}
|
|
149
|
-
schemaType={schemaType}
|
|
150
|
-
value={initialValue}
|
|
151
|
-
/>,
|
|
152
|
-
)
|
|
153
|
-
await waitFor(() => {
|
|
154
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
155
|
-
type: 'value',
|
|
156
|
-
value: initialValue,
|
|
157
|
-
})
|
|
158
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
159
|
-
})
|
|
160
|
-
await waitFor(() => {
|
|
161
|
-
if (editorRef.current) {
|
|
162
|
-
PortableTextEditor.focus(editorRef.current)
|
|
163
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
164
|
-
{
|
|
165
|
-
_key: 'abc',
|
|
166
|
-
_type: 'myTestBlockType',
|
|
167
|
-
children: [
|
|
168
|
-
{
|
|
169
|
-
_key: 'k3',
|
|
170
|
-
_type: 'span',
|
|
171
|
-
text: '',
|
|
172
|
-
marks: [],
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
markDefs: [],
|
|
176
|
-
style: 'normal',
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
_key: 'def',
|
|
180
|
-
_type: 'myTestBlockType',
|
|
181
|
-
children: [
|
|
182
|
-
{
|
|
183
|
-
_key: 'k5',
|
|
184
|
-
_type: 'span',
|
|
185
|
-
text: '',
|
|
186
|
-
marks: [],
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
markDefs: [],
|
|
190
|
-
style: 'normal',
|
|
191
|
-
},
|
|
192
|
-
])
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('removes orphaned marks', async () => {
|
|
198
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
199
|
-
const initialValue = [
|
|
200
|
-
{
|
|
201
|
-
_key: 'abc',
|
|
202
|
-
_type: 'myTestBlockType',
|
|
203
|
-
style: 'normal',
|
|
204
|
-
markDefs: [],
|
|
205
|
-
children: [
|
|
206
|
-
{
|
|
207
|
-
_key: 'def',
|
|
208
|
-
_type: 'span',
|
|
209
|
-
marks: ['ghi'],
|
|
210
|
-
text: 'Hello',
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
},
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
const onChange = vi.fn()
|
|
217
|
-
render(
|
|
218
|
-
<PortableTextEditorTester
|
|
219
|
-
keyGenerator={createTestKeyGenerator()}
|
|
220
|
-
onChange={onChange}
|
|
221
|
-
ref={editorRef}
|
|
222
|
-
schemaType={schemaType}
|
|
223
|
-
value={initialValue}
|
|
224
|
-
/>,
|
|
225
|
-
)
|
|
226
|
-
await waitFor(() => {
|
|
227
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
228
|
-
type: 'value',
|
|
229
|
-
value: initialValue,
|
|
230
|
-
})
|
|
231
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
232
|
-
})
|
|
233
|
-
await waitFor(() => {
|
|
234
|
-
if (editorRef.current) {
|
|
235
|
-
PortableTextEditor.focus(editorRef.current)
|
|
236
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
237
|
-
{
|
|
238
|
-
_key: 'abc',
|
|
239
|
-
_type: 'myTestBlockType',
|
|
240
|
-
children: [
|
|
241
|
-
{
|
|
242
|
-
_key: 'def',
|
|
243
|
-
_type: 'span',
|
|
244
|
-
text: 'Hello',
|
|
245
|
-
marks: [],
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
markDefs: [],
|
|
249
|
-
style: 'normal',
|
|
250
|
-
},
|
|
251
|
-
])
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('removes orphaned marksDefs', async () => {
|
|
257
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
258
|
-
const initialValue = [
|
|
259
|
-
{
|
|
260
|
-
_key: 'abc',
|
|
261
|
-
_type: 'myTestBlockType',
|
|
262
|
-
style: 'normal',
|
|
263
|
-
markDefs: [
|
|
264
|
-
{
|
|
265
|
-
_key: 'ghi',
|
|
266
|
-
_type: 'link',
|
|
267
|
-
href: 'https://sanity.io',
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
children: [
|
|
271
|
-
{
|
|
272
|
-
_key: 'def',
|
|
273
|
-
_type: 'span',
|
|
274
|
-
marks: [],
|
|
275
|
-
text: 'Hello',
|
|
276
|
-
},
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
]
|
|
280
|
-
|
|
281
|
-
const onChange = vi.fn()
|
|
282
|
-
render(
|
|
283
|
-
<PortableTextEditorTester
|
|
284
|
-
keyGenerator={createTestKeyGenerator()}
|
|
285
|
-
onChange={onChange}
|
|
286
|
-
ref={editorRef}
|
|
287
|
-
schemaType={schemaType}
|
|
288
|
-
value={initialValue}
|
|
289
|
-
/>,
|
|
290
|
-
)
|
|
291
|
-
await waitFor(() => {
|
|
292
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
293
|
-
type: 'value',
|
|
294
|
-
value: initialValue,
|
|
295
|
-
})
|
|
296
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
297
|
-
})
|
|
298
|
-
await waitFor(() => {
|
|
299
|
-
if (editorRef.current) {
|
|
300
|
-
PortableTextEditor.focus(editorRef.current)
|
|
301
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
302
|
-
{
|
|
303
|
-
_key: 'abc',
|
|
304
|
-
_type: 'myTestBlockType',
|
|
305
|
-
children: [
|
|
306
|
-
{
|
|
307
|
-
_key: 'def',
|
|
308
|
-
_type: 'span',
|
|
309
|
-
text: 'Hello',
|
|
310
|
-
marks: [],
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
markDefs: [],
|
|
314
|
-
style: 'normal',
|
|
315
|
-
},
|
|
316
|
-
])
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('allows empty array of blocks', async () => {
|
|
322
|
-
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
323
|
-
const initialValue = [] as PortableTextBlock[]
|
|
324
|
-
|
|
325
|
-
const onChange = vi.fn()
|
|
326
|
-
render(
|
|
327
|
-
<PortableTextEditorTester
|
|
328
|
-
keyGenerator={createTestKeyGenerator()}
|
|
329
|
-
onChange={onChange}
|
|
330
|
-
ref={editorRef}
|
|
331
|
-
schemaType={schemaType}
|
|
332
|
-
value={initialValue}
|
|
333
|
-
/>,
|
|
334
|
-
)
|
|
335
|
-
await waitFor(() => {
|
|
336
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
337
|
-
type: 'value',
|
|
338
|
-
value: initialValue,
|
|
339
|
-
})
|
|
340
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
341
|
-
})
|
|
342
|
-
await waitFor(() => {
|
|
343
|
-
if (editorRef.current) {
|
|
344
|
-
PortableTextEditor.focus(editorRef.current)
|
|
345
|
-
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
346
|
-
{
|
|
347
|
-
_key: 'k2',
|
|
348
|
-
_type: 'myTestBlockType',
|
|
349
|
-
children: [{_key: 'k3', _type: 'span', marks: [], text: ''}],
|
|
350
|
-
markDefs: [],
|
|
351
|
-
style: 'normal',
|
|
352
|
-
},
|
|
353
|
-
])
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
|
-
await waitFor(() => {
|
|
357
|
-
expect(onChange).toHaveBeenCalledWith({
|
|
358
|
-
type: 'value',
|
|
359
|
-
value: initialValue,
|
|
360
|
-
})
|
|
361
|
-
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
})
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import {useActorRef, useSelector} from '@xstate/react'
|
|
2
|
-
import {useEffect} from 'react'
|
|
3
|
-
import {debugWithName} from '../../internal-utils/debug'
|
|
4
|
-
import {fromSlateValue} from '../../internal-utils/values'
|
|
5
|
-
import {KEY_TO_VALUE_ELEMENT} from '../../internal-utils/weakMaps'
|
|
6
|
-
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
7
|
-
import type {EditorActor} from '../editor-machine'
|
|
8
|
-
import {mutationMachine} from '../mutation-machine'
|
|
9
|
-
import {syncMachine} from '../sync-machine'
|
|
10
|
-
|
|
11
|
-
const debug = debugWithName('component:PortableTextEditor:Synchronizer')
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
export interface SynchronizerProps {
|
|
17
|
-
editorActor: EditorActor
|
|
18
|
-
slateEditor: PortableTextSlateEditor
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Synchronizes the server value with the editor, and provides various contexts for the editor state.
|
|
23
|
-
* @internal
|
|
24
|
-
*/
|
|
25
|
-
export function Synchronizer(props: SynchronizerProps) {
|
|
26
|
-
const {editorActor, slateEditor} = props
|
|
27
|
-
|
|
28
|
-
const incomingValue = useSelector(
|
|
29
|
-
props.editorActor,
|
|
30
|
-
(s) => s.context.incomingValue,
|
|
31
|
-
)
|
|
32
|
-
const readOnly = useSelector(props.editorActor, (s) =>
|
|
33
|
-
s.matches({'edit mode': 'read only'}),
|
|
34
|
-
)
|
|
35
|
-
const syncActorRef = useActorRef(syncMachine, {
|
|
36
|
-
input: {
|
|
37
|
-
keyGenerator: props.editorActor.getSnapshot().context.keyGenerator,
|
|
38
|
-
readOnly: props.editorActor
|
|
39
|
-
.getSnapshot()
|
|
40
|
-
.matches({'edit mode': 'read only'}),
|
|
41
|
-
schema: props.editorActor.getSnapshot().context.schema,
|
|
42
|
-
slateEditor,
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
const mutationActorRef = useActorRef(mutationMachine, {
|
|
46
|
-
input: {
|
|
47
|
-
schema: props.editorActor.getSnapshot().context.schema,
|
|
48
|
-
slateEditor,
|
|
49
|
-
},
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
const subscription = mutationActorRef.on('*', (event) => {
|
|
54
|
-
if (event.type === 'has pending patches') {
|
|
55
|
-
syncActorRef.send({type: 'has pending patches'})
|
|
56
|
-
}
|
|
57
|
-
if (event.type === 'mutation') {
|
|
58
|
-
syncActorRef.send({type: 'mutation'})
|
|
59
|
-
editorActor.send({
|
|
60
|
-
type: 'mutation',
|
|
61
|
-
patches: event.patches,
|
|
62
|
-
snapshot: event.snapshot,
|
|
63
|
-
value: event.snapshot,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
subscription.unsubscribe()
|
|
70
|
-
}
|
|
71
|
-
}, [mutationActorRef, syncActorRef, editorActor])
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
const subscription = syncActorRef.on('*', (event) => {
|
|
75
|
-
switch (event.type) {
|
|
76
|
-
case 'invalid value':
|
|
77
|
-
props.editorActor.send({
|
|
78
|
-
...event,
|
|
79
|
-
type: 'notify.invalid value',
|
|
80
|
-
})
|
|
81
|
-
break
|
|
82
|
-
case 'value changed':
|
|
83
|
-
props.editorActor.send({
|
|
84
|
-
...event,
|
|
85
|
-
type: 'notify.value changed',
|
|
86
|
-
})
|
|
87
|
-
break
|
|
88
|
-
case 'patch':
|
|
89
|
-
props.editorActor.send({
|
|
90
|
-
...event,
|
|
91
|
-
type: 'internal.patch',
|
|
92
|
-
value: fromSlateValue(
|
|
93
|
-
slateEditor.children,
|
|
94
|
-
props.editorActor.getSnapshot().context.schema.block.name,
|
|
95
|
-
KEY_TO_VALUE_ELEMENT.get(slateEditor),
|
|
96
|
-
),
|
|
97
|
-
})
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
default:
|
|
101
|
-
props.editorActor.send(event)
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
return () => {
|
|
106
|
-
subscription.unsubscribe()
|
|
107
|
-
}
|
|
108
|
-
}, [props.editorActor, slateEditor, syncActorRef])
|
|
109
|
-
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
syncActorRef.send({type: 'update readOnly', readOnly})
|
|
112
|
-
}, [syncActorRef, readOnly])
|
|
113
|
-
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
debug('Value from props changed, syncing new value')
|
|
116
|
-
syncActorRef.send({type: 'update value', value: incomingValue})
|
|
117
|
-
}, [syncActorRef, incomingValue])
|
|
118
|
-
|
|
119
|
-
// Subscribe to, and handle changes from the editor
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
debug('Subscribing to patch events')
|
|
122
|
-
const sub = editorActor.on('internal.patch', (event) => {
|
|
123
|
-
mutationActorRef.send({...event, type: 'patch'})
|
|
124
|
-
})
|
|
125
|
-
return () => {
|
|
126
|
-
debug('Unsubscribing to patch events')
|
|
127
|
-
sub.unsubscribe()
|
|
128
|
-
}
|
|
129
|
-
}, [editorActor, mutationActorRef, slateEditor])
|
|
130
|
-
|
|
131
|
-
return null
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
Synchronizer.displayName = 'Synchronizer'
|