@wordpress/core-data 7.43.0 → 7.44.0
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/CHANGELOG.md +7 -1
- package/build/entities.cjs +28 -20
- package/build/entities.cjs.map +2 -2
- package/build/hooks/index.cjs +3 -3
- package/build/hooks/index.cjs.map +1 -1
- package/build/hooks/use-entity-prop.cjs +1 -1
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-entity-record.cjs +4 -4
- package/build/hooks/use-entity-record.cjs.map +2 -2
- package/build/hooks/use-entity-records.cjs +3 -3
- package/build/hooks/use-entity-records.cjs.map +2 -2
- package/build/hooks/use-query-select.cjs +2 -2
- package/build/hooks/use-query-select.cjs.map +2 -2
- package/build/hooks/use-resource-permissions.cjs +4 -4
- package/build/hooks/use-resource-permissions.cjs.map +2 -2
- package/build/queried-data/reducer.cjs +2 -1
- package/build/queried-data/reducer.cjs.map +2 -2
- package/build/queried-data/selectors.cjs +21 -17
- package/build/queried-data/selectors.cjs.map +2 -2
- package/build/resolvers.cjs +6 -6
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +4 -2
- package/build/selectors.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +151 -31
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt.cjs +8 -0
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/entities.mjs +29 -20
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/index.mjs +6 -6
- package/build-module/hooks/index.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +1 -1
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-entity-record.mjs +3 -3
- package/build-module/hooks/use-entity-record.mjs.map +2 -2
- package/build-module/hooks/use-entity-records.mjs +2 -2
- package/build-module/hooks/use-entity-records.mjs.map +2 -2
- package/build-module/hooks/use-query-select.mjs +1 -1
- package/build-module/hooks/use-query-select.mjs.map +1 -1
- package/build-module/hooks/use-resource-permissions.mjs +3 -3
- package/build-module/hooks/use-resource-permissions.mjs.map +2 -2
- package/build-module/queried-data/reducer.mjs +2 -1
- package/build-module/queried-data/reducer.mjs.map +2 -2
- package/build-module/queried-data/selectors.mjs +21 -17
- package/build-module/queried-data/selectors.mjs.map +2 -2
- package/build-module/resolvers.mjs +6 -6
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +4 -2
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +151 -31
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +7 -0
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/entities.d.ts +51 -32
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/index.d.ts +3 -3
- package/build-types/hooks/index.d.ts.map +1 -1
- package/build-types/hooks/use-entity-record.d.ts +1 -1
- package/build-types/hooks/use-entity-record.d.ts.map +1 -1
- package/build-types/hooks/use-entity-records.d.ts +1 -1
- package/build-types/hooks/use-entity-records.d.ts.map +1 -1
- package/build-types/hooks/use-resource-permissions.d.ts +1 -1
- package/build-types/hooks/use-resource-permissions.d.ts.map +1 -1
- package/build-types/index.d.ts.map +1 -1
- package/build-types/queried-data/reducer.d.ts.map +1 -1
- package/build-types/queried-data/selectors.d.ts +5 -3
- package/build-types/queried-data/selectors.d.ts.map +1 -1
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-selection.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +7 -0
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/entities.js +16 -8
- package/src/hooks/index.ts +3 -3
- package/src/hooks/test/use-entity-records.js +1 -1
- package/src/hooks/use-entity-prop.js +1 -1
- package/src/hooks/use-entity-record.ts +1 -1
- package/src/hooks/use-entity-records.ts +1 -1
- package/src/hooks/use-query-select.ts +1 -1
- package/src/hooks/use-resource-permissions.ts +1 -1
- package/src/queried-data/reducer.js +3 -1
- package/src/queried-data/selectors.js +32 -26
- package/src/queried-data/test/reducer.js +7 -0
- package/src/queried-data/test/selectors.js +30 -21
- package/src/resolvers.js +6 -6
- package/src/selectors.ts +8 -2
- package/src/utils/crdt-blocks.ts +336 -62
- package/src/utils/crdt-selection.ts +0 -1
- package/src/utils/crdt.ts +15 -1
- package/src/utils/test/crdt-blocks.ts +1328 -6
- package/src/utils/test/crdt.ts +39 -1
- package/build/hooks/memoize.cjs +0 -38
- package/build/hooks/memoize.cjs.map +0 -7
- package/build-module/hooks/memoize.mjs +0 -7
- package/build-module/hooks/memoize.mjs.map +0 -7
- package/build-types/hooks/memoize.d.ts +0 -3
- package/build-types/hooks/memoize.d.ts.map +0 -1
- package/src/hooks/memoize.js +0 -7
package/src/utils/crdt-blocks.ts
CHANGED
|
@@ -7,7 +7,6 @@ import fastDeepEqual from 'fast-deep-equal/es6/index.js';
|
|
|
7
7
|
/**
|
|
8
8
|
* WordPress dependencies
|
|
9
9
|
*/
|
|
10
|
-
// @ts-expect-error No exported types.
|
|
11
10
|
import { getBlockTypes } from '@wordpress/blocks';
|
|
12
11
|
import { RichTextData } from '@wordpress/rich-text';
|
|
13
12
|
import { Y } from '@wordpress/sync';
|
|
@@ -23,14 +22,14 @@ interface BlockAttributes {
|
|
|
23
22
|
[ key: string ]: unknown;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
interface
|
|
25
|
+
interface BlockAttributeSchema {
|
|
27
26
|
role?: string;
|
|
28
27
|
type?: string;
|
|
29
|
-
query?: Record< string,
|
|
28
|
+
query?: Record< string, BlockAttributeSchema >;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
interface BlockType {
|
|
33
|
-
attributes?: Record< string,
|
|
32
|
+
attributes?: Record< string, BlockAttributeSchema >;
|
|
34
33
|
name: string;
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -134,7 +133,7 @@ function makeBlocksSerializable( blocks: Block[] ): Block[] {
|
|
|
134
133
|
* @return The value with rich-text strings replaced by RichTextData.
|
|
135
134
|
*/
|
|
136
135
|
function deserializeAttributeValue(
|
|
137
|
-
schema:
|
|
136
|
+
schema: BlockAttributeSchema | undefined,
|
|
138
137
|
value: unknown
|
|
139
138
|
): unknown {
|
|
140
139
|
if ( schema?.type === 'rich-text' && typeof value === 'string' ) {
|
|
@@ -184,7 +183,7 @@ export function deserializeBlockAttributes( blocks: Block[] ): Block[] {
|
|
|
184
183
|
const newAttributes = { ...attributes };
|
|
185
184
|
|
|
186
185
|
for ( const [ key, value ] of Object.entries( attributes ) ) {
|
|
187
|
-
const schema =
|
|
186
|
+
const schema = getBlockAttributeSchema( name, key );
|
|
188
187
|
|
|
189
188
|
if ( schema ) {
|
|
190
189
|
newAttributes[ key ] = deserializeAttributeValue(
|
|
@@ -255,14 +254,89 @@ function createNewYAttributeValue(
|
|
|
255
254
|
blockName: string,
|
|
256
255
|
attributeName: string,
|
|
257
256
|
attributeValue: unknown
|
|
258
|
-
): Y.Text | unknown {
|
|
259
|
-
const
|
|
257
|
+
): Y.Text | Y.Array< unknown > | Y.Map< unknown > | unknown {
|
|
258
|
+
const schema = getBlockAttributeSchema( blockName, attributeName );
|
|
259
|
+
return createYValueFromSchema( schema, attributeValue );
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Recursively create the appropriate Y.js type for a value based on its
|
|
264
|
+
* block-attribute schema.
|
|
265
|
+
*
|
|
266
|
+
* - `rich-text` -> Y.Text
|
|
267
|
+
* - `array` with query -> Y.Array of Y.Maps
|
|
268
|
+
* - `object` with query -> Y.Map
|
|
269
|
+
* - anything else -> plain value (unchanged)
|
|
270
|
+
*
|
|
271
|
+
* @param schema The attribute type definition.
|
|
272
|
+
* @param value The plain JS value to convert.
|
|
273
|
+
* @return A Y.js type or the original value.
|
|
274
|
+
*/
|
|
275
|
+
function createYValueFromSchema(
|
|
276
|
+
schema: BlockAttributeSchema | undefined,
|
|
277
|
+
value: unknown
|
|
278
|
+
): Y.Text | Y.Array< unknown > | Y.Map< unknown > | unknown {
|
|
279
|
+
if ( ! schema ) {
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if ( schema.type === 'rich-text' ) {
|
|
284
|
+
return new Y.Text( value?.toString() ?? '' );
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if ( schema.type === 'array' && schema.query && Array.isArray( value ) ) {
|
|
288
|
+
const query = schema.query;
|
|
289
|
+
const yArray = new Y.Array< Y.Map< unknown > >();
|
|
290
|
+
|
|
291
|
+
yArray.insert(
|
|
292
|
+
0,
|
|
293
|
+
value.map( ( item ) => createYMapFromQuery( query, item ) )
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return yArray;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if ( schema.type === 'object' && schema.query && isRecord( value ) ) {
|
|
300
|
+
return createYMapFromQuery( schema.query, value );
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
260
305
|
|
|
261
|
-
|
|
262
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Type guard that narrows `unknown` to `Record< string, unknown >`.
|
|
308
|
+
*
|
|
309
|
+
* @param value Value to check.
|
|
310
|
+
* @return True if `value` is a non-null, non-array object.
|
|
311
|
+
*/
|
|
312
|
+
function isRecord( value: unknown ): value is Record< string, unknown > {
|
|
313
|
+
return !! value && typeof value === 'object' && ! Array.isArray( value );
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create a Y.Map from a plain object, using a query schema to decide which
|
|
318
|
+
* properties should become nested Y.js types (Y.Text, Y.Array, Y.Map).
|
|
319
|
+
*
|
|
320
|
+
* @param query The query schema defining the properties.
|
|
321
|
+
* @param obj The plain object to convert.
|
|
322
|
+
* @return A Y.Map with typed values.
|
|
323
|
+
*/
|
|
324
|
+
function createYMapFromQuery(
|
|
325
|
+
query: Record< string, BlockAttributeSchema >,
|
|
326
|
+
obj: unknown
|
|
327
|
+
): Y.Map< unknown > {
|
|
328
|
+
if ( ! isRecord( obj ) ) {
|
|
329
|
+
return new Y.Map();
|
|
263
330
|
}
|
|
264
331
|
|
|
265
|
-
|
|
332
|
+
const entries: [ string, unknown ][] = Object.entries( obj ).map(
|
|
333
|
+
( [ key, val ] ): [ string, unknown ] => {
|
|
334
|
+
const subSchema = query[ key ];
|
|
335
|
+
return [ key, createYValueFromSchema( subSchema, val ) ];
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
return new Y.Map( entries );
|
|
266
340
|
}
|
|
267
341
|
|
|
268
342
|
function createNewYBlock( block: Block ): YBlock {
|
|
@@ -383,6 +457,7 @@ export function mergeCrdtBlocks(
|
|
|
383
457
|
for ( let i = 0; i < numOfUpdatesNeeded; i++, left++ ) {
|
|
384
458
|
const block = blocksToSync[ left ];
|
|
385
459
|
const yblock = yblocks.get( left );
|
|
460
|
+
|
|
386
461
|
Object.entries( block ).forEach( ( [ key, value ] ) => {
|
|
387
462
|
switch ( key ) {
|
|
388
463
|
case 'attributes': {
|
|
@@ -408,8 +483,16 @@ export function mergeCrdtBlocks(
|
|
|
408
483
|
currentAttribute
|
|
409
484
|
);
|
|
410
485
|
|
|
486
|
+
// Y types (Y.Text, Y.Array, Y.Map) cannot be
|
|
487
|
+
// compared with fastDeepEqual against plain values.
|
|
488
|
+
// Delegate to mergeYValue which handles no-op
|
|
489
|
+
// detection at the edges.
|
|
490
|
+
const isYType =
|
|
491
|
+
currentAttribute instanceof Y.AbstractType;
|
|
492
|
+
|
|
411
493
|
const isAttributeChanged =
|
|
412
494
|
! isExpectedType ||
|
|
495
|
+
isYType ||
|
|
413
496
|
! fastDeepEqual(
|
|
414
497
|
currentAttribute,
|
|
415
498
|
attributeValue
|
|
@@ -499,11 +582,218 @@ export function mergeCrdtBlocks(
|
|
|
499
582
|
}
|
|
500
583
|
|
|
501
584
|
/**
|
|
502
|
-
*
|
|
585
|
+
* Compare a plain array element against a Y.Map element for equality.
|
|
586
|
+
* Used by the left-right sweep diff in mergeYArray.
|
|
587
|
+
*
|
|
588
|
+
* @param newElement The plain object from the incoming array.
|
|
589
|
+
* @param yElement The Y.Map element from the existing Y.Array.
|
|
590
|
+
* @return True if the elements are deeply equal.
|
|
591
|
+
*/
|
|
592
|
+
function areArrayElementsEqual(
|
|
593
|
+
newElement: unknown,
|
|
594
|
+
yElement: unknown
|
|
595
|
+
): boolean {
|
|
596
|
+
if ( yElement instanceof Y.Map && isRecord( newElement ) ) {
|
|
597
|
+
return fastDeepEqual( newElement, yElement.toJSON() );
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return fastDeepEqual( newElement, yElement );
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Merge an incoming plain array into an existing Y.Array in-place.
|
|
605
|
+
*
|
|
606
|
+
* Uses the same left-right sweep diff approach as mergeCrdtBlocks:
|
|
607
|
+
* equal elements are skipped from both ends, then the middle section
|
|
608
|
+
* is updated, deleted, or inserted as needed. This preserves existing
|
|
609
|
+
* Y.Map/Y.Text objects for unchanged elements, so concurrent edits
|
|
610
|
+
* to those elements are not lost.
|
|
611
|
+
*
|
|
612
|
+
* @param yArray The existing Y.Array to update.
|
|
613
|
+
* @param newValue The new plain array to merge into the Y.Array.
|
|
614
|
+
* @param schema The attribute schema (must have `query`).
|
|
615
|
+
* @param cursorPosition The local cursor position for rich-text delta merges.
|
|
616
|
+
*/
|
|
617
|
+
function mergeYArray(
|
|
618
|
+
yArray: Y.Array< unknown >,
|
|
619
|
+
newValue: unknown[],
|
|
620
|
+
schema: BlockAttributeSchema,
|
|
621
|
+
cursorPosition: number | null
|
|
622
|
+
): void {
|
|
623
|
+
if ( ! schema.query ) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const query = schema.query;
|
|
628
|
+
const numOfCommonEntries = Math.min( newValue.length, yArray.length );
|
|
629
|
+
|
|
630
|
+
let left = 0;
|
|
631
|
+
let right = 0;
|
|
632
|
+
|
|
633
|
+
// Skip equal elements from left.
|
|
634
|
+
for (
|
|
635
|
+
;
|
|
636
|
+
left < numOfCommonEntries &&
|
|
637
|
+
areArrayElementsEqual( newValue[ left ], yArray.get( left ) );
|
|
638
|
+
left++
|
|
639
|
+
) {
|
|
640
|
+
/* nop */
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Skip equal elements from right.
|
|
644
|
+
for (
|
|
645
|
+
;
|
|
646
|
+
right < numOfCommonEntries - left &&
|
|
647
|
+
areArrayElementsEqual(
|
|
648
|
+
newValue[ newValue.length - right - 1 ],
|
|
649
|
+
yArray.get( yArray.length - right - 1 )
|
|
650
|
+
);
|
|
651
|
+
right++
|
|
652
|
+
) {
|
|
653
|
+
/* nop */
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Updates: merge changed elements in-place.
|
|
657
|
+
const numOfUpdatesNeeded = numOfCommonEntries - left - right;
|
|
658
|
+
|
|
659
|
+
for ( let i = 0; i < numOfUpdatesNeeded; i++ ) {
|
|
660
|
+
const currentElement = yArray.get( left + i );
|
|
661
|
+
const newElement = newValue[ left + i ];
|
|
662
|
+
|
|
663
|
+
if ( currentElement instanceof Y.Map && isRecord( newElement ) ) {
|
|
664
|
+
mergeYMapValues(
|
|
665
|
+
currentElement,
|
|
666
|
+
newElement,
|
|
667
|
+
query,
|
|
668
|
+
cursorPosition
|
|
669
|
+
);
|
|
670
|
+
} else {
|
|
671
|
+
// Element is the wrong type (e.g. partial migration) or the
|
|
672
|
+
// incoming value is not an object. Rebuild the entire array.
|
|
673
|
+
yArray.delete( 0, yArray.length );
|
|
674
|
+
yArray.insert(
|
|
675
|
+
0,
|
|
676
|
+
newValue.map( ( item ) => createYMapFromQuery( query, item ) )
|
|
677
|
+
);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Deletes.
|
|
683
|
+
const numOfDeletionsNeeded = Math.max( 0, yArray.length - newValue.length );
|
|
684
|
+
|
|
685
|
+
if ( numOfDeletionsNeeded > 0 ) {
|
|
686
|
+
yArray.delete( left + numOfUpdatesNeeded, numOfDeletionsNeeded );
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Inserts.
|
|
690
|
+
const numOfInsertionsNeeded = Math.max(
|
|
691
|
+
0,
|
|
692
|
+
newValue.length - yArray.length
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
if ( numOfInsertionsNeeded > 0 ) {
|
|
696
|
+
const insertAt = left + numOfUpdatesNeeded;
|
|
697
|
+
const itemsToInsert: Y.Map< unknown >[] = new Array(
|
|
698
|
+
numOfInsertionsNeeded
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
for ( let i = 0; i < numOfInsertionsNeeded; i++ ) {
|
|
702
|
+
itemsToInsert[ i ] = createYMapFromQuery(
|
|
703
|
+
query,
|
|
704
|
+
newValue[ insertAt + i ]
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
yArray.insert( insertAt, itemsToInsert );
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Merge a single value into a Y.Map entry, using the attribute schema to
|
|
714
|
+
* decide how to merge.
|
|
503
715
|
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
716
|
+
* If the current value is already a matching Y.js type (Y.Text, Y.Array,
|
|
717
|
+
* Y.Map), the update is merged in-place so concurrent edits are preserved.
|
|
718
|
+
* Otherwise the value is replaced wholesale.
|
|
719
|
+
*
|
|
720
|
+
* @param schema The attribute type definition for this value.
|
|
721
|
+
* @param newVal The new value to merge into the Y.Map entry.
|
|
722
|
+
* @param yMap The Y.Map that owns this entry.
|
|
723
|
+
* @param key The key of this entry in the Y.Map.
|
|
724
|
+
* @param cursorPosition The local cursor position for rich-text delta merges.
|
|
725
|
+
*/
|
|
726
|
+
function mergeYValue(
|
|
727
|
+
schema: BlockAttributeSchema | undefined,
|
|
728
|
+
newVal: unknown,
|
|
729
|
+
yMap: Y.Map< unknown >,
|
|
730
|
+
key: string,
|
|
731
|
+
cursorPosition: number | null
|
|
732
|
+
): void {
|
|
733
|
+
const currentVal = yMap.get( key );
|
|
734
|
+
if (
|
|
735
|
+
schema?.type === 'rich-text' &&
|
|
736
|
+
typeof newVal === 'string' &&
|
|
737
|
+
currentVal instanceof Y.Text
|
|
738
|
+
) {
|
|
739
|
+
mergeRichTextUpdate( currentVal, newVal, cursorPosition );
|
|
740
|
+
} else if (
|
|
741
|
+
schema?.type === 'array' &&
|
|
742
|
+
schema.query &&
|
|
743
|
+
Array.isArray( newVal ) &&
|
|
744
|
+
currentVal instanceof Y.Array
|
|
745
|
+
) {
|
|
746
|
+
mergeYArray( currentVal, newVal, schema, cursorPosition );
|
|
747
|
+
} else if (
|
|
748
|
+
schema?.type === 'object' &&
|
|
749
|
+
schema.query &&
|
|
750
|
+
isRecord( newVal ) &&
|
|
751
|
+
currentVal instanceof Y.Map
|
|
752
|
+
) {
|
|
753
|
+
mergeYMapValues( currentVal, newVal, schema.query, cursorPosition );
|
|
754
|
+
} else {
|
|
755
|
+
const newYValue = createYValueFromSchema( schema, newVal );
|
|
756
|
+
|
|
757
|
+
// If createYValueFromSchema wrapped the value into a Y type, the
|
|
758
|
+
// current value is the wrong type and needs upgrading. Otherwise,
|
|
759
|
+
// only replace if the raw value actually changed.
|
|
760
|
+
if ( newYValue !== newVal || ! fastDeepEqual( currentVal, newVal ) ) {
|
|
761
|
+
yMap.set( key, newYValue );
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Merge an incoming plain object into an existing Y.Map in-place, using
|
|
768
|
+
* the query schema to decide how each property should be merged.
|
|
769
|
+
*
|
|
770
|
+
* Properties present in the Y.Map but absent from `newObj` are deleted.
|
|
771
|
+
*
|
|
772
|
+
* @param yMap The existing Y.Map to update.
|
|
773
|
+
* @param newObj The new plain object to merge into the Y.Map.
|
|
774
|
+
* @param query The query schema defining property types.
|
|
775
|
+
* @param cursorPosition The local cursor position for rich-text delta merges.
|
|
776
|
+
*/
|
|
777
|
+
function mergeYMapValues(
|
|
778
|
+
yMap: Y.Map< unknown >,
|
|
779
|
+
newObj: Record< string, unknown >,
|
|
780
|
+
query: Record< string, BlockAttributeSchema >,
|
|
781
|
+
cursorPosition: number | null
|
|
782
|
+
): void {
|
|
783
|
+
for ( const [ key, newVal ] of Object.entries( newObj ) ) {
|
|
784
|
+
mergeYValue( query[ key ], newVal, yMap, key, cursorPosition );
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Delete properties absent from the incoming object.
|
|
788
|
+
for ( const key of yMap.keys() ) {
|
|
789
|
+
if ( ! Object.hasOwn( newObj, key ) ) {
|
|
790
|
+
yMap.delete( key );
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Update a single attribute on a Yjs block attributes map (currentAttributes).
|
|
507
797
|
*
|
|
508
798
|
* @param blockName The block type name, e.g. 'core/paragraph'.
|
|
509
799
|
* @param attributeName The name of the attribute to update, e.g. 'content'.
|
|
@@ -518,28 +808,22 @@ function updateYBlockAttribute(
|
|
|
518
808
|
currentAttributes: YBlockAttributes,
|
|
519
809
|
cursorPosition: number | null
|
|
520
810
|
): void {
|
|
521
|
-
const
|
|
522
|
-
const currentAttribute = currentAttributes.get( attributeName );
|
|
811
|
+
const schema = getBlockAttributeSchema( blockName, attributeName );
|
|
523
812
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
currentAttributes
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
// Update the value with a delta in place.
|
|
532
|
-
mergeRichTextUpdate( currentAttribute, attributeValue, cursorPosition );
|
|
533
|
-
} else {
|
|
534
|
-
currentAttributes.set(
|
|
535
|
-
attributeName,
|
|
536
|
-
createNewYAttributeValue( blockName, attributeName, attributeValue )
|
|
537
|
-
);
|
|
538
|
-
}
|
|
813
|
+
mergeYValue(
|
|
814
|
+
schema,
|
|
815
|
+
attributeValue,
|
|
816
|
+
currentAttributes,
|
|
817
|
+
attributeName,
|
|
818
|
+
cursorPosition
|
|
819
|
+
);
|
|
539
820
|
}
|
|
540
821
|
|
|
541
822
|
// Cached block attribute types, populated once from getBlockTypes().
|
|
542
|
-
let
|
|
823
|
+
let cachedBlockAttributeSchemas: Map<
|
|
824
|
+
string,
|
|
825
|
+
Map< string, BlockAttributeSchema >
|
|
826
|
+
>;
|
|
543
827
|
|
|
544
828
|
/**
|
|
545
829
|
* Get the attribute type definition for a block attribute.
|
|
@@ -548,18 +832,18 @@ let cachedBlockAttributeTypes: Map< string, Map< string, BlockAttributeType > >;
|
|
|
548
832
|
* @param attributeName The name of the attribute, e.g. 'content'.
|
|
549
833
|
* @return The type definition of the attribute.
|
|
550
834
|
*/
|
|
551
|
-
function
|
|
835
|
+
function getBlockAttributeSchema(
|
|
552
836
|
blockName: string,
|
|
553
837
|
attributeName: string
|
|
554
|
-
):
|
|
555
|
-
if ( !
|
|
838
|
+
): BlockAttributeSchema | undefined {
|
|
839
|
+
if ( ! cachedBlockAttributeSchemas ) {
|
|
556
840
|
// Parse the attributes for all blocks once.
|
|
557
|
-
|
|
841
|
+
cachedBlockAttributeSchemas = new Map();
|
|
558
842
|
|
|
559
843
|
for ( const blockType of getBlockTypes() as BlockType[] ) {
|
|
560
|
-
|
|
844
|
+
cachedBlockAttributeSchemas.set(
|
|
561
845
|
blockType.name,
|
|
562
|
-
new Map< string,
|
|
846
|
+
new Map< string, BlockAttributeSchema >(
|
|
563
847
|
Object.entries( blockType.attributes ?? {} ).map(
|
|
564
848
|
( [ name, definition ] ) => {
|
|
565
849
|
const { role, type, query } = definition;
|
|
@@ -571,7 +855,7 @@ function getBlockAttributeType(
|
|
|
571
855
|
}
|
|
572
856
|
}
|
|
573
857
|
|
|
574
|
-
return
|
|
858
|
+
return cachedBlockAttributeSchemas.get( blockName )?.get( attributeName );
|
|
575
859
|
}
|
|
576
860
|
|
|
577
861
|
/**
|
|
@@ -587,20 +871,24 @@ function isExpectedAttributeType(
|
|
|
587
871
|
attributeName: string,
|
|
588
872
|
attributeValue: unknown
|
|
589
873
|
): boolean {
|
|
590
|
-
const
|
|
591
|
-
blockName,
|
|
592
|
-
attributeName
|
|
593
|
-
)?.type;
|
|
874
|
+
const schema = getBlockAttributeSchema( blockName, attributeName );
|
|
594
875
|
|
|
595
|
-
if (
|
|
876
|
+
if ( schema?.type === 'rich-text' ) {
|
|
596
877
|
return attributeValue instanceof Y.Text;
|
|
597
878
|
}
|
|
598
879
|
|
|
599
|
-
if (
|
|
880
|
+
if ( schema?.type === 'string' ) {
|
|
600
881
|
return typeof attributeValue === 'string';
|
|
601
882
|
}
|
|
602
883
|
|
|
603
|
-
|
|
884
|
+
if ( schema?.type === 'array' && schema.query ) {
|
|
885
|
+
return attributeValue instanceof Y.Array;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if ( schema?.type === 'object' && schema.query ) {
|
|
889
|
+
return attributeValue instanceof Y.Map;
|
|
890
|
+
}
|
|
891
|
+
|
|
604
892
|
return true;
|
|
605
893
|
}
|
|
606
894
|
|
|
@@ -613,22 +901,8 @@ function isExpectedAttributeType(
|
|
|
613
901
|
* @return True if the attribute is local, false otherwise.
|
|
614
902
|
*/
|
|
615
903
|
function isLocalAttribute( blockName: string, attributeName: string ): boolean {
|
|
616
|
-
return 'local' === getBlockAttributeType( blockName, attributeName )?.role;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Given a block name and attribute key, return true if the attribute is rich-text typed.
|
|
621
|
-
*
|
|
622
|
-
* @param blockName The name of the block, e.g. 'core/paragraph'.
|
|
623
|
-
* @param attributeName The name of the attribute to check, e.g. 'content'.
|
|
624
|
-
* @return True if the attribute is rich-text typed, false otherwise.
|
|
625
|
-
*/
|
|
626
|
-
function isRichTextAttribute(
|
|
627
|
-
blockName: string,
|
|
628
|
-
attributeName: string
|
|
629
|
-
): boolean {
|
|
630
904
|
return (
|
|
631
|
-
'
|
|
905
|
+
'local' === getBlockAttributeSchema( blockName, attributeName )?.role
|
|
632
906
|
);
|
|
633
907
|
}
|
|
634
908
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import { dispatch, select } from '@wordpress/data';
|
|
5
5
|
// @ts-expect-error No exported types.
|
|
6
6
|
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
7
|
-
// @ts-expect-error No exported types.
|
|
8
7
|
import { isUnmodifiedBlock } from '@wordpress/blocks';
|
|
9
8
|
import { type CRDTDoc, Y } from '@wordpress/sync';
|
|
10
9
|
|
package/src/utils/crdt.ts
CHANGED
|
@@ -6,11 +6,12 @@ import fastDeepEqual from 'fast-deep-equal/es6/index.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
8
8
|
*/
|
|
9
|
-
// @ts-expect-error No exported types.
|
|
10
9
|
import { __unstableSerializeAndClean } from '@wordpress/blocks';
|
|
11
10
|
import {
|
|
12
11
|
type CRDTDoc,
|
|
13
12
|
type ObjectData,
|
|
13
|
+
type ObjectID,
|
|
14
|
+
type ObjectType,
|
|
14
15
|
type SyncConfig,
|
|
15
16
|
Y,
|
|
16
17
|
} from '@wordpress/sync';
|
|
@@ -428,6 +429,19 @@ export const defaultSyncConfig: SyncConfig = {
|
|
|
428
429
|
getChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc,
|
|
429
430
|
};
|
|
430
431
|
|
|
432
|
+
/**
|
|
433
|
+
* This default collection sync config can be used to sync entity collections
|
|
434
|
+
* (e.g., block comments) where we are not interested in merging changes at the
|
|
435
|
+
* individual record level, but instead want to replace the entire collection
|
|
436
|
+
* when changes are detected.
|
|
437
|
+
*/
|
|
438
|
+
export const defaultCollectionSyncConfig: SyncConfig = {
|
|
439
|
+
applyChangesToCRDTDoc: () => {},
|
|
440
|
+
getChangesFromCRDTDoc: () => ( {} ),
|
|
441
|
+
shouldSync: ( _: ObjectType, objectId: ObjectID | null ) =>
|
|
442
|
+
null === objectId,
|
|
443
|
+
};
|
|
444
|
+
|
|
431
445
|
/**
|
|
432
446
|
* Extract the raw string value from a property that may be a string or an object
|
|
433
447
|
* with a `raw` property (`RenderedText`).
|