@portabletext/editor 1.16.1 → 1.16.3
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 +222 -151
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +36 -113
- package/lib/index.d.ts +36 -113
- package/lib/index.js +223 -152
- package/lib/index.js.map +1 -1
- package/package.json +4 -4
- package/src/behavior-actions/behavior.actions.ts +0 -3
- package/src/editor/__tests__/RangeDecorations.test.tsx +23 -8
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +3 -3
- package/src/editor/editor-machine.ts +8 -7
- package/src/editor/sync-machine.ts +262 -202
|
@@ -3,13 +3,10 @@ import type {PortableTextBlock} from '@sanity/types'
|
|
|
3
3
|
import {isEqual} from 'lodash'
|
|
4
4
|
import {Editor, Text, Transforms, type Descendant, type Node} from 'slate'
|
|
5
5
|
import {
|
|
6
|
-
and,
|
|
7
6
|
assertEvent,
|
|
8
7
|
assign,
|
|
9
8
|
emit,
|
|
10
9
|
fromCallback,
|
|
11
|
-
not,
|
|
12
|
-
or,
|
|
13
10
|
setup,
|
|
14
11
|
type AnyEventObject,
|
|
15
12
|
type CallbackLogicFunction,
|
|
@@ -57,6 +54,7 @@ const syncValueCallback: CallbackLogicFunction<
|
|
|
57
54
|
schema: EditorSchema
|
|
58
55
|
}
|
|
59
56
|
slateEditor: PortableTextSlateEditor
|
|
57
|
+
streamBlocks: boolean
|
|
60
58
|
value: Array<PortableTextBlock> | undefined
|
|
61
59
|
}
|
|
62
60
|
> = ({sendBack, input}) => {
|
|
@@ -65,6 +63,7 @@ const syncValueCallback: CallbackLogicFunction<
|
|
|
65
63
|
sendBack,
|
|
66
64
|
slateEditor: input.slateEditor,
|
|
67
65
|
value: input.value,
|
|
66
|
+
streamBlocks: input.streamBlocks,
|
|
68
67
|
})
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -85,6 +84,7 @@ const syncValueLogic = fromCallback(syncValueCallback)
|
|
|
85
84
|
export const syncMachine = setup({
|
|
86
85
|
types: {
|
|
87
86
|
context: {} as {
|
|
87
|
+
initialValueSynced: boolean
|
|
88
88
|
isProcessingLocalChanges: boolean
|
|
89
89
|
keyGenerator: () => string
|
|
90
90
|
schema: EditorSchema
|
|
@@ -115,13 +115,18 @@ export const syncMachine = setup({
|
|
|
115
115
|
readOnly: boolean
|
|
116
116
|
}
|
|
117
117
|
| SyncValueEvent,
|
|
118
|
-
emitted: {} as
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
emitted: {} as
|
|
119
|
+
| PickFromUnion<
|
|
120
|
+
SyncValueEvent,
|
|
121
|
+
'type',
|
|
122
|
+
'invalid value' | 'patch' | 'value changed'
|
|
123
|
+
>
|
|
124
|
+
| {type: 'done syncing initial value'},
|
|
123
125
|
},
|
|
124
126
|
actions: {
|
|
127
|
+
'assign initial value synced': assign({
|
|
128
|
+
initialValueSynced: true,
|
|
129
|
+
}),
|
|
125
130
|
'assign readOnly': assign({
|
|
126
131
|
readOnly: ({event}) => {
|
|
127
132
|
assertEvent(event, 'update readOnly')
|
|
@@ -143,21 +148,19 @@ export const syncMachine = setup({
|
|
|
143
148
|
return event.value
|
|
144
149
|
},
|
|
145
150
|
}),
|
|
146
|
-
'emit done syncing': emit(
|
|
147
|
-
|
|
148
|
-
return event
|
|
151
|
+
'emit done syncing initial value': emit({
|
|
152
|
+
type: 'done syncing initial value',
|
|
149
153
|
}),
|
|
150
154
|
},
|
|
151
155
|
guards: {
|
|
152
|
-
'
|
|
153
|
-
'is
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
]),
|
|
156
|
+
'initial value synced': ({context}) => context.initialValueSynced,
|
|
157
|
+
'is busy': ({context}) => {
|
|
158
|
+
return (
|
|
159
|
+
!context.readOnly &&
|
|
160
|
+
(context.isProcessingLocalChanges ||
|
|
161
|
+
(isChangingRemotely(context.slateEditor) ?? false))
|
|
162
|
+
)
|
|
163
|
+
},
|
|
161
164
|
'value changed while syncing': ({context, event}) => {
|
|
162
165
|
assertEvent(event, 'done syncing')
|
|
163
166
|
return context.pendingValue !== event.value
|
|
@@ -174,6 +177,7 @@ export const syncMachine = setup({
|
|
|
174
177
|
}).createMachine({
|
|
175
178
|
id: 'sync',
|
|
176
179
|
context: ({input}) => ({
|
|
180
|
+
initialValueSynced: false,
|
|
177
181
|
isProcessingLocalChanges: false,
|
|
178
182
|
keyGenerator: input.keyGenerator,
|
|
179
183
|
schema: input.schema,
|
|
@@ -182,7 +186,6 @@ export const syncMachine = setup({
|
|
|
182
186
|
pendingValue: undefined,
|
|
183
187
|
previousValue: undefined,
|
|
184
188
|
}),
|
|
185
|
-
initial: 'idle',
|
|
186
189
|
on: {
|
|
187
190
|
'has pending patches': {
|
|
188
191
|
actions: assign({
|
|
@@ -198,95 +201,117 @@ export const syncMachine = setup({
|
|
|
198
201
|
actions: ['assign readOnly'],
|
|
199
202
|
},
|
|
200
203
|
},
|
|
204
|
+
type: 'parallel',
|
|
201
205
|
states: {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
target: 'syncing',
|
|
212
|
-
actions: ['assign pending value'],
|
|
206
|
+
'setting up': {
|
|
207
|
+
initial: 'syncing initial value',
|
|
208
|
+
states: {
|
|
209
|
+
'syncing initial value': {
|
|
210
|
+
always: {
|
|
211
|
+
guard: 'initial value synced',
|
|
212
|
+
target: 'done syncing initial value',
|
|
213
213
|
},
|
|
214
|
-
],
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
busy: {
|
|
218
|
-
after: {
|
|
219
|
-
1000: {
|
|
220
|
-
target: 'syncing',
|
|
221
214
|
},
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
guard: 'is busy',
|
|
227
|
-
actions: ['assign pending value'],
|
|
228
|
-
reenter: true,
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
target: 'syncing',
|
|
232
|
-
actions: ['assign pending value'],
|
|
233
|
-
},
|
|
234
|
-
],
|
|
215
|
+
'done syncing initial value': {
|
|
216
|
+
entry: ['emit done syncing initial value'],
|
|
217
|
+
type: 'final',
|
|
218
|
+
},
|
|
235
219
|
},
|
|
236
220
|
},
|
|
237
|
-
syncing: {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
221
|
+
'syncing': {
|
|
222
|
+
initial: 'idle',
|
|
223
|
+
states: {
|
|
224
|
+
idle: {
|
|
225
|
+
on: {
|
|
226
|
+
'update value': [
|
|
227
|
+
{
|
|
228
|
+
guard: 'is busy',
|
|
229
|
+
target: 'busy',
|
|
230
|
+
actions: ['assign pending value'],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
target: 'syncing',
|
|
234
|
+
actions: ['assign pending value'],
|
|
235
|
+
},
|
|
236
|
+
],
|
|
247
237
|
},
|
|
248
|
-
slateEditor: context.slateEditor,
|
|
249
|
-
value: context.pendingValue ?? undefined,
|
|
250
|
-
}),
|
|
251
|
-
},
|
|
252
|
-
always: {
|
|
253
|
-
guard: 'pending value equals previous value',
|
|
254
|
-
actions: [
|
|
255
|
-
emit(({context}) => ({
|
|
256
|
-
type: 'done syncing',
|
|
257
|
-
value: context.previousValue,
|
|
258
|
-
})),
|
|
259
|
-
],
|
|
260
|
-
target: 'idle',
|
|
261
|
-
},
|
|
262
|
-
on: {
|
|
263
|
-
'update value': {
|
|
264
|
-
actions: ['assign pending value'],
|
|
265
238
|
},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
guard: 'value changed while syncing',
|
|
278
|
-
actions: ['assign previous value', 'emit done syncing'],
|
|
279
|
-
reenter: true,
|
|
239
|
+
busy: {
|
|
240
|
+
after: {
|
|
241
|
+
1000: [
|
|
242
|
+
{
|
|
243
|
+
guard: 'is busy',
|
|
244
|
+
reenter: true,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
target: 'syncing',
|
|
248
|
+
},
|
|
249
|
+
],
|
|
280
250
|
},
|
|
281
|
-
{
|
|
251
|
+
on: {
|
|
252
|
+
'update value': [
|
|
253
|
+
{
|
|
254
|
+
actions: ['assign pending value'],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
syncing: {
|
|
260
|
+
always: {
|
|
261
|
+
guard: 'pending value equals previous value',
|
|
282
262
|
target: 'idle',
|
|
283
|
-
actions: [
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
263
|
+
actions: ['clear pending value', 'assign initial value synced'],
|
|
264
|
+
},
|
|
265
|
+
invoke: {
|
|
266
|
+
src: 'sync value',
|
|
267
|
+
id: 'sync value',
|
|
268
|
+
input: ({context}) => {
|
|
269
|
+
return {
|
|
270
|
+
context: {
|
|
271
|
+
keyGenerator: context.keyGenerator,
|
|
272
|
+
previousValue: context.previousValue,
|
|
273
|
+
readOnly: context.readOnly,
|
|
274
|
+
schema: context.schema,
|
|
275
|
+
},
|
|
276
|
+
slateEditor: context.slateEditor,
|
|
277
|
+
streamBlocks: !context.initialValueSynced,
|
|
278
|
+
value: context.pendingValue,
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
on: {
|
|
283
|
+
'update value': {
|
|
284
|
+
actions: ['assign pending value'],
|
|
285
|
+
},
|
|
286
|
+
'patch': {
|
|
287
|
+
actions: [emit(({event}) => event)],
|
|
288
|
+
},
|
|
289
|
+
'invalid value': {
|
|
290
|
+
actions: [emit(({event}) => event)],
|
|
291
|
+
},
|
|
292
|
+
'value changed': {
|
|
293
|
+
actions: [emit(({event}) => event)],
|
|
294
|
+
},
|
|
295
|
+
'done syncing': [
|
|
296
|
+
{
|
|
297
|
+
guard: 'value changed while syncing',
|
|
298
|
+
actions: [
|
|
299
|
+
'assign previous value',
|
|
300
|
+
'assign initial value synced',
|
|
301
|
+
],
|
|
302
|
+
reenter: true,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
target: 'idle',
|
|
306
|
+
actions: [
|
|
307
|
+
'clear pending value',
|
|
308
|
+
'assign previous value',
|
|
309
|
+
'assign initial value synced',
|
|
310
|
+
],
|
|
311
|
+
},
|
|
287
312
|
],
|
|
288
313
|
},
|
|
289
|
-
|
|
314
|
+
},
|
|
290
315
|
},
|
|
291
316
|
},
|
|
292
317
|
},
|
|
@@ -294,10 +319,11 @@ export const syncMachine = setup({
|
|
|
294
319
|
|
|
295
320
|
const debug = debugWithName('hook:useSyncValue')
|
|
296
321
|
|
|
297
|
-
function updateValue({
|
|
322
|
+
async function updateValue({
|
|
298
323
|
context,
|
|
299
324
|
sendBack,
|
|
300
325
|
slateEditor,
|
|
326
|
+
streamBlocks,
|
|
301
327
|
value,
|
|
302
328
|
}: {
|
|
303
329
|
context: {
|
|
@@ -308,6 +334,7 @@ function updateValue({
|
|
|
308
334
|
}
|
|
309
335
|
sendBack: (event: SyncValueEvent) => void
|
|
310
336
|
slateEditor: PortableTextSlateEditor
|
|
337
|
+
streamBlocks: boolean
|
|
311
338
|
value: PortableTextBlock[] | undefined
|
|
312
339
|
}) {
|
|
313
340
|
let isChanged = false
|
|
@@ -350,41 +377,45 @@ function updateValue({
|
|
|
350
377
|
schemaTypes: context.schema,
|
|
351
378
|
})
|
|
352
379
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
380
|
+
await new Promise<void>((resolve) => {
|
|
381
|
+
Editor.withoutNormalizing(slateEditor, () => {
|
|
382
|
+
withRemoteChanges(slateEditor, () => {
|
|
383
|
+
withoutPatching(slateEditor, async () => {
|
|
384
|
+
const childrenLength = slateEditor.children.length
|
|
357
385
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
386
|
+
// Remove blocks that have become superfluous
|
|
387
|
+
if (slateValueFromProps.length < childrenLength) {
|
|
388
|
+
for (
|
|
389
|
+
let i = childrenLength - 1;
|
|
390
|
+
i > slateValueFromProps.length - 1;
|
|
391
|
+
i--
|
|
392
|
+
) {
|
|
393
|
+
Transforms.removeNodes(slateEditor, {
|
|
394
|
+
at: [i],
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
isChanged = true
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
for await (const [currentBlock, currentBlockIndex] of getBlocks({
|
|
401
|
+
slateValue: slateValueFromProps,
|
|
402
|
+
streamBlocks,
|
|
403
|
+
})) {
|
|
404
|
+
// Go through all of the blocks and see if they need to be updated
|
|
405
|
+
const {blockChanged, blockValid} = syncBlock({
|
|
406
|
+
context,
|
|
407
|
+
sendBack,
|
|
408
|
+
block: currentBlock,
|
|
409
|
+
index: currentBlockIndex,
|
|
410
|
+
slateEditor,
|
|
411
|
+
value,
|
|
367
412
|
})
|
|
413
|
+
isChanged = blockChanged || isChanged
|
|
414
|
+
isValid = isValid && blockValid
|
|
368
415
|
}
|
|
369
|
-
isChanged = true
|
|
370
|
-
}
|
|
371
416
|
|
|
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
|
-
}
|
|
417
|
+
resolve()
|
|
418
|
+
})
|
|
388
419
|
})
|
|
389
420
|
})
|
|
390
421
|
})
|
|
@@ -425,6 +456,23 @@ function updateValue({
|
|
|
425
456
|
sendBack({type: 'done syncing', value})
|
|
426
457
|
}
|
|
427
458
|
|
|
459
|
+
async function* getBlocks({
|
|
460
|
+
slateValue,
|
|
461
|
+
streamBlocks,
|
|
462
|
+
}: {
|
|
463
|
+
slateValue: Array<Descendant>
|
|
464
|
+
streamBlocks: boolean
|
|
465
|
+
}) {
|
|
466
|
+
let index = 0
|
|
467
|
+
for await (const block of slateValue) {
|
|
468
|
+
if (streamBlocks) {
|
|
469
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
|
470
|
+
}
|
|
471
|
+
yield [block, index] as const
|
|
472
|
+
index++
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
428
476
|
function syncBlock({
|
|
429
477
|
context,
|
|
430
478
|
sendBack,
|
|
@@ -452,79 +500,91 @@ function syncBlock({
|
|
|
452
500
|
const oldBlock = slateEditor.children[currentBlockIndex]
|
|
453
501
|
const hasChanges = oldBlock && !isEqual(currentBlock, oldBlock)
|
|
454
502
|
|
|
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
|
-
|
|
503
|
+
Editor.withoutNormalizing(slateEditor, () => {
|
|
504
|
+
withRemoteChanges(slateEditor, () => {
|
|
505
|
+
withoutPatching(slateEditor, () => {
|
|
506
|
+
if (hasChanges && blockValid) {
|
|
507
|
+
const validationValue = [value[currentBlockIndex]]
|
|
508
|
+
const validation = validateValue(
|
|
509
|
+
validationValue,
|
|
510
|
+
context.schema,
|
|
511
|
+
context.keyGenerator,
|
|
512
|
+
)
|
|
513
|
+
// Resolve validations that can be resolved automatically, without involving the user (but only if the value was changed)
|
|
514
|
+
if (
|
|
515
|
+
!validation.valid &&
|
|
516
|
+
validation.resolution?.autoResolve &&
|
|
517
|
+
validation.resolution?.patches.length > 0
|
|
518
|
+
) {
|
|
519
|
+
// Only apply auto resolution if the value has been populated before and is different from the last one.
|
|
520
|
+
if (
|
|
521
|
+
!context.readOnly &&
|
|
522
|
+
context.previousValue &&
|
|
523
|
+
context.previousValue !== value
|
|
524
|
+
) {
|
|
525
|
+
// Give a console warning about the fact that it did an auto resolution
|
|
526
|
+
console.warn(
|
|
527
|
+
`${validation.resolution.action} for block with _key '${validationValue[0]._key}'. ${validation.resolution?.description}`,
|
|
528
|
+
)
|
|
529
|
+
validation.resolution.patches.forEach((patch) => {
|
|
530
|
+
sendBack({type: 'patch', patch})
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (validation.valid || validation.resolution?.autoResolve) {
|
|
535
|
+
if (oldBlock._key === currentBlock._key) {
|
|
536
|
+
if (debug.enabled) debug('Updating block', oldBlock, currentBlock)
|
|
537
|
+
_updateBlock(
|
|
538
|
+
slateEditor,
|
|
539
|
+
currentBlock,
|
|
540
|
+
oldBlock,
|
|
541
|
+
currentBlockIndex,
|
|
542
|
+
)
|
|
543
|
+
} else {
|
|
544
|
+
if (debug.enabled)
|
|
545
|
+
debug('Replacing block', oldBlock, currentBlock)
|
|
546
|
+
_replaceBlock(slateEditor, currentBlock, currentBlockIndex)
|
|
547
|
+
}
|
|
548
|
+
blockChanged = true
|
|
549
|
+
} else {
|
|
550
|
+
sendBack({
|
|
551
|
+
type: 'invalid value',
|
|
552
|
+
resolution: validation.resolution,
|
|
553
|
+
value,
|
|
554
|
+
})
|
|
555
|
+
blockValid = false
|
|
556
|
+
}
|
|
557
|
+
}
|
|
501
558
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
559
|
+
if (!oldBlock && blockValid) {
|
|
560
|
+
const validationValue = [value[currentBlockIndex]]
|
|
561
|
+
const validation = validateValue(
|
|
562
|
+
validationValue,
|
|
563
|
+
context.schema,
|
|
564
|
+
context.keyGenerator,
|
|
565
|
+
)
|
|
566
|
+
if (debug.enabled)
|
|
567
|
+
debug(
|
|
568
|
+
'Validating and inserting new block in the end of the value',
|
|
569
|
+
currentBlock,
|
|
570
|
+
)
|
|
571
|
+
if (validation.valid || validation.resolution?.autoResolve) {
|
|
572
|
+
Transforms.insertNodes(slateEditor, currentBlock, {
|
|
573
|
+
at: [currentBlockIndex],
|
|
574
|
+
})
|
|
575
|
+
} else {
|
|
576
|
+
debug('Invalid', validation)
|
|
577
|
+
sendBack({
|
|
578
|
+
type: 'invalid value',
|
|
579
|
+
resolution: validation.resolution,
|
|
580
|
+
value,
|
|
581
|
+
})
|
|
582
|
+
blockValid = false
|
|
583
|
+
}
|
|
584
|
+
}
|
|
524
585
|
})
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
586
|
+
})
|
|
587
|
+
})
|
|
528
588
|
|
|
529
589
|
return {blockChanged, blockValid}
|
|
530
590
|
}
|