@wordpress/core-data 7.39.1-next.v.202602111440.0 → 7.40.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 +2 -0
- package/README.md +41 -0
- package/build/actions.cjs +52 -0
- package/build/actions.cjs.map +2 -2
- package/build/awareness/base-awareness.cjs +1 -8
- package/build/awareness/base-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/awareness/utils.cjs +8 -51
- package/build/awareness/utils.cjs.map +2 -2
- package/build/entities.cjs +7 -1
- package/build/entities.cjs.map +2 -2
- package/build/hooks/use-entity-block-editor.cjs +13 -19
- package/build/hooks/use-entity-block-editor.cjs.map +2 -2
- package/build/index.cjs +6 -1
- package/build/index.cjs.map +2 -2
- package/build/private-actions.cjs +8 -0
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +2 -1
- package/build/private-apis.cjs.map +2 -2
- package/build/private-selectors.cjs +5 -0
- package/build/private-selectors.cjs.map +2 -2
- package/build/reducer.cjs +31 -1
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +25 -0
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +15 -0
- package/build/selectors.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +5 -3
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt.cjs +23 -19
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/actions.mjs +50 -0
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/base-awareness.mjs +1 -8
- package/build-module/awareness/base-awareness.mjs.map +2 -2
- package/build-module/awareness/utils.mjs +8 -51
- package/build-module/awareness/utils.mjs.map +2 -2
- package/build-module/entities.mjs +7 -1
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/use-entity-block-editor.mjs +13 -19
- package/build-module/hooks/use-entity-block-editor.mjs.map +2 -2
- package/build-module/index.mjs +3 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-actions.mjs +7 -0
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +6 -2
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/private-selectors.mjs +8 -1
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +29 -1
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +24 -0
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +14 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +3 -2
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +25 -20
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/actions.d.ts +12 -0
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/base-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +0 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/awareness/utils.d.ts +2 -3
- package/build-types/awareness/utils.d.ts.map +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/use-entity-block-editor.d.ts.map +1 -1
- package/build-types/index.d.ts +5 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-actions.d.ts +8 -0
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +8 -1
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts +23 -0
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +3 -0
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +17 -0
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts +9 -0
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +6 -4
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/actions.js +78 -0
- package/src/awareness/base-awareness.ts +1 -14
- package/src/awareness/test/awareness-state.ts +5 -1
- package/src/awareness/test/base-awareness.ts +2 -6
- package/src/awareness/test/post-editor-awareness.ts +0 -1
- package/src/awareness/test/typed-awareness.ts +5 -1
- package/src/awareness/test/utils.ts +12 -88
- package/src/awareness/types.ts +0 -1
- package/src/awareness/utils.ts +8 -82
- package/src/entities.js +7 -1
- package/src/hooks/test/use-post-editor-awareness-state.ts +0 -3
- package/src/hooks/use-entity-block-editor.js +15 -21
- package/src/index.js +7 -0
- package/src/private-actions.js +14 -0
- package/src/private-apis.js +5 -1
- package/src/private-selectors.ts +16 -1
- package/src/reducer.js +45 -0
- package/src/resolvers.js +29 -0
- package/src/selectors.ts +41 -0
- package/src/test/actions.js +79 -0
- package/src/test/entity-provider.js +74 -0
- package/src/test/resolvers.js +2 -0
- package/src/test/store.js +30 -0
- package/src/utils/crdt-blocks.ts +2 -2
- package/src/utils/crdt.ts +44 -29
- package/src/utils/test/crdt.ts +212 -7
package/src/private-selectors.ts
CHANGED
|
@@ -6,7 +6,12 @@ import { createSelector, createRegistrySelector } from '@wordpress/data';
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getDefaultTemplateId,
|
|
11
|
+
getEntityRecord,
|
|
12
|
+
type State,
|
|
13
|
+
type Icon,
|
|
14
|
+
} from './selectors';
|
|
10
15
|
import { STORE_NAME } from './name';
|
|
11
16
|
import { unlock } from './lock-unlock';
|
|
12
17
|
import { getSyncManager } from './sync';
|
|
@@ -308,3 +313,13 @@ export function getEditorSettings(
|
|
|
308
313
|
export function getEditorAssets( state: State ): Record< string, any > | null {
|
|
309
314
|
return state.editorAssets;
|
|
310
315
|
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Returns the list of available icons.
|
|
319
|
+
*
|
|
320
|
+
* @param state Data state.
|
|
321
|
+
* @return The list of icons or empty array if not loaded.
|
|
322
|
+
*/
|
|
323
|
+
export function getIcons( state: State ): Icon[] {
|
|
324
|
+
return state.icons ?? [];
|
|
325
|
+
}
|
package/src/reducer.js
CHANGED
|
@@ -660,6 +660,49 @@ export function editorAssets( state = null, action ) {
|
|
|
660
660
|
return state;
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
+
/**
|
|
664
|
+
* Reducer managing icons.
|
|
665
|
+
*
|
|
666
|
+
* @param {Array} state Current state.
|
|
667
|
+
* @param {Object} action Action object.
|
|
668
|
+
*
|
|
669
|
+
* @return {Array} Updated state.
|
|
670
|
+
*/
|
|
671
|
+
export function icons( state = [], action ) {
|
|
672
|
+
switch ( action.type ) {
|
|
673
|
+
case 'RECEIVE_ICONS':
|
|
674
|
+
return action.icons;
|
|
675
|
+
}
|
|
676
|
+
return state;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Reducer managing sync connection states for entities.
|
|
681
|
+
* Keyed by "kind/name:id" (e.g., "postType/post:123").
|
|
682
|
+
*
|
|
683
|
+
* @param {Object} state Current state.
|
|
684
|
+
* @param {Object} action Dispatched action.
|
|
685
|
+
*
|
|
686
|
+
* @return {Object} Updated state.
|
|
687
|
+
*/
|
|
688
|
+
export function syncConnectionStatuses( state = {}, action ) {
|
|
689
|
+
switch ( action.type ) {
|
|
690
|
+
case 'SET_SYNC_CONNECTION_STATUS': {
|
|
691
|
+
const key = `${ action.kind }/${ action.name }:${ action.key }`;
|
|
692
|
+
return {
|
|
693
|
+
...state,
|
|
694
|
+
[ key ]: action.status,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
case 'CLEAR_SYNC_CONNECTION_STATUS': {
|
|
698
|
+
const key = `${ action.kind }/${ action.name }:${ action.key }`;
|
|
699
|
+
const { [ key ]: _, ...rest } = state;
|
|
700
|
+
return rest;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return state;
|
|
704
|
+
}
|
|
705
|
+
|
|
663
706
|
export default combineReducers( {
|
|
664
707
|
users,
|
|
665
708
|
currentTheme,
|
|
@@ -682,4 +725,6 @@ export default combineReducers( {
|
|
|
682
725
|
registeredPostMeta,
|
|
683
726
|
editorSettings,
|
|
684
727
|
editorAssets,
|
|
728
|
+
icons,
|
|
729
|
+
syncConnectionStatuses,
|
|
685
730
|
} );
|
package/src/resolvers.js
CHANGED
|
@@ -217,6 +217,15 @@ export const getEntityRecord =
|
|
|
217
217
|
name,
|
|
218
218
|
key
|
|
219
219
|
),
|
|
220
|
+
// Handle sync connection status changes.
|
|
221
|
+
onStatusChange: ( status ) => {
|
|
222
|
+
dispatch.setSyncConnectionStatus(
|
|
223
|
+
kind,
|
|
224
|
+
name,
|
|
225
|
+
key,
|
|
226
|
+
status
|
|
227
|
+
);
|
|
228
|
+
},
|
|
220
229
|
// Refetch the current entity record from the database.
|
|
221
230
|
refetchRecord: async () => {
|
|
222
231
|
dispatch.receiveEntityRecords(
|
|
@@ -469,6 +478,14 @@ export const getEntityRecords =
|
|
|
469
478
|
entityConfig.syncConfig,
|
|
470
479
|
objectType,
|
|
471
480
|
{
|
|
481
|
+
onStatusChange: ( status ) => {
|
|
482
|
+
dispatch.setSyncConnectionStatus(
|
|
483
|
+
kind,
|
|
484
|
+
name,
|
|
485
|
+
null,
|
|
486
|
+
status
|
|
487
|
+
);
|
|
488
|
+
},
|
|
472
489
|
refetchRecords: async () => {
|
|
473
490
|
dispatch.receiveEntityRecords(
|
|
474
491
|
kind,
|
|
@@ -1254,3 +1271,15 @@ export const getEditorAssets =
|
|
|
1254
1271
|
} );
|
|
1255
1272
|
dispatch.receiveEditorAssets( assets );
|
|
1256
1273
|
};
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Requests icons from the REST API.
|
|
1277
|
+
*/
|
|
1278
|
+
export const getIcons =
|
|
1279
|
+
() =>
|
|
1280
|
+
async ( { dispatch } ) => {
|
|
1281
|
+
const icons = await apiFetch( {
|
|
1282
|
+
path: '/wp/v2/icons',
|
|
1283
|
+
} );
|
|
1284
|
+
dispatch.receiveIcons( icons );
|
|
1285
|
+
};
|
package/src/selectors.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createSelector, createRegistrySelector } from '@wordpress/data';
|
|
|
5
5
|
import { addQueryArgs } from '@wordpress/url';
|
|
6
6
|
import type { UndoManager } from '@wordpress/undo-manager';
|
|
7
7
|
import deprecated from '@wordpress/deprecated';
|
|
8
|
+
import type { ConnectionStatus } from '@wordpress/sync';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Internal dependencies
|
|
@@ -52,10 +53,18 @@ export interface State {
|
|
|
52
53
|
registeredPostMeta: Record< string, Object >;
|
|
53
54
|
editorSettings: Record< string, any > | null;
|
|
54
55
|
editorAssets: Record< string, any > | null;
|
|
56
|
+
icons: Icon[];
|
|
57
|
+
syncConnectionStatuses?: Record< string, ConnectionStatus >;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
type EntityRecordKey = string | number;
|
|
58
61
|
|
|
62
|
+
export interface Icon {
|
|
63
|
+
name: string;
|
|
64
|
+
content: string;
|
|
65
|
+
label: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
interface EntitiesState {
|
|
60
69
|
config: EntityConfig[];
|
|
61
70
|
records: Record< string, Record< string, EntityState< ET.EntityRecord > > >;
|
|
@@ -1595,3 +1604,35 @@ export const getRevision = createSelector(
|
|
|
1595
1604
|
];
|
|
1596
1605
|
}
|
|
1597
1606
|
);
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Returns the current sync connection status across all entities. Prioritizes
|
|
1610
|
+
* disconnected states, then connecting, then connected.
|
|
1611
|
+
*
|
|
1612
|
+
* @param state Data state.
|
|
1613
|
+
*
|
|
1614
|
+
* @return The current sync connection state, prioritized by importance.
|
|
1615
|
+
*/
|
|
1616
|
+
export function getSyncConnectionStatus(
|
|
1617
|
+
state: State
|
|
1618
|
+
): ConnectionStatus | undefined {
|
|
1619
|
+
if ( ! state.syncConnectionStatuses ) {
|
|
1620
|
+
return undefined;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const PRIORITIZED_STATUSES = [ 'disconnected', 'connecting', 'connected' ];
|
|
1624
|
+
|
|
1625
|
+
let coalesced: ConnectionStatus | undefined;
|
|
1626
|
+
|
|
1627
|
+
for ( const status of Object.values( state.syncConnectionStatuses ) ) {
|
|
1628
|
+
if (
|
|
1629
|
+
! coalesced ||
|
|
1630
|
+
PRIORITIZED_STATUSES.indexOf( status.status ) <
|
|
1631
|
+
PRIORITIZED_STATUSES.indexOf( coalesced.status )
|
|
1632
|
+
) {
|
|
1633
|
+
coalesced = status;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
return coalesced;
|
|
1638
|
+
}
|
package/src/test/actions.js
CHANGED
|
@@ -10,6 +10,7 @@ jest.mock( '@wordpress/api-fetch' );
|
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
12
12
|
editEntityRecord,
|
|
13
|
+
clearEntityRecordEdits,
|
|
13
14
|
saveEntityRecord,
|
|
14
15
|
saveEditedEntityRecord,
|
|
15
16
|
deleteEntityRecord,
|
|
@@ -454,6 +455,84 @@ describe( 'editEntityRecord', () => {
|
|
|
454
455
|
} );
|
|
455
456
|
} );
|
|
456
457
|
|
|
458
|
+
describe( 'clearEntityRecordEdits', () => {
|
|
459
|
+
it( 'throws when the entity does not have a loaded config.', async () => {
|
|
460
|
+
const select = {
|
|
461
|
+
getEntityConfig: jest.fn(),
|
|
462
|
+
};
|
|
463
|
+
const fulfillment = async () =>
|
|
464
|
+
clearEntityRecordEdits(
|
|
465
|
+
'someKind',
|
|
466
|
+
'someName',
|
|
467
|
+
'someId'
|
|
468
|
+
)( { select } );
|
|
469
|
+
await expect( fulfillment ).rejects.toThrow(
|
|
470
|
+
`The entity being edited (someKind, someName) does not have a loaded config.`
|
|
471
|
+
);
|
|
472
|
+
} );
|
|
473
|
+
|
|
474
|
+
it( 'does nothing when there are no edits', () => {
|
|
475
|
+
const dispatch = jest.fn();
|
|
476
|
+
const select = {
|
|
477
|
+
getEntityConfig: () => ( {
|
|
478
|
+
kind: 'postType',
|
|
479
|
+
name: 'post',
|
|
480
|
+
} ),
|
|
481
|
+
getEntityRecordEdits: () => undefined,
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
clearEntityRecordEdits(
|
|
485
|
+
'postType',
|
|
486
|
+
'post',
|
|
487
|
+
1
|
|
488
|
+
)( {
|
|
489
|
+
select,
|
|
490
|
+
dispatch,
|
|
491
|
+
} );
|
|
492
|
+
|
|
493
|
+
expect( dispatch ).not.toHaveBeenCalled();
|
|
494
|
+
} );
|
|
495
|
+
|
|
496
|
+
it( 'clears all edits for an entity record', () => {
|
|
497
|
+
const dispatch = jest.fn();
|
|
498
|
+
const select = {
|
|
499
|
+
getEntityConfig: () => ( {
|
|
500
|
+
kind: 'postType',
|
|
501
|
+
name: 'post',
|
|
502
|
+
} ),
|
|
503
|
+
getEntityRecordEdits: () => ( {
|
|
504
|
+
title: 'New Title',
|
|
505
|
+
content: 'New Content',
|
|
506
|
+
} ),
|
|
507
|
+
getEditedEntityRecord: () => ( {
|
|
508
|
+
id: 1,
|
|
509
|
+
title: 'New Title',
|
|
510
|
+
content: 'New Content',
|
|
511
|
+
} ),
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
clearEntityRecordEdits(
|
|
515
|
+
'postType',
|
|
516
|
+
'post',
|
|
517
|
+
1
|
|
518
|
+
)( {
|
|
519
|
+
select,
|
|
520
|
+
dispatch,
|
|
521
|
+
} );
|
|
522
|
+
|
|
523
|
+
expect( dispatch ).toHaveBeenCalledWith( {
|
|
524
|
+
type: 'EDIT_ENTITY_RECORD',
|
|
525
|
+
kind: 'postType',
|
|
526
|
+
name: 'post',
|
|
527
|
+
recordId: 1,
|
|
528
|
+
edits: {
|
|
529
|
+
title: undefined,
|
|
530
|
+
content: undefined,
|
|
531
|
+
},
|
|
532
|
+
} );
|
|
533
|
+
} );
|
|
534
|
+
} );
|
|
535
|
+
|
|
457
536
|
describe( 'deleteEntityRecord', () => {
|
|
458
537
|
beforeEach( async () => {
|
|
459
538
|
apiFetch.mockReset();
|
|
@@ -276,4 +276,78 @@ describe( 'useEntityBlockEditor', () => {
|
|
|
276
276
|
'A paragraph<sup data-fn="abcd" class="fn"><a href="#abcd" id="abcd-link">2</a></sup>'
|
|
277
277
|
);
|
|
278
278
|
} );
|
|
279
|
+
|
|
280
|
+
it( 'preserves block clientIds across unmount and remount when content is unchanged', () => {
|
|
281
|
+
let blocks;
|
|
282
|
+
const TestComponent = () => {
|
|
283
|
+
[ blocks ] = useEntityBlockEditor( 'postType', 'post', {
|
|
284
|
+
id: 1,
|
|
285
|
+
} );
|
|
286
|
+
return <div />;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const { unmount } = render(
|
|
290
|
+
<RegistryProvider value={ registry }>
|
|
291
|
+
<TestComponent />
|
|
292
|
+
</RegistryProvider>
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const firstClientIds = blocks.map( ( b ) => b.clientId );
|
|
296
|
+
expect( firstClientIds ).toHaveLength( 2 );
|
|
297
|
+
|
|
298
|
+
// Simulate navigating away.
|
|
299
|
+
unmount();
|
|
300
|
+
|
|
301
|
+
// Simulate navigating back — same entity, same content.
|
|
302
|
+
render(
|
|
303
|
+
<RegistryProvider value={ registry }>
|
|
304
|
+
<TestComponent />
|
|
305
|
+
</RegistryProvider>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// The cache should return the same block objects with the same clientIds.
|
|
309
|
+
expect( blocks.map( ( b ) => b.clientId ) ).toEqual( firstClientIds );
|
|
310
|
+
} );
|
|
311
|
+
|
|
312
|
+
it( 'returns new blocks when content changes', () => {
|
|
313
|
+
let blocks;
|
|
314
|
+
const TestComponent = () => {
|
|
315
|
+
[ blocks ] = useEntityBlockEditor( 'postType', 'post', {
|
|
316
|
+
id: 1,
|
|
317
|
+
} );
|
|
318
|
+
return <div />;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
render(
|
|
322
|
+
<RegistryProvider value={ registry }>
|
|
323
|
+
<TestComponent />
|
|
324
|
+
</RegistryProvider>
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const firstClientIds = blocks.map( ( b ) => b.clientId );
|
|
328
|
+
|
|
329
|
+
// Receive a new entity record with different content.
|
|
330
|
+
act( () => {
|
|
331
|
+
registry
|
|
332
|
+
.dispatch( coreDataStore )
|
|
333
|
+
.receiveEntityRecords( 'postType', 'post', [
|
|
334
|
+
{
|
|
335
|
+
id: 1,
|
|
336
|
+
type: 'post',
|
|
337
|
+
content: {
|
|
338
|
+
raw: '<!-- wp:test-block --><p>Different content</p><!-- /wp:test-block -->',
|
|
339
|
+
rendered: '<p>Different content</p>',
|
|
340
|
+
},
|
|
341
|
+
meta: { footnotes: '[]' },
|
|
342
|
+
},
|
|
343
|
+
] );
|
|
344
|
+
} );
|
|
345
|
+
|
|
346
|
+
// Blocks should be new objects with new clientIds.
|
|
347
|
+
expect( blocks ).toHaveLength( 1 );
|
|
348
|
+
expect( blocks[ 0 ].attributes.content ).toEqual( 'Different content' );
|
|
349
|
+
expect( blocks.map( ( b ) => b.clientId ) ).not.toEqual(
|
|
350
|
+
firstClientIds
|
|
351
|
+
);
|
|
352
|
+
} );
|
|
279
353
|
} );
|
package/src/test/resolvers.js
CHANGED
|
@@ -171,6 +171,7 @@ describe( 'getEntityRecord', () => {
|
|
|
171
171
|
addUndoMeta: expect.any( Function ),
|
|
172
172
|
editRecord: expect.any( Function ),
|
|
173
173
|
getEditedRecord: expect.any( Function ),
|
|
174
|
+
onStatusChange: expect.any( Function ),
|
|
174
175
|
refetchRecord: expect.any( Function ),
|
|
175
176
|
restoreUndoMeta: expect.any( Function ),
|
|
176
177
|
saveRecord: expect.any( Function ),
|
|
@@ -226,6 +227,7 @@ describe( 'getEntityRecord', () => {
|
|
|
226
227
|
addUndoMeta: expect.any( Function ),
|
|
227
228
|
editRecord: expect.any( Function ),
|
|
228
229
|
getEditedRecord: expect.any( Function ),
|
|
230
|
+
onStatusChange: expect.any( Function ),
|
|
229
231
|
refetchRecord: expect.any( Function ),
|
|
230
232
|
restoreUndoMeta: expect.any( Function ),
|
|
231
233
|
saveRecord: expect.any( Function ),
|
package/src/test/store.js
CHANGED
|
@@ -112,3 +112,33 @@ describe( 'getEntityRecord', () => {
|
|
|
112
112
|
expect( triggerFetch ).not.toHaveBeenCalled();
|
|
113
113
|
} );
|
|
114
114
|
} );
|
|
115
|
+
|
|
116
|
+
describe( 'clearEntityRecordEdits', () => {
|
|
117
|
+
let registry;
|
|
118
|
+
|
|
119
|
+
beforeEach( () => {
|
|
120
|
+
registry = createTestRegistry();
|
|
121
|
+
triggerFetch.mockReset();
|
|
122
|
+
} );
|
|
123
|
+
|
|
124
|
+
it( 'should return the persisted record after clearing edits', () => {
|
|
125
|
+
const post = createTestPost( 1 );
|
|
126
|
+
const dispatch = registry.dispatch( coreDataStore );
|
|
127
|
+
const select = registry.select( coreDataStore );
|
|
128
|
+
|
|
129
|
+
dispatch.receiveEntityRecords( 'postType', 'post', post );
|
|
130
|
+
dispatch.editEntityRecord( 'postType', 'post', post.id, {
|
|
131
|
+
slug: 'updated-slug',
|
|
132
|
+
} );
|
|
133
|
+
|
|
134
|
+
expect(
|
|
135
|
+
select.getEditedEntityRecord( 'postType', 'post', post.id ).slug
|
|
136
|
+
).toBe( 'updated-slug' );
|
|
137
|
+
|
|
138
|
+
dispatch.clearEntityRecordEdits( 'postType', 'post', post.id );
|
|
139
|
+
|
|
140
|
+
expect(
|
|
141
|
+
select.getEditedEntityRecord( 'postType', 'post', post.id )
|
|
142
|
+
).toEqual( select.getRawEntityRecord( 'postType', 'post', post.id ) );
|
|
143
|
+
} );
|
|
144
|
+
} );
|
package/src/utils/crdt-blocks.ts
CHANGED
|
@@ -473,10 +473,10 @@ let localDoc: Y.Doc;
|
|
|
473
473
|
* @param updatedValue The updated value.
|
|
474
474
|
* @param cursorPosition The position of the cursor after the change occurs.
|
|
475
475
|
*/
|
|
476
|
-
function mergeRichTextUpdate(
|
|
476
|
+
export function mergeRichTextUpdate(
|
|
477
477
|
blockYText: Y.Text,
|
|
478
478
|
updatedValue: string,
|
|
479
|
-
cursorPosition: number | null
|
|
479
|
+
cursorPosition: number | null = null
|
|
480
480
|
): void {
|
|
481
481
|
// Gutenberg does not use Yjs shared types natively, so we can only subscribe
|
|
482
482
|
// to changes from store and apply them to Yjs types that we create and
|
package/src/utils/crdt.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { BaseAwareness } from '../awareness/base-awareness';
|
|
22
22
|
import {
|
|
23
23
|
mergeCrdtBlocks,
|
|
24
|
+
mergeRichTextUpdate,
|
|
24
25
|
type Block,
|
|
25
26
|
type YBlock,
|
|
26
27
|
type YBlocks,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
// Changes that can be applied to a post entity record.
|
|
46
47
|
export type PostChanges = Partial< Post > & {
|
|
47
48
|
blocks?: Block[];
|
|
49
|
+
content?: Post[ 'content' ] | string;
|
|
48
50
|
excerpt?: Post[ 'excerpt' ] | string;
|
|
49
51
|
selection?: WPSelection;
|
|
50
52
|
title?: Post[ 'title' ] | string;
|
|
@@ -53,11 +55,13 @@ export type PostChanges = Partial< Post > & {
|
|
|
53
55
|
// A post record as represented in the CRDT document (Y.Map).
|
|
54
56
|
export interface YPostRecord extends YMapRecord {
|
|
55
57
|
author: number;
|
|
56
|
-
|
|
58
|
+
// Blocks are undefined when they need to be re-parsed from content.
|
|
59
|
+
blocks: YBlocks | undefined;
|
|
60
|
+
content: Y.Text;
|
|
57
61
|
categories: number[];
|
|
58
62
|
comment_status: string;
|
|
59
63
|
date: string | null;
|
|
60
|
-
excerpt:
|
|
64
|
+
excerpt: Y.Text;
|
|
61
65
|
featured_media: number;
|
|
62
66
|
format: string;
|
|
63
67
|
meta: YMapWrap< YMapRecord >;
|
|
@@ -67,13 +71,14 @@ export interface YPostRecord extends YMapRecord {
|
|
|
67
71
|
sticky: boolean;
|
|
68
72
|
tags: number[];
|
|
69
73
|
template: string;
|
|
70
|
-
title:
|
|
74
|
+
title: Y.Text;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
// Properties that are allowed to be synced for a post.
|
|
74
78
|
const allowedPostProperties = new Set< string >( [
|
|
75
79
|
'author',
|
|
76
80
|
'blocks',
|
|
81
|
+
'content',
|
|
77
82
|
'categories',
|
|
78
83
|
'comment_status',
|
|
79
84
|
'date',
|
|
@@ -156,6 +161,14 @@ export function applyPostChangesToCRDTDoc(
|
|
|
156
161
|
|
|
157
162
|
switch ( key ) {
|
|
158
163
|
case 'blocks': {
|
|
164
|
+
// Blocks are undefined when they need to be re-parsed from content.
|
|
165
|
+
if ( ! newValue ) {
|
|
166
|
+
// Set to undefined instead of deleting the key. This is important
|
|
167
|
+
// since we iterate over the Y.Map keys in getPostChangesFromCRDTDoc.
|
|
168
|
+
ymap.set( key, undefined );
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
159
172
|
let currentBlocks = ymap.get( key );
|
|
160
173
|
|
|
161
174
|
// Initialize.
|
|
@@ -164,9 +177,6 @@ export function applyPostChangesToCRDTDoc(
|
|
|
164
177
|
ymap.set( key, currentBlocks );
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
// Block[] from local changes.
|
|
168
|
-
const newBlocks = ( newValue as PostChanges[ 'blocks' ] ) ?? [];
|
|
169
|
-
|
|
170
180
|
// Block changes from typing are bundled with a 'selection' update.
|
|
171
181
|
// Pass the resulting cursor position to the mergeCrdtBlocks function.
|
|
172
182
|
const cursorPosition =
|
|
@@ -174,15 +184,33 @@ export function applyPostChangesToCRDTDoc(
|
|
|
174
184
|
|
|
175
185
|
// Merge blocks does not need `setValue` because it is operating on a
|
|
176
186
|
// Yjs type that is already in the Y.Doc.
|
|
177
|
-
mergeCrdtBlocks( currentBlocks,
|
|
187
|
+
mergeCrdtBlocks( currentBlocks, newValue, cursorPosition );
|
|
178
188
|
break;
|
|
179
189
|
}
|
|
180
190
|
|
|
181
|
-
case '
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
case 'content':
|
|
192
|
+
case 'excerpt':
|
|
193
|
+
case 'title': {
|
|
194
|
+
const currentValue = ymap.get( key );
|
|
195
|
+
let rawValue = getRawValue( newValue );
|
|
196
|
+
|
|
197
|
+
// Copy logic from prePersistPostType to ensure that the "Auto
|
|
198
|
+
// Draft" template title is not synced.
|
|
199
|
+
if (
|
|
200
|
+
key === 'title' &&
|
|
201
|
+
! currentValue?.toString() &&
|
|
202
|
+
'Auto Draft' === rawValue
|
|
203
|
+
) {
|
|
204
|
+
rawValue = '';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ( currentValue instanceof Y.Text ) {
|
|
208
|
+
mergeRichTextUpdate( currentValue, rawValue ?? '' );
|
|
209
|
+
} else {
|
|
210
|
+
const newYText = new Y.Text( rawValue ?? '' );
|
|
211
|
+
ymap.set( key, newYText );
|
|
212
|
+
}
|
|
184
213
|
|
|
185
|
-
updateMapValue( ymap, key, currentValue, rawNewValue );
|
|
186
214
|
break;
|
|
187
215
|
}
|
|
188
216
|
|
|
@@ -227,20 +255,6 @@ export function applyPostChangesToCRDTDoc(
|
|
|
227
255
|
break;
|
|
228
256
|
}
|
|
229
257
|
|
|
230
|
-
case 'title': {
|
|
231
|
-
const currentValue = ymap.get( key );
|
|
232
|
-
|
|
233
|
-
// Copy logic from prePersistPostType to ensure that the "Auto
|
|
234
|
-
// Draft" template title is not synced.
|
|
235
|
-
let rawNewValue = getRawValue( newValue );
|
|
236
|
-
if ( ! currentValue && 'Auto Draft' === rawNewValue ) {
|
|
237
|
-
rawNewValue = '';
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
updateMapValue( ymap, key, currentValue, rawNewValue );
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
258
|
// Add support for additional properties here.
|
|
245
259
|
|
|
246
260
|
default: {
|
|
@@ -318,11 +332,11 @@ export function getPostChangesFromCRDTDoc(
|
|
|
318
332
|
ydoc.meta?.get( CRDT_DOC_META_PERSISTENCE_KEY ) &&
|
|
319
333
|
editedRecord.content
|
|
320
334
|
) {
|
|
321
|
-
const
|
|
335
|
+
const blocksJson = ymap.get( 'blocks' )?.toJSON() ?? [];
|
|
336
|
+
|
|
322
337
|
return (
|
|
323
|
-
__unstableSerializeAndClean(
|
|
324
|
-
|
|
325
|
-
).trim() !== editedRecord.content.raw.trim()
|
|
338
|
+
__unstableSerializeAndClean( blocksJson ).trim() !==
|
|
339
|
+
getRawValue( editedRecord.content )
|
|
326
340
|
);
|
|
327
341
|
}
|
|
328
342
|
|
|
@@ -375,6 +389,7 @@ export function getPostChangesFromCRDTDoc(
|
|
|
375
389
|
return haveValuesChanged( currentValue, newValue );
|
|
376
390
|
}
|
|
377
391
|
|
|
392
|
+
case 'content':
|
|
378
393
|
case 'excerpt':
|
|
379
394
|
case 'title': {
|
|
380
395
|
return haveValuesChanged(
|