@portabletext/editor 1.16.0 → 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 +144 -72
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +154 -87
- package/lib/index.d.ts +154 -87
- package/lib/index.js +144 -72
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/editor/PortableTextEditor.tsx +2 -1
- package/src/editor/__tests__/RangeDecorations.test.tsx +23 -8
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +3 -3
- package/src/editor/components/Synchronizer.tsx +1 -1
- package/src/editor/create-editor.ts +4 -1
- package/src/editor/editor-machine.ts +8 -5
- package/src/editor/sync-machine.ts +171 -117
package/package.json
CHANGED
|
@@ -173,7 +173,8 @@ export class PortableTextEditor extends Component<
|
|
|
173
173
|
if (!this.props.editor && !prevProps.editor) {
|
|
174
174
|
if (this.props.readOnly !== prevProps.readOnly) {
|
|
175
175
|
this.editor._internal.editorActor.send({
|
|
176
|
-
type: '
|
|
176
|
+
type: 'update readOnly',
|
|
177
|
+
readOnly: this.props.readOnly ?? false,
|
|
177
178
|
})
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -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: [],
|
|
@@ -71,7 +71,7 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
71
71
|
}, [props.editorActor, syncActorRef])
|
|
72
72
|
|
|
73
73
|
useEffect(() => {
|
|
74
|
-
syncActorRef.send({type: '
|
|
74
|
+
syncActorRef.send({type: 'update readOnly', readOnly})
|
|
75
75
|
}, [syncActorRef, readOnly])
|
|
76
76
|
|
|
77
77
|
useEffect(() => {
|
|
@@ -33,6 +33,9 @@ import {createEditableAPI} from './plugins/createWithEditableAPI'
|
|
|
33
33
|
export type EditorConfig = {
|
|
34
34
|
behaviors?: Array<Behavior>
|
|
35
35
|
keyGenerator?: () => string
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated Will be removed in the next major version
|
|
38
|
+
*/
|
|
36
39
|
maxBlocks?: number
|
|
37
40
|
readOnly?: boolean
|
|
38
41
|
initialValue?: Array<PortableTextBlock>
|
|
@@ -64,8 +67,8 @@ export type EditorEvent = PickFromUnion<
|
|
|
64
67
|
| 'list item.toggle'
|
|
65
68
|
| 'style.toggle'
|
|
66
69
|
| 'patches'
|
|
67
|
-
| 'toggle readOnly'
|
|
68
70
|
| 'update behaviors'
|
|
71
|
+
| 'update readOnly'
|
|
69
72
|
| 'update value'
|
|
70
73
|
>
|
|
71
74
|
|
|
@@ -81,6 +81,10 @@ export type InternalEditorEvent =
|
|
|
81
81
|
editor: PortableTextSlateEditor
|
|
82
82
|
actionIntends: Array<BehaviorActionIntend>
|
|
83
83
|
}
|
|
84
|
+
| {
|
|
85
|
+
type: 'update readOnly'
|
|
86
|
+
readOnly: boolean
|
|
87
|
+
}
|
|
84
88
|
| {
|
|
85
89
|
type: 'update schema'
|
|
86
90
|
schema: EditorSchema
|
|
@@ -93,9 +97,6 @@ export type InternalEditorEvent =
|
|
|
93
97
|
type: 'update value'
|
|
94
98
|
value: Array<PortableTextBlock> | undefined
|
|
95
99
|
}
|
|
96
|
-
| {
|
|
97
|
-
type: 'toggle readOnly'
|
|
98
|
-
}
|
|
99
100
|
| {
|
|
100
101
|
type: 'update maxBlocks'
|
|
101
102
|
maxBlocks: number | undefined
|
|
@@ -439,7 +440,8 @@ export const editorMachine = setup({
|
|
|
439
440
|
},
|
|
440
441
|
'read only': {
|
|
441
442
|
on: {
|
|
442
|
-
'
|
|
443
|
+
'update readOnly': {
|
|
444
|
+
guard: ({event}) => !event.readOnly,
|
|
443
445
|
target: '#editor.edit mode.editable',
|
|
444
446
|
actions: ['emit editable'],
|
|
445
447
|
},
|
|
@@ -449,7 +451,8 @@ export const editorMachine = setup({
|
|
|
449
451
|
},
|
|
450
452
|
'editable': {
|
|
451
453
|
on: {
|
|
452
|
-
'
|
|
454
|
+
'update readOnly': {
|
|
455
|
+
guard: ({event}) => event.readOnly,
|
|
453
456
|
target: '#editor.edit mode.read only.read only',
|
|
454
457
|
actions: ['emit read only'],
|
|
455
458
|
},
|
|
@@ -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
|
|
@@ -111,7 +114,8 @@ export const syncMachine = setup({
|
|
|
111
114
|
value: Array<PortableTextBlock> | undefined
|
|
112
115
|
}
|
|
113
116
|
| {
|
|
114
|
-
type: '
|
|
117
|
+
type: 'update readOnly'
|
|
118
|
+
readOnly: boolean
|
|
115
119
|
}
|
|
116
120
|
| SyncValueEvent,
|
|
117
121
|
emitted: {} as PickFromUnion<
|
|
@@ -121,8 +125,14 @@ export const syncMachine = setup({
|
|
|
121
125
|
>,
|
|
122
126
|
},
|
|
123
127
|
actions: {
|
|
128
|
+
'assign initial value synced': assign({
|
|
129
|
+
initialValueSynced: true,
|
|
130
|
+
}),
|
|
124
131
|
'assign readOnly': assign({
|
|
125
|
-
readOnly: ({
|
|
132
|
+
readOnly: ({event}) => {
|
|
133
|
+
assertEvent(event, 'update readOnly')
|
|
134
|
+
return event.readOnly
|
|
135
|
+
},
|
|
126
136
|
}),
|
|
127
137
|
'assign pending value': assign({
|
|
128
138
|
pendingValue: ({event}) => {
|
|
@@ -170,6 +180,7 @@ export const syncMachine = setup({
|
|
|
170
180
|
}).createMachine({
|
|
171
181
|
id: 'sync',
|
|
172
182
|
context: ({input}) => ({
|
|
183
|
+
initialValueSynced: false,
|
|
173
184
|
isProcessingLocalChanges: false,
|
|
174
185
|
keyGenerator: input.keyGenerator,
|
|
175
186
|
schema: input.schema,
|
|
@@ -190,7 +201,7 @@ export const syncMachine = setup({
|
|
|
190
201
|
isProcessingLocalChanges: false,
|
|
191
202
|
}),
|
|
192
203
|
},
|
|
193
|
-
'
|
|
204
|
+
'update readOnly': {
|
|
194
205
|
actions: ['assign readOnly'],
|
|
195
206
|
},
|
|
196
207
|
},
|
|
@@ -234,16 +245,19 @@ export const syncMachine = setup({
|
|
|
234
245
|
invoke: {
|
|
235
246
|
src: 'sync value',
|
|
236
247
|
id: 'sync value',
|
|
237
|
-
input: ({context}) =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
},
|
|
247
261
|
},
|
|
248
262
|
always: {
|
|
249
263
|
guard: 'pending value equals previous value',
|
|
@@ -271,7 +285,11 @@ export const syncMachine = setup({
|
|
|
271
285
|
'done syncing': [
|
|
272
286
|
{
|
|
273
287
|
guard: 'value changed while syncing',
|
|
274
|
-
actions: [
|
|
288
|
+
actions: [
|
|
289
|
+
'assign previous value',
|
|
290
|
+
'emit done syncing',
|
|
291
|
+
'assign initial value synced',
|
|
292
|
+
],
|
|
275
293
|
reenter: true,
|
|
276
294
|
},
|
|
277
295
|
{
|
|
@@ -280,6 +298,7 @@ export const syncMachine = setup({
|
|
|
280
298
|
'clear pending value',
|
|
281
299
|
'assign previous value',
|
|
282
300
|
'emit done syncing',
|
|
301
|
+
'assign initial value synced',
|
|
283
302
|
],
|
|
284
303
|
},
|
|
285
304
|
],
|
|
@@ -290,10 +309,11 @@ export const syncMachine = setup({
|
|
|
290
309
|
|
|
291
310
|
const debug = debugWithName('hook:useSyncValue')
|
|
292
311
|
|
|
293
|
-
function updateValue({
|
|
312
|
+
async function updateValue({
|
|
294
313
|
context,
|
|
295
314
|
sendBack,
|
|
296
315
|
slateEditor,
|
|
316
|
+
streamBlocks,
|
|
297
317
|
value,
|
|
298
318
|
}: {
|
|
299
319
|
context: {
|
|
@@ -304,6 +324,7 @@ function updateValue({
|
|
|
304
324
|
}
|
|
305
325
|
sendBack: (event: SyncValueEvent) => void
|
|
306
326
|
slateEditor: PortableTextSlateEditor
|
|
327
|
+
streamBlocks: boolean
|
|
307
328
|
value: PortableTextBlock[] | undefined
|
|
308
329
|
}) {
|
|
309
330
|
let isChanged = false
|
|
@@ -346,41 +367,45 @@ function updateValue({
|
|
|
346
367
|
schemaTypes: context.schema,
|
|
347
368
|
})
|
|
348
369
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
370
|
+
await new Promise<void>((resolve) => {
|
|
371
|
+
Editor.withoutNormalizing(slateEditor, () => {
|
|
372
|
+
withRemoteChanges(slateEditor, () => {
|
|
373
|
+
withoutPatching(slateEditor, async () => {
|
|
374
|
+
const childrenLength = slateEditor.children.length
|
|
353
375
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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,
|
|
363
402
|
})
|
|
403
|
+
isChanged = blockChanged || isChanged
|
|
404
|
+
isValid = isValid && blockValid
|
|
364
405
|
}
|
|
365
|
-
isChanged = true
|
|
366
|
-
}
|
|
367
406
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
currentBlock,
|
|
371
|
-
] of slateValueFromProps.entries()) {
|
|
372
|
-
// Go through all of the blocks and see if they need to be updated
|
|
373
|
-
const {blockChanged, blockValid} = syncBlock({
|
|
374
|
-
context,
|
|
375
|
-
sendBack,
|
|
376
|
-
block: currentBlock,
|
|
377
|
-
index: currentBlockIndex,
|
|
378
|
-
slateEditor,
|
|
379
|
-
value,
|
|
380
|
-
})
|
|
381
|
-
isChanged = blockChanged || isChanged
|
|
382
|
-
isValid = isValid && blockValid
|
|
383
|
-
}
|
|
407
|
+
resolve()
|
|
408
|
+
})
|
|
384
409
|
})
|
|
385
410
|
})
|
|
386
411
|
})
|
|
@@ -421,6 +446,23 @@ function updateValue({
|
|
|
421
446
|
sendBack({type: 'done syncing', value})
|
|
422
447
|
}
|
|
423
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
|
+
|
|
424
466
|
function syncBlock({
|
|
425
467
|
context,
|
|
426
468
|
sendBack,
|
|
@@ -448,79 +490,91 @@ function syncBlock({
|
|
|
448
490
|
const oldBlock = slateEditor.children[currentBlockIndex]
|
|
449
491
|
const hasChanges = oldBlock && !isEqual(currentBlock, oldBlock)
|
|
450
492
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
497
548
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
+
}
|
|
520
575
|
})
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
576
|
+
})
|
|
577
|
+
})
|
|
524
578
|
|
|
525
579
|
return {blockChanged, blockValid}
|
|
526
580
|
}
|