@wordpress/core-data 7.41.2-next.v.202603102151.0 → 7.42.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +19 -0
  3. package/build/actions.cjs +17 -25
  4. package/build/actions.cjs.map +2 -2
  5. package/build/awareness/post-editor-awareness.cjs +46 -6
  6. package/build/awareness/post-editor-awareness.cjs.map +2 -2
  7. package/build/awareness/types.cjs.map +1 -1
  8. package/build/entities.cjs +33 -7
  9. package/build/entities.cjs.map +2 -2
  10. package/build/hooks/use-entity-prop.cjs +2 -1
  11. package/build/hooks/use-entity-prop.cjs.map +2 -2
  12. package/build/hooks/use-post-editor-awareness-state.cjs +84 -3
  13. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  14. package/build/index.cjs +3 -0
  15. package/build/index.cjs.map +2 -2
  16. package/build/private-apis.cjs +3 -1
  17. package/build/private-apis.cjs.map +2 -2
  18. package/build/queried-data/get-query-parts.cjs +7 -0
  19. package/build/queried-data/get-query-parts.cjs.map +2 -2
  20. package/build/queried-data/selectors.cjs +19 -5
  21. package/build/queried-data/selectors.cjs.map +2 -2
  22. package/build/reducer.cjs +6 -0
  23. package/build/reducer.cjs.map +2 -2
  24. package/build/resolvers.cjs +110 -74
  25. package/build/resolvers.cjs.map +2 -2
  26. package/build/selectors.cjs +29 -0
  27. package/build/selectors.cjs.map +2 -2
  28. package/build/sync.cjs +3 -0
  29. package/build/sync.cjs.map +2 -2
  30. package/build/types.cjs +16 -0
  31. package/build/types.cjs.map +3 -3
  32. package/build/utils/block-selection-history.cjs +1 -1
  33. package/build/utils/block-selection-history.cjs.map +2 -2
  34. package/build/utils/crdt-blocks.cjs +17 -3
  35. package/build/utils/crdt-blocks.cjs.map +2 -2
  36. package/build/utils/crdt-selection.cjs +4 -1
  37. package/build/utils/crdt-selection.cjs.map +2 -2
  38. package/build/utils/crdt-user-selections.cjs +9 -6
  39. package/build/utils/crdt-user-selections.cjs.map +2 -2
  40. package/build/utils/crdt-utils.cjs +54 -2
  41. package/build/utils/crdt-utils.cjs.map +2 -2
  42. package/build/utils/crdt.cjs +4 -23
  43. package/build/utils/crdt.cjs.map +2 -2
  44. package/build/utils/index.cjs +3 -0
  45. package/build/utils/index.cjs.map +2 -2
  46. package/build/utils/normalize-query-for-resolution.cjs +35 -0
  47. package/build/utils/normalize-query-for-resolution.cjs.map +7 -0
  48. package/build-module/actions.mjs +17 -25
  49. package/build-module/actions.mjs.map +2 -2
  50. package/build-module/awareness/post-editor-awareness.mjs +46 -6
  51. package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
  52. package/build-module/entities.mjs +33 -7
  53. package/build-module/entities.mjs.map +2 -2
  54. package/build-module/hooks/use-entity-prop.mjs +2 -1
  55. package/build-module/hooks/use-entity-prop.mjs.map +2 -2
  56. package/build-module/hooks/use-post-editor-awareness-state.mjs +81 -2
  57. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  58. package/build-module/index.mjs +2 -0
  59. package/build-module/index.mjs.map +2 -2
  60. package/build-module/private-apis.mjs +6 -2
  61. package/build-module/private-apis.mjs.map +2 -2
  62. package/build-module/queried-data/get-query-parts.mjs +7 -0
  63. package/build-module/queried-data/get-query-parts.mjs.map +2 -2
  64. package/build-module/queried-data/selectors.mjs +19 -5
  65. package/build-module/queried-data/selectors.mjs.map +2 -2
  66. package/build-module/reducer.mjs +6 -0
  67. package/build-module/reducer.mjs.map +2 -2
  68. package/build-module/resolvers.mjs +112 -75
  69. package/build-module/resolvers.mjs.map +2 -2
  70. package/build-module/selectors.mjs +28 -0
  71. package/build-module/selectors.mjs.map +2 -2
  72. package/build-module/sync.mjs +2 -0
  73. package/build-module/sync.mjs.map +2 -2
  74. package/build-module/types.mjs +9 -0
  75. package/build-module/types.mjs.map +4 -4
  76. package/build-module/utils/block-selection-history.mjs +5 -2
  77. package/build-module/utils/block-selection-history.mjs.map +2 -2
  78. package/build-module/utils/crdt-blocks.mjs +17 -3
  79. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  80. package/build-module/utils/crdt-selection.mjs +8 -2
  81. package/build-module/utils/crdt-selection.mjs.map +2 -2
  82. package/build-module/utils/crdt-user-selections.mjs +10 -7
  83. package/build-module/utils/crdt-user-selections.mjs.map +2 -2
  84. package/build-module/utils/crdt-utils.mjs +51 -1
  85. package/build-module/utils/crdt-utils.mjs.map +2 -2
  86. package/build-module/utils/crdt.mjs +4 -23
  87. package/build-module/utils/crdt.mjs.map +2 -2
  88. package/build-module/utils/index.mjs +2 -0
  89. package/build-module/utils/index.mjs.map +2 -2
  90. package/build-module/utils/normalize-query-for-resolution.mjs +14 -0
  91. package/build-module/utils/normalize-query-for-resolution.mjs.map +7 -0
  92. package/build-types/actions.d.ts.map +1 -1
  93. package/build-types/awareness/post-editor-awareness.d.ts +2 -2
  94. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  95. package/build-types/awareness/types.d.ts +1 -1
  96. package/build-types/awareness/types.d.ts.map +1 -1
  97. package/build-types/entities.d.ts +1 -1
  98. package/build-types/entities.d.ts.map +1 -1
  99. package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
  100. package/build-types/hooks/use-post-editor-awareness-state.d.ts +34 -10
  101. package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
  102. package/build-types/index.d.ts +2 -0
  103. package/build-types/index.d.ts.map +1 -1
  104. package/build-types/private-apis.d.ts.map +1 -1
  105. package/build-types/queried-data/get-query-parts.d.ts +7 -0
  106. package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
  107. package/build-types/queried-data/selectors.d.ts.map +1 -1
  108. package/build-types/reducer.d.ts.map +1 -1
  109. package/build-types/resolvers.d.ts +2 -1
  110. package/build-types/resolvers.d.ts.map +1 -1
  111. package/build-types/selectors.d.ts +17 -0
  112. package/build-types/selectors.d.ts.map +1 -1
  113. package/build-types/sync.d.ts +2 -2
  114. package/build-types/sync.d.ts.map +1 -1
  115. package/build-types/types.d.ts +18 -1
  116. package/build-types/types.d.ts.map +1 -1
  117. package/build-types/utils/block-selection-history.d.ts.map +1 -1
  118. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  119. package/build-types/utils/crdt-selection.d.ts.map +1 -1
  120. package/build-types/utils/crdt-user-selections.d.ts +9 -5
  121. package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
  122. package/build-types/utils/crdt-utils.d.ts +20 -0
  123. package/build-types/utils/crdt-utils.d.ts.map +1 -1
  124. package/build-types/utils/crdt.d.ts +6 -7
  125. package/build-types/utils/crdt.d.ts.map +1 -1
  126. package/build-types/utils/index.d.ts +1 -0
  127. package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
  128. package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -0
  129. package/build-types/utils/test/crdt-utils.d.ts +2 -0
  130. package/build-types/utils/test/crdt-utils.d.ts.map +1 -0
  131. package/package.json +18 -18
  132. package/src/actions.js +25 -40
  133. package/src/awareness/post-editor-awareness.ts +106 -7
  134. package/src/awareness/test/post-editor-awareness.ts +50 -10
  135. package/src/awareness/types.ts +1 -1
  136. package/src/entities.js +38 -6
  137. package/src/hooks/test/use-post-editor-awareness-state.ts +446 -3
  138. package/src/hooks/use-entity-prop.js +2 -0
  139. package/src/hooks/use-post-editor-awareness-state.ts +160 -8
  140. package/src/index.js +1 -0
  141. package/src/private-apis.js +6 -2
  142. package/src/queried-data/get-query-parts.js +13 -0
  143. package/src/queried-data/selectors.js +33 -8
  144. package/src/queried-data/test/get-query-parts.js +34 -0
  145. package/src/queried-data/test/selectors.js +183 -0
  146. package/src/reducer.js +11 -0
  147. package/src/resolvers.js +136 -88
  148. package/src/selectors.ts +56 -0
  149. package/src/sync.ts +2 -0
  150. package/src/test/entities.js +185 -1
  151. package/src/test/resolvers.js +64 -11
  152. package/src/test/selectors.js +150 -0
  153. package/src/test/store.js +66 -0
  154. package/src/types.ts +26 -1
  155. package/src/utils/block-selection-history.ts +5 -2
  156. package/src/utils/crdt-blocks.ts +32 -3
  157. package/src/utils/crdt-selection.ts +8 -2
  158. package/src/utils/crdt-user-selections.ts +20 -8
  159. package/src/utils/crdt-utils.ts +99 -0
  160. package/src/utils/crdt.ts +8 -32
  161. package/src/utils/index.js +1 -0
  162. package/src/utils/normalize-query-for-resolution.js +23 -0
  163. package/src/utils/test/crdt-blocks.ts +146 -0
  164. package/src/utils/test/crdt-user-selections.ts +5 -0
  165. package/src/utils/test/crdt-utils.ts +387 -0
  166. package/src/utils/test/crdt.ts +120 -53
package/src/actions.js CHANGED
@@ -678,40 +678,25 @@ export const saveEntityRecord =
678
678
  ? select.getRawEntityRecord( kind, name, recordId )
679
679
  : {};
680
680
 
681
+ // Most of this autosave logic is very specific to posts.
682
+ // This is fine for now as it is the only supported autosave,
683
+ // but ideally this should all be handled in the back end,
684
+ // so the client just sends and receives objects.
681
685
  if ( isAutosave ) {
682
- // Most of this autosave logic is very specific to posts.
683
- // This is fine for now as it is the only supported autosave,
684
- // but ideally this should all be handled in the back end,
685
- // so the client just sends and receives objects.
686
- const currentUser = select.getCurrentUser();
687
- const currentUserId = currentUser
688
- ? currentUser.id
689
- : undefined;
690
- const autosavePost = await resolveSelect.getAutosave(
691
- persistedRecord.type,
692
- persistedRecord.id,
693
- currentUserId
694
- );
695
- // Autosaves need all expected fields to be present.
696
- // So we fallback to the previous autosave and then
697
- // to the actual persisted entity if the edits don't
698
- // have a value.
699
- let data = {
700
- ...persistedRecord,
701
- ...autosavePost,
702
- ...record,
703
- };
704
- data = Object.keys( data ).reduce(
686
+ // Build the autosave payload from the persisted
687
+ // record and the incoming edits. The previous autosave
688
+ // is intentionally excluded to avoid stale values
689
+ // overriding reverted fields.
690
+ const merged = { ...persistedRecord, ...record };
691
+ const data = [
692
+ 'title',
693
+ 'excerpt',
694
+ 'content',
695
+ 'meta',
696
+ ].reduce(
705
697
  ( acc, key ) => {
706
- if (
707
- [
708
- 'title',
709
- 'excerpt',
710
- 'content',
711
- 'meta',
712
- ].includes( key )
713
- ) {
714
- acc[ key ] = data[ key ];
698
+ if ( key in merged ) {
699
+ acc[ key ] = merged[ key ];
715
700
  }
716
701
  return acc;
717
702
  },
@@ -721,7 +706,7 @@ export const saveEntityRecord =
721
706
  // because it can lead to unexpected results. An example would be to
722
707
  // have a draft post and change the status to publish.
723
708
  status:
724
- data.status === 'auto-draft'
709
+ merged.status === 'auto-draft'
725
710
  ? 'draft'
726
711
  : undefined,
727
712
  }
@@ -745,9 +730,12 @@ export const saveEntityRecord =
745
730
  ( acc, key ) => {
746
731
  // These properties are persisted in autosaves.
747
732
  if (
748
- [ 'title', 'excerpt', 'content' ].includes(
749
- key
750
- )
733
+ [
734
+ 'title',
735
+ 'excerpt',
736
+ 'content',
737
+ 'meta',
738
+ ].includes( key )
751
739
  ) {
752
740
  acc[ key ] = newRecord[ key ];
753
741
  } else if ( key === 'status' ) {
@@ -1114,10 +1102,7 @@ export const receiveRevisions =
1114
1102
  const entityConfig = configs.find(
1115
1103
  ( config ) => config.kind === kind && config.name === name
1116
1104
  );
1117
- const key =
1118
- entityConfig && entityConfig?.revisionKey
1119
- ? entityConfig.revisionKey
1120
- : DEFAULT_ENTITY_KEY;
1105
+ const key = entityConfig?.revisionKey ?? DEFAULT_ENTITY_KEY;
1121
1106
 
1122
1107
  dispatch( {
1123
1108
  type: 'RECEIVE_ITEM_REVISIONS',
@@ -16,12 +16,14 @@ import {
16
16
  LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS,
17
17
  } from './config';
18
18
  import { STORE_NAME as coreStore } from '../name';
19
+ import { htmlIndexToRichTextOffset } from '../utils/crdt-utils';
19
20
  import {
20
21
  areSelectionsStatesEqual,
21
22
  getSelectionState,
22
23
  SelectionType,
23
24
  } from '../utils/crdt-user-selections';
24
25
 
26
+ import { SelectionDirection } from '../types';
25
27
  import type { SelectionState, WPBlockSelection } from '../types';
26
28
  import type { YBlocks } from '../utils/crdt-blocks';
27
29
  import type {
@@ -69,6 +71,18 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
69
71
  let selectionEnd = getSelectionEnd();
70
72
  let localCursorTimeout: NodeJS.Timeout | null = null;
71
73
 
74
+ // During rapid selection changes (e.g. undo restoring content and
75
+ // selection), the debounce discards intermediate events. If we use the
76
+ // last intermediate state instead of the overall change it can produce
77
+ // the wrong direction.
78
+ // Use selectionBeforeDebounce to capture the selection state from
79
+ // before the debounce window so that direction is computed across the
80
+ // full window when it fires.
81
+ let selectionBeforeDebounce: {
82
+ start: WPBlockSelection;
83
+ end: WPBlockSelection;
84
+ } | null = null;
85
+
72
86
  subscribe( () => {
73
87
  const newSelectionStart = getSelectionStart();
74
88
  const newSelectionEnd = getSelectionEnd();
@@ -80,6 +94,15 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
80
94
  return;
81
95
  }
82
96
 
97
+ // On the first change of a debounce window, snapshot the state
98
+ // we're moving away from.
99
+ if ( ! selectionBeforeDebounce ) {
100
+ selectionBeforeDebounce = {
101
+ start: selectionStart,
102
+ end: selectionEnd,
103
+ };
104
+ }
105
+
83
106
  selectionStart = newSelectionStart;
84
107
  selectionEnd = newSelectionEnd;
85
108
 
@@ -103,10 +126,29 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
103
126
  }
104
127
 
105
128
  localCursorTimeout = setTimeout( () => {
129
+ // Compute direction across the full debounce window.
130
+ const selectionStateOptions: {
131
+ selectionDirection?: SelectionDirection;
132
+ } = {};
133
+
134
+ if ( selectionBeforeDebounce ) {
135
+ selectionStateOptions.selectionDirection =
136
+ detectSelectionDirection(
137
+ selectionBeforeDebounce.start,
138
+ selectionBeforeDebounce.end,
139
+ selectionStart,
140
+ selectionEnd
141
+ );
142
+
143
+ // Reset debounced selection state.
144
+ selectionBeforeDebounce = null;
145
+ }
146
+
106
147
  const selectionState = getSelectionState(
107
148
  selectionStart,
108
149
  selectionEnd,
109
- this.doc
150
+ this.doc,
151
+ selectionStateOptions
110
152
  );
111
153
 
112
154
  this.setThrottledLocalStateField(
@@ -171,6 +213,10 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
171
213
  return state1 === state2;
172
214
  }
173
215
 
216
+ if ( ! state1.selection || ! state2.selection ) {
217
+ return state1.selection === state2.selection;
218
+ }
219
+
174
220
  return areSelectionsStatesEqual( state1.selection, state2.selection );
175
221
  }
176
222
 
@@ -188,14 +234,14 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
188
234
  * clientIds (e.g. in "Show Template" mode where blocks are cloned).
189
235
  *
190
236
  * @param selection - The selection state.
191
- * @return The text index and block client ID, or nulls if not resolvable.
237
+ * @return The rich-text offset and block client ID, or nulls if not resolvable.
192
238
  */
193
239
  public convertSelectionStateToAbsolute( selection: SelectionState ): {
194
- textIndex: number | null;
240
+ richTextOffset: number | null;
195
241
  localClientId: string | null;
196
242
  } {
197
243
  if ( selection.type === SelectionType.None ) {
198
- return { textIndex: null, localClientId: null };
244
+ return { richTextOffset: null, localClientId: null };
199
245
  }
200
246
 
201
247
  if ( selection.type === SelectionType.WholeBlock ) {
@@ -218,7 +264,7 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
218
264
  }
219
265
  }
220
266
 
221
- return { textIndex: null, localClientId };
267
+ return { richTextOffset: null, localClientId };
222
268
  }
223
269
 
224
270
  // Text-based selections: resolve cursor position and navigate up.
@@ -233,7 +279,7 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
233
279
  );
234
280
 
235
281
  if ( ! absolutePosition ) {
236
- return { textIndex: null, localClientId: null };
282
+ return { richTextOffset: null, localClientId: null };
237
283
  }
238
284
 
239
285
  // Navigate up: Y.Text -> attributes Y.Map -> block Y.Map
@@ -242,7 +288,13 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
242
288
  yType instanceof Y.Map ? getBlockPathInYdoc( yType ) : null;
243
289
  const localClientId = path ? resolveBlockClientIdByPath( path ) : null;
244
290
 
245
- return { textIndex: absolutePosition.index, localClientId };
291
+ return {
292
+ richTextOffset: htmlIndexToRichTextOffset(
293
+ absolutePosition.type.toString(),
294
+ absolutePosition.index
295
+ ),
296
+ localClientId,
297
+ };
246
298
  }
247
299
 
248
300
  /**
@@ -325,3 +377,50 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
325
377
  };
326
378
  }
327
379
  }
380
+
381
+ /**
382
+ * Detect the direction of a selection change by comparing old and new edges.
383
+ *
384
+ * When the user extends a selection backward (e.g. Shift+Left), the
385
+ * selectionStart edge moves while selectionEnd stays fixed, so the caret
386
+ * is at the start. The reverse is true for forward extension.
387
+ *
388
+ * @param prevStart - The previous selectionStart.
389
+ * @param prevEnd - The previous selectionEnd.
390
+ * @param newStart - The new selectionStart.
391
+ * @param newEnd - The new selectionEnd.
392
+ * @return The detected direction, defaulting to Forward when indeterminate.
393
+ */
394
+ function detectSelectionDirection(
395
+ prevStart: WPBlockSelection,
396
+ prevEnd: WPBlockSelection,
397
+ newStart: WPBlockSelection,
398
+ newEnd: WPBlockSelection
399
+ ): SelectionDirection {
400
+ const startMoved = ! areBlockSelectionsEqual( prevStart, newStart );
401
+ const endMoved = ! areBlockSelectionsEqual( prevEnd, newEnd );
402
+
403
+ if ( startMoved && ! endMoved ) {
404
+ return SelectionDirection.Backward;
405
+ }
406
+
407
+ return SelectionDirection.Forward;
408
+ }
409
+
410
+ /**
411
+ * Compare two WPBlockSelection objects by value.
412
+ *
413
+ * @param a - First selection.
414
+ * @param b - Second selection.
415
+ * @return True if all fields are equal.
416
+ */
417
+ function areBlockSelectionsEqual(
418
+ a: WPBlockSelection,
419
+ b: WPBlockSelection
420
+ ): boolean {
421
+ return (
422
+ a.clientId === b.clientId &&
423
+ a.attributeKey === b.attributeKey &&
424
+ a.offset === b.offset
425
+ );
426
+ }
@@ -23,6 +23,11 @@ jest.mock( '@wordpress/data', () => ( {
23
23
  select: jest.fn(),
24
24
  subscribe: jest.fn(),
25
25
  resolveSelect: jest.fn(),
26
+ // Needed because @wordpress/rich-text initialises its store at import time.
27
+ combineReducers: jest.fn( () => jest.fn( () => ( {} ) ) ),
28
+ createReduxStore: jest.fn( () => ( {} ) ),
29
+ register: jest.fn(),
30
+ createSelector: ( selector: Function ) => selector,
26
31
  } ) );
27
32
 
28
33
  jest.mock( '@wordpress/block-editor', () => ( {
@@ -422,6 +427,41 @@ describe( 'PostEditorAwareness', () => {
422
427
  // Callback should not be called for equal editor states
423
428
  expect( callback ).not.toHaveBeenCalled();
424
429
  } );
430
+
431
+ test( 'should not notify when editorState without selection is unchanged', () => {
432
+ const awareness = new PostEditorAwareness(
433
+ doc,
434
+ 'postType',
435
+ 'post',
436
+ 123
437
+ );
438
+ awareness.setUp();
439
+
440
+ awareness.setLocalStateField( 'editorState', {} );
441
+
442
+ const callback = jest.fn();
443
+ awareness.onStateChange( callback );
444
+
445
+ awareness.emit( 'change', [
446
+ {
447
+ added: [],
448
+ updated: [ awareness.clientID ],
449
+ removed: [],
450
+ },
451
+ ] );
452
+ callback.mockClear();
453
+
454
+ awareness.setLocalStateField( 'editorState', {} );
455
+ awareness.emit( 'change', [
456
+ {
457
+ added: [],
458
+ updated: [ awareness.clientID ],
459
+ removed: [],
460
+ },
461
+ ] );
462
+
463
+ expect( callback ).not.toHaveBeenCalled();
464
+ } );
425
465
  } );
426
466
 
427
467
  describe( 'convertSelectionStateToAbsolute', () => {
@@ -456,7 +496,7 @@ describe( 'PostEditorAwareness', () => {
456
496
  awareness.convertSelectionStateToAbsolute( selection );
457
497
 
458
498
  // Should return nulls when the relative position's type cannot be found
459
- expect( result.textIndex ).toBeNull();
499
+ expect( result.richTextOffset ).toBeNull();
460
500
  expect( result.localClientId ).toBeNull();
461
501
  } );
462
502
 
@@ -494,7 +534,7 @@ describe( 'PostEditorAwareness', () => {
494
534
  const result =
495
535
  awareness.convertSelectionStateToAbsolute( selection );
496
536
 
497
- expect( result.textIndex ).toBe( 5 );
537
+ expect( result.richTextOffset ).toBe( 5 );
498
538
  expect( result.localClientId ).toBe( 'block-1' );
499
539
  } );
500
540
 
@@ -526,7 +566,7 @@ describe( 'PostEditorAwareness', () => {
526
566
  const result =
527
567
  awareness.convertSelectionStateToAbsolute( selection );
528
568
 
529
- expect( result.textIndex ).toBeNull();
569
+ expect( result.richTextOffset ).toBeNull();
530
570
  expect( result.localClientId ).toBe( 'block-1' );
531
571
  } );
532
572
  } );
@@ -732,7 +772,7 @@ describe( 'PostEditorAwareness', () => {
732
772
  const result =
733
773
  awareness.convertSelectionStateToAbsolute( selection );
734
774
 
735
- expect( result.textIndex ).toBe( 2 );
775
+ expect( result.richTextOffset ).toBe( 2 );
736
776
  expect( result.localClientId ).toBe( 'local-2' );
737
777
 
738
778
  nestedDoc.destroy();
@@ -805,7 +845,7 @@ describe( 'PostEditorAwareness', () => {
805
845
  const result =
806
846
  awareness.convertSelectionStateToAbsolute( selection );
807
847
 
808
- expect( result.textIndex ).toBe( 5 );
848
+ expect( result.richTextOffset ).toBe( 5 );
809
849
  expect( result.localClientId ).toBe( 'local-inner-1' );
810
850
 
811
851
  nestedDoc.destroy();
@@ -860,7 +900,7 @@ describe( 'PostEditorAwareness', () => {
860
900
  const result =
861
901
  awareness.convertSelectionStateToAbsolute( selection );
862
902
 
863
- expect( result.textIndex ).toBeNull();
903
+ expect( result.richTextOffset ).toBeNull();
864
904
  expect( result.localClientId ).toBe( 'local-img' );
865
905
 
866
906
  nestedDoc.destroy();
@@ -954,7 +994,7 @@ describe( 'PostEditorAwareness', () => {
954
994
  const result =
955
995
  awareness.convertSelectionStateToAbsolute( selection );
956
996
 
957
- expect( result.textIndex ).toBe( 7 );
997
+ expect( result.richTextOffset ).toBe( 7 );
958
998
  expect( result.localClientId ).toBe( 'local-deep-1' );
959
999
 
960
1000
  nestedDoc.destroy();
@@ -1055,7 +1095,7 @@ describe( 'PostEditorAwareness', () => {
1055
1095
  const result =
1056
1096
  awareness.convertSelectionStateToAbsolute( selection );
1057
1097
 
1058
- expect( result.textIndex ).toBe( 4 );
1098
+ expect( result.richTextOffset ).toBe( 4 );
1059
1099
  // Should resolve to the post-content inner block, not a template block
1060
1100
  expect( result.localClientId ).toBe( 'local-para-1' );
1061
1101
  // Verify getBlocks was called with the post-content clientId
@@ -1127,7 +1167,7 @@ describe( 'PostEditorAwareness', () => {
1127
1167
  const result =
1128
1168
  awareness.convertSelectionStateToAbsolute( selection );
1129
1169
 
1130
- expect( result.textIndex ).toBeNull();
1170
+ expect( result.richTextOffset ).toBeNull();
1131
1171
  expect( result.localClientId ).toBe( 'local-img' );
1132
1172
 
1133
1173
  templateDoc.destroy();
@@ -1176,7 +1216,7 @@ describe( 'PostEditorAwareness', () => {
1176
1216
  const result =
1177
1217
  awareness.convertSelectionStateToAbsolute( selection );
1178
1218
 
1179
- expect( result.textIndex ).toBe( 3 );
1219
+ expect( result.richTextOffset ).toBe( 3 );
1180
1220
  expect( result.localClientId ).toBe( 'local-para' );
1181
1221
 
1182
1222
  normalDoc.destroy();
@@ -30,7 +30,7 @@ export interface BaseState {
30
30
  * The editor state includes information about the collaborator's current selection.
31
31
  */
32
32
  export interface EditorState {
33
- selection: SelectionState;
33
+ selection?: SelectionState;
34
34
  }
35
35
 
36
36
  /**
package/src/entities.js CHANGED
@@ -226,6 +226,7 @@ export const rootEntitiesConfig = [
226
226
  baseURLParams: { context: 'view' },
227
227
  plural: 'fontCollections',
228
228
  key: 'slug',
229
+ supportsPagination: true,
229
230
  },
230
231
  {
231
232
  label: __( 'Icons' ),
@@ -328,15 +329,42 @@ export const prePersistPostType = async (
328
329
  * @return {Promise} Entities promise
329
330
  */
330
331
  async function loadPostTypeEntities() {
331
- const postTypes = await apiFetch( {
332
- path: '/wp/v2/types?context=view',
333
- } );
332
+ const postTypesPromise = apiFetch( { path: '/wp/v2/types?context=view' } );
333
+ const taxonomiesPromise = window._wpCollaborationEnabled
334
+ ? apiFetch( { path: '/wp/v2/taxonomies?context=view' } )
335
+ : Promise.resolve( {} );
336
+ const [ postTypes, taxonomies ] = await Promise.all( [
337
+ postTypesPromise,
338
+ taxonomiesPromise,
339
+ ] );
340
+
334
341
  return Object.entries( postTypes ?? {} ).map( ( [ name, postType ] ) => {
335
342
  const isTemplate = [ 'wp_template', 'wp_template_part' ].includes(
336
343
  name
337
344
  );
338
345
  const namespace = postType?.rest_namespace ?? 'wp/v2';
339
346
 
347
+ const syncedProperties = new Set( [
348
+ 'author',
349
+ 'blocks',
350
+ 'content',
351
+ 'comment_status',
352
+ 'date',
353
+ 'excerpt',
354
+ 'featured_media',
355
+ 'format',
356
+ 'meta',
357
+ 'ping_status',
358
+ 'slug',
359
+ 'status',
360
+ 'sticky',
361
+ 'template',
362
+ 'title',
363
+ ...( postType.taxonomies
364
+ ?.map( ( taxonomy ) => taxonomies?.[ taxonomy ]?.rest_base )
365
+ ?.filter( Boolean ) ?? [] ),
366
+ ] );
367
+
340
368
  const entity = {
341
369
  kind: 'postType',
342
370
  baseURL: `/${ namespace }/${ postType.rest_base }`,
@@ -384,7 +412,7 @@ async function loadPostTypeEntities() {
384
412
  * @return {void}
385
413
  */
386
414
  applyChangesToCRDTDoc: ( crdtDoc, changes ) =>
387
- applyPostChangesToCRDTDoc( crdtDoc, changes, postType ),
415
+ applyPostChangesToCRDTDoc( crdtDoc, changes, syncedProperties ),
388
416
 
389
417
  /**
390
418
  * Create the awareness instance for the entity's CRDT document.
@@ -408,7 +436,11 @@ async function loadPostTypeEntities() {
408
436
  * @return {Partial< import('@wordpress/sync').ObjectData >} Changes to record
409
437
  */
410
438
  getChangesFromCRDTDoc: ( crdtDoc, editedRecord ) =>
411
- getPostChangesFromCRDTDoc( crdtDoc, editedRecord, postType ),
439
+ getPostChangesFromCRDTDoc(
440
+ crdtDoc,
441
+ editedRecord,
442
+ syncedProperties
443
+ ),
412
444
 
413
445
  /**
414
446
  * Extract changes from a CRDT document that can be used to update the
@@ -419,7 +451,7 @@ async function loadPostTypeEntities() {
419
451
  */
420
452
  getPersistedCRDTDoc: ( record ) => {
421
453
  return (
422
- record?.meta[ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ] ||
454
+ record?.meta?.[ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ] ||
423
455
  null
424
456
  );
425
457
  },