@portabletext/editor 1.16.1 → 1.16.2
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.cjs +119 -54
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +119 -54
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/editor/__tests__/RangeDecorations.test.tsx +23 -8
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +3 -3
- package/src/editor/sync-machine.ts +164 -114
package/package.json
CHANGED
|
@@ -34,18 +34,33 @@ describe('RangeDecorations', () => {
|
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
|
|
38
|
+
const {rerender} = await waitFor(() =>
|
|
39
|
+
render(
|
|
40
|
+
<PortableTextEditorTester
|
|
41
|
+
onChange={onChange}
|
|
42
|
+
rangeDecorations={rangeDecorations}
|
|
43
|
+
ref={editorRef}
|
|
44
|
+
schemaType={schemaType}
|
|
45
|
+
value={value}
|
|
46
|
+
/>,
|
|
47
|
+
),
|
|
45
48
|
)
|
|
49
|
+
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
if (editorRef.current) {
|
|
52
|
+
expect(onChange).toHaveBeenCalledWith({
|
|
53
|
+
type: 'value',
|
|
54
|
+
value,
|
|
55
|
+
})
|
|
56
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
46
60
|
await waitFor(() => {
|
|
47
61
|
expect([rangeDecorationIteration, 'initial']).toEqual([1, 'initial'])
|
|
48
62
|
})
|
|
63
|
+
|
|
49
64
|
// Re-render with the same range decorations
|
|
50
65
|
rerender(
|
|
51
66
|
<PortableTextEditorTester
|
|
@@ -48,7 +48,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
48
48
|
_type: 'myTestBlockType',
|
|
49
49
|
children: [
|
|
50
50
|
{
|
|
51
|
-
_key: '
|
|
51
|
+
_key: '2',
|
|
52
52
|
_type: 'span',
|
|
53
53
|
text: 'Hello with a new key',
|
|
54
54
|
marks: [],
|
|
@@ -162,7 +162,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
162
162
|
_type: 'myTestBlockType',
|
|
163
163
|
children: [
|
|
164
164
|
{
|
|
165
|
-
_key: '
|
|
165
|
+
_key: '2',
|
|
166
166
|
_type: 'span',
|
|
167
167
|
text: '',
|
|
168
168
|
marks: [],
|
|
@@ -176,7 +176,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
176
176
|
_type: 'myTestBlockType',
|
|
177
177
|
children: [
|
|
178
178
|
{
|
|
179
|
-
_key: '
|
|
179
|
+
_key: '4',
|
|
180
180
|
_type: 'span',
|
|
181
181
|
text: '',
|
|
182
182
|
marks: [],
|
|
@@ -57,6 +57,7 @@ const syncValueCallback: CallbackLogicFunction<
|
|
|
57
57
|
schema: EditorSchema
|
|
58
58
|
}
|
|
59
59
|
slateEditor: PortableTextSlateEditor
|
|
60
|
+
streamBlocks: boolean
|
|
60
61
|
value: Array<PortableTextBlock> | undefined
|
|
61
62
|
}
|
|
62
63
|
> = ({sendBack, input}) => {
|
|
@@ -65,6 +66,7 @@ const syncValueCallback: CallbackLogicFunction<
|
|
|
65
66
|
sendBack,
|
|
66
67
|
slateEditor: input.slateEditor,
|
|
67
68
|
value: input.value,
|
|
69
|
+
streamBlocks: input.streamBlocks,
|
|
68
70
|
})
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -85,6 +87,7 @@ const syncValueLogic = fromCallback(syncValueCallback)
|
|
|
85
87
|
export const syncMachine = setup({
|
|
86
88
|
types: {
|
|
87
89
|
context: {} as {
|
|
90
|
+
initialValueSynced: boolean
|
|
88
91
|
isProcessingLocalChanges: boolean
|
|
89
92
|
keyGenerator: () => string
|
|
90
93
|
schema: EditorSchema
|
|
@@ -122,6 +125,9 @@ export const syncMachine = setup({
|
|
|
122
125
|
>,
|
|
123
126
|
},
|
|
124
127
|
actions: {
|
|
128
|
+
'assign initial value synced': assign({
|
|
129
|
+
initialValueSynced: true,
|
|
130
|
+
}),
|
|
125
131
|
'assign readOnly': assign({
|
|
126
132
|
readOnly: ({event}) => {
|
|
127
133
|
assertEvent(event, 'update readOnly')
|
|
@@ -174,6 +180,7 @@ export const syncMachine = setup({
|
|
|
174
180
|
}).createMachine({
|
|
175
181
|
id: 'sync',
|
|
176
182
|
context: ({input}) => ({
|
|
183
|
+
initialValueSynced: false,
|
|
177
184
|
isProcessingLocalChanges: false,
|
|
178
185
|
keyGenerator: input.keyGenerator,
|
|
179
186
|
schema: input.schema,
|
|
@@ -238,16 +245,19 @@ export const syncMachine = setup({
|
|
|
238
245
|
invoke: {
|
|
239
246
|
src: 'sync value',
|
|
240
247
|
id: 'sync value',
|
|
241
|
-
input: ({context}) =>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
input: ({context}) => {
|
|
249
|
+
return {
|
|
250
|
+
context: {
|
|
251
|
+
keyGenerator: context.keyGenerator,
|
|
252
|
+
previousValue: context.previousValue,
|
|
253
|
+
readOnly: context.readOnly,
|
|
254
|
+
schema: context.schema,
|
|
255
|
+
},
|
|
256
|
+
slateEditor: context.slateEditor,
|
|
257
|
+
streamBlocks: !context.initialValueSynced,
|
|
258
|
+
value: context.pendingValue ?? undefined,
|
|
259
|
+
}
|
|
260
|
+
},
|
|
251
261
|
},
|
|
252
262
|
always: {
|
|
253
263
|
guard: 'pending value equals previous value',
|
|
@@ -275,7 +285,11 @@ export const syncMachine = setup({
|
|
|
275
285
|
'done syncing': [
|
|
276
286
|
{
|
|
277
287
|
guard: 'value changed while syncing',
|
|
278
|
-
actions: [
|
|
288
|
+
actions: [
|
|
289
|
+
'assign previous value',
|
|
290
|
+
'emit done syncing',
|
|
291
|
+
'assign initial value synced',
|
|
292
|
+
],
|
|
279
293
|
reenter: true,
|
|
280
294
|
},
|
|
281
295
|
{
|
|
@@ -284,6 +298,7 @@ export const syncMachine = setup({
|
|
|
284
298
|
'clear pending value',
|
|
285
299
|
'assign previous value',
|
|
286
300
|
'emit done syncing',
|
|
301
|
+
'assign initial value synced',
|
|
287
302
|
],
|
|
288
303
|
},
|
|
289
304
|
],
|
|
@@ -294,10 +309,11 @@ export const syncMachine = setup({
|
|
|
294
309
|
|
|
295
310
|
const debug = debugWithName('hook:useSyncValue')
|
|
296
311
|
|
|
297
|
-
function updateValue({
|
|
312
|
+
async function updateValue({
|
|
298
313
|
context,
|
|
299
314
|
sendBack,
|
|
300
315
|
slateEditor,
|
|
316
|
+
streamBlocks,
|
|
301
317
|
value,
|
|
302
318
|
}: {
|
|
303
319
|
context: {
|
|
@@ -308,6 +324,7 @@ function updateValue({
|
|
|
308
324
|
}
|
|
309
325
|
sendBack: (event: SyncValueEvent) => void
|
|
310
326
|
slateEditor: PortableTextSlateEditor
|
|
327
|
+
streamBlocks: boolean
|
|
311
328
|
value: PortableTextBlock[] | undefined
|
|
312
329
|
}) {
|
|
313
330
|
let isChanged = false
|
|
@@ -350,41 +367,45 @@ function updateValue({
|
|
|
350
367
|
schemaTypes: context.schema,
|
|
351
368
|
})
|
|
352
369
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
370
|
+
await new Promise<void>((resolve) => {
|
|
371
|
+
Editor.withoutNormalizing(slateEditor, () => {
|
|
372
|
+
withRemoteChanges(slateEditor, () => {
|
|
373
|
+
withoutPatching(slateEditor, async () => {
|
|
374
|
+
const childrenLength = slateEditor.children.length
|
|
357
375
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
376
|
+
// Remove blocks that have become superfluous
|
|
377
|
+
if (slateValueFromProps.length < childrenLength) {
|
|
378
|
+
for (
|
|
379
|
+
let i = childrenLength - 1;
|
|
380
|
+
i > slateValueFromProps.length - 1;
|
|
381
|
+
i--
|
|
382
|
+
) {
|
|
383
|
+
Transforms.removeNodes(slateEditor, {
|
|
384
|
+
at: [i],
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
isChanged = true
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for await (const [currentBlock, currentBlockIndex] of getBlocks({
|
|
391
|
+
slateValue: slateValueFromProps,
|
|
392
|
+
streamBlocks,
|
|
393
|
+
})) {
|
|
394
|
+
// Go through all of the blocks and see if they need to be updated
|
|
395
|
+
const {blockChanged, blockValid} = syncBlock({
|
|
396
|
+
context,
|
|
397
|
+
sendBack,
|
|
398
|
+
block: currentBlock,
|
|
399
|
+
index: currentBlockIndex,
|
|
400
|
+
slateEditor,
|
|
401
|
+
value,
|
|
367
402
|
})
|
|
403
|
+
isChanged = blockChanged || isChanged
|
|
404
|
+
isValid = isValid && blockValid
|
|
368
405
|
}
|
|
369
|
-
isChanged = true
|
|
370
|
-
}
|
|
371
406
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
currentBlock,
|
|
375
|
-
] of slateValueFromProps.entries()) {
|
|
376
|
-
// Go through all of the blocks and see if they need to be updated
|
|
377
|
-
const {blockChanged, blockValid} = syncBlock({
|
|
378
|
-
context,
|
|
379
|
-
sendBack,
|
|
380
|
-
block: currentBlock,
|
|
381
|
-
index: currentBlockIndex,
|
|
382
|
-
slateEditor,
|
|
383
|
-
value,
|
|
384
|
-
})
|
|
385
|
-
isChanged = blockChanged || isChanged
|
|
386
|
-
isValid = isValid && blockValid
|
|
387
|
-
}
|
|
407
|
+
resolve()
|
|
408
|
+
})
|
|
388
409
|
})
|
|
389
410
|
})
|
|
390
411
|
})
|
|
@@ -425,6 +446,23 @@ function updateValue({
|
|
|
425
446
|
sendBack({type: 'done syncing', value})
|
|
426
447
|
}
|
|
427
448
|
|
|
449
|
+
async function* getBlocks({
|
|
450
|
+
slateValue,
|
|
451
|
+
streamBlocks,
|
|
452
|
+
}: {
|
|
453
|
+
slateValue: Array<Descendant>
|
|
454
|
+
streamBlocks: boolean
|
|
455
|
+
}) {
|
|
456
|
+
let index = 0
|
|
457
|
+
for await (const block of slateValue) {
|
|
458
|
+
if (streamBlocks) {
|
|
459
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
|
460
|
+
}
|
|
461
|
+
yield [block, index] as const
|
|
462
|
+
index++
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
428
466
|
function syncBlock({
|
|
429
467
|
context,
|
|
430
468
|
sendBack,
|
|
@@ -452,79 +490,91 @@ function syncBlock({
|
|
|
452
490
|
const oldBlock = slateEditor.children[currentBlockIndex]
|
|
453
491
|
const hasChanges = oldBlock && !isEqual(currentBlock, oldBlock)
|
|
454
492
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
493
|
+
Editor.withoutNormalizing(slateEditor, () => {
|
|
494
|
+
withRemoteChanges(slateEditor, () => {
|
|
495
|
+
withoutPatching(slateEditor, () => {
|
|
496
|
+
if (hasChanges && blockValid) {
|
|
497
|
+
const validationValue = [value[currentBlockIndex]]
|
|
498
|
+
const validation = validateValue(
|
|
499
|
+
validationValue,
|
|
500
|
+
context.schema,
|
|
501
|
+
context.keyGenerator,
|
|
502
|
+
)
|
|
503
|
+
// Resolve validations that can be resolved automatically, without involving the user (but only if the value was changed)
|
|
504
|
+
if (
|
|
505
|
+
!validation.valid &&
|
|
506
|
+
validation.resolution?.autoResolve &&
|
|
507
|
+
validation.resolution?.patches.length > 0
|
|
508
|
+
) {
|
|
509
|
+
// Only apply auto resolution if the value has been populated before and is different from the last one.
|
|
510
|
+
if (
|
|
511
|
+
!context.readOnly &&
|
|
512
|
+
context.previousValue &&
|
|
513
|
+
context.previousValue !== value
|
|
514
|
+
) {
|
|
515
|
+
// Give a console warning about the fact that it did an auto resolution
|
|
516
|
+
console.warn(
|
|
517
|
+
`${validation.resolution.action} for block with _key '${validationValue[0]._key}'. ${validation.resolution?.description}`,
|
|
518
|
+
)
|
|
519
|
+
validation.resolution.patches.forEach((patch) => {
|
|
520
|
+
sendBack({type: 'patch', patch})
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (validation.valid || validation.resolution?.autoResolve) {
|
|
525
|
+
if (oldBlock._key === currentBlock._key) {
|
|
526
|
+
if (debug.enabled) debug('Updating block', oldBlock, currentBlock)
|
|
527
|
+
_updateBlock(
|
|
528
|
+
slateEditor,
|
|
529
|
+
currentBlock,
|
|
530
|
+
oldBlock,
|
|
531
|
+
currentBlockIndex,
|
|
532
|
+
)
|
|
533
|
+
} else {
|
|
534
|
+
if (debug.enabled)
|
|
535
|
+
debug('Replacing block', oldBlock, currentBlock)
|
|
536
|
+
_replaceBlock(slateEditor, currentBlock, currentBlockIndex)
|
|
537
|
+
}
|
|
538
|
+
blockChanged = true
|
|
539
|
+
} else {
|
|
540
|
+
sendBack({
|
|
541
|
+
type: 'invalid value',
|
|
542
|
+
resolution: validation.resolution,
|
|
543
|
+
value,
|
|
544
|
+
})
|
|
545
|
+
blockValid = false
|
|
546
|
+
}
|
|
547
|
+
}
|
|
501
548
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
549
|
+
if (!oldBlock && blockValid) {
|
|
550
|
+
const validationValue = [value[currentBlockIndex]]
|
|
551
|
+
const validation = validateValue(
|
|
552
|
+
validationValue,
|
|
553
|
+
context.schema,
|
|
554
|
+
context.keyGenerator,
|
|
555
|
+
)
|
|
556
|
+
if (debug.enabled)
|
|
557
|
+
debug(
|
|
558
|
+
'Validating and inserting new block in the end of the value',
|
|
559
|
+
currentBlock,
|
|
560
|
+
)
|
|
561
|
+
if (validation.valid || validation.resolution?.autoResolve) {
|
|
562
|
+
Transforms.insertNodes(slateEditor, currentBlock, {
|
|
563
|
+
at: [currentBlockIndex],
|
|
564
|
+
})
|
|
565
|
+
} else {
|
|
566
|
+
debug('Invalid', validation)
|
|
567
|
+
sendBack({
|
|
568
|
+
type: 'invalid value',
|
|
569
|
+
resolution: validation.resolution,
|
|
570
|
+
value,
|
|
571
|
+
})
|
|
572
|
+
blockValid = false
|
|
573
|
+
}
|
|
574
|
+
}
|
|
524
575
|
})
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
576
|
+
})
|
|
577
|
+
})
|
|
528
578
|
|
|
529
579
|
return {blockChanged, blockValid}
|
|
530
580
|
}
|