@wordpress/core-data 7.48.0 → 7.48.2-next.v.202606191442.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 (148) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/build/actions.cjs +1 -7
  3. package/build/actions.cjs.map +3 -3
  4. package/build/awareness/block-lookup.cjs +14 -26
  5. package/build/awareness/block-lookup.cjs.map +2 -2
  6. package/build/awareness/post-editor-awareness.cjs +4 -3
  7. package/build/awareness/post-editor-awareness.cjs.map +2 -2
  8. package/build/entities.cjs +6 -3
  9. package/build/entities.cjs.map +2 -2
  10. package/build/entity-types/helpers.cjs.map +1 -1
  11. package/build/hooks/use-entity-record.cjs +21 -19
  12. package/build/hooks/use-entity-record.cjs.map +3 -3
  13. package/build/hooks/use-entity-records.cjs +22 -20
  14. package/build/hooks/use-entity-records.cjs.map +3 -3
  15. package/build/hooks/use-post-editor-awareness-state.cjs +8 -2
  16. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  17. package/build/hooks/use-query-select.cjs +2 -20
  18. package/build/hooks/use-query-select.cjs.map +2 -2
  19. package/build/hooks/utils.cjs +53 -0
  20. package/build/hooks/utils.cjs.map +7 -0
  21. package/build/private-actions.cjs +8 -0
  22. package/build/private-actions.cjs.map +2 -2
  23. package/build/private-selectors.cjs.map +2 -2
  24. package/build/reducer.cjs +23 -7
  25. package/build/reducer.cjs.map +2 -2
  26. package/build/resolvers.cjs +13 -8
  27. package/build/resolvers.cjs.map +2 -2
  28. package/build/selectors.cjs +7 -0
  29. package/build/selectors.cjs.map +2 -2
  30. package/build/types.cjs.map +1 -1
  31. package/build/utils/clear-unchanged-edits.cjs +51 -0
  32. package/build/utils/clear-unchanged-edits.cjs.map +7 -0
  33. package/build/utils/crdt-blocks.cjs +12 -2
  34. package/build/utils/crdt-blocks.cjs.map +2 -2
  35. package/build/utils/crdt-user-selections.cjs.map +1 -1
  36. package/build/utils/crdt-utils.cjs.map +1 -1
  37. package/build/utils/crdt.cjs +2 -1
  38. package/build/utils/crdt.cjs.map +2 -2
  39. package/build/utils/index.cjs +6 -0
  40. package/build/utils/index.cjs.map +2 -2
  41. package/build/utils/save-crdt-doc.cjs +75 -0
  42. package/build/utils/save-crdt-doc.cjs.map +7 -0
  43. package/build/utils/set-nested-value.cjs.map +1 -1
  44. package/build-module/actions.mjs +2 -8
  45. package/build-module/actions.mjs.map +2 -2
  46. package/build-module/awareness/block-lookup.mjs +13 -26
  47. package/build-module/awareness/block-lookup.mjs.map +2 -2
  48. package/build-module/awareness/post-editor-awareness.mjs +4 -3
  49. package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
  50. package/build-module/entities.mjs +6 -3
  51. package/build-module/entities.mjs.map +2 -2
  52. package/build-module/hooks/use-entity-record.mjs +21 -19
  53. package/build-module/hooks/use-entity-record.mjs.map +2 -2
  54. package/build-module/hooks/use-entity-records.mjs +20 -18
  55. package/build-module/hooks/use-entity-records.mjs.map +2 -2
  56. package/build-module/hooks/use-post-editor-awareness-state.mjs +9 -3
  57. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  58. package/build-module/hooks/use-query-select.mjs +2 -20
  59. package/build-module/hooks/use-query-select.mjs.map +2 -2
  60. package/build-module/hooks/utils.mjs +28 -0
  61. package/build-module/hooks/utils.mjs.map +7 -0
  62. package/build-module/private-actions.mjs +7 -0
  63. package/build-module/private-actions.mjs.map +2 -2
  64. package/build-module/private-selectors.mjs.map +2 -2
  65. package/build-module/reducer.mjs +23 -8
  66. package/build-module/reducer.mjs.map +2 -2
  67. package/build-module/resolvers.mjs +15 -9
  68. package/build-module/resolvers.mjs.map +2 -2
  69. package/build-module/selectors.mjs +7 -0
  70. package/build-module/selectors.mjs.map +2 -2
  71. package/build-module/utils/clear-unchanged-edits.mjs +20 -0
  72. package/build-module/utils/clear-unchanged-edits.mjs.map +7 -0
  73. package/build-module/utils/crdt-blocks.mjs +12 -2
  74. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  75. package/build-module/utils/crdt-user-selections.mjs.map +1 -1
  76. package/build-module/utils/crdt-utils.mjs.map +1 -1
  77. package/build-module/utils/crdt.mjs +2 -1
  78. package/build-module/utils/crdt.mjs.map +2 -2
  79. package/build-module/utils/index.mjs +24 -20
  80. package/build-module/utils/index.mjs.map +2 -2
  81. package/build-module/utils/save-crdt-doc.mjs +40 -0
  82. package/build-module/utils/save-crdt-doc.mjs.map +7 -0
  83. package/build-module/utils/set-nested-value.mjs.map +1 -1
  84. package/build-types/actions.d.ts.map +1 -1
  85. package/build-types/awareness/block-lookup.d.ts +27 -7
  86. package/build-types/awareness/block-lookup.d.ts.map +1 -1
  87. package/build-types/awareness/post-editor-awareness.d.ts +3 -1
  88. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  89. package/build-types/entities.d.ts.map +1 -1
  90. package/build-types/hooks/use-entity-record.d.ts +4 -0
  91. package/build-types/hooks/use-entity-record.d.ts.map +1 -1
  92. package/build-types/hooks/use-entity-records.d.ts +5 -1
  93. package/build-types/hooks/use-entity-records.d.ts.map +1 -1
  94. package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
  95. package/build-types/hooks/utils.d.ts +22 -0
  96. package/build-types/hooks/utils.d.ts.map +1 -0
  97. package/build-types/index.d.ts +8 -8
  98. package/build-types/private-actions.d.ts +15 -0
  99. package/build-types/private-actions.d.ts.map +1 -1
  100. package/build-types/private-selectors.d.ts +0 -12
  101. package/build-types/private-selectors.d.ts.map +1 -1
  102. package/build-types/reducer.d.ts +15 -0
  103. package/build-types/reducer.d.ts.map +1 -1
  104. package/build-types/resolvers.d.ts.map +1 -1
  105. package/build-types/selectors.d.ts +12 -8
  106. package/build-types/selectors.d.ts.map +1 -1
  107. package/build-types/utils/clear-unchanged-edits.d.ts +12 -0
  108. package/build-types/utils/clear-unchanged-edits.d.ts.map +1 -0
  109. package/build-types/utils/crdt-blocks.d.ts +5 -1
  110. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  111. package/build-types/utils/crdt.d.ts.map +1 -1
  112. package/build-types/utils/index.d.ts +2 -0
  113. package/build-types/utils/index.d.ts.map +1 -1
  114. package/build-types/utils/save-crdt-doc.d.ts +8 -0
  115. package/build-types/utils/save-crdt-doc.d.ts.map +1 -0
  116. package/package.json +27 -20
  117. package/src/actions.js +2 -10
  118. package/src/awareness/block-lookup.ts +21 -62
  119. package/src/awareness/post-editor-awareness.ts +8 -3
  120. package/src/awareness/test/block-lookup.ts +98 -94
  121. package/src/awareness/test/post-editor-awareness.ts +177 -180
  122. package/src/entities.js +14 -3
  123. package/src/hooks/test/use-entity-record.js +5 -1
  124. package/src/hooks/test/use-post-editor-awareness-state.ts +10 -2
  125. package/src/hooks/use-entity-record.ts +26 -19
  126. package/src/hooks/use-entity-records.ts +26 -18
  127. package/src/hooks/use-post-editor-awareness-state.ts +20 -7
  128. package/src/hooks/use-query-select.ts +2 -23
  129. package/src/hooks/utils.ts +40 -0
  130. package/src/private-actions.js +18 -0
  131. package/src/private-selectors.ts +0 -12
  132. package/src/reducer.js +30 -9
  133. package/src/resolvers.js +20 -13
  134. package/src/selectors.ts +11 -0
  135. package/src/test/entities.js +51 -0
  136. package/src/test/private-selectors.js +66 -0
  137. package/src/test/reducer.js +44 -0
  138. package/src/test/resolvers.js +121 -113
  139. package/src/test/selectors.js +48 -0
  140. package/src/utils/clear-unchanged-edits.ts +34 -0
  141. package/src/utils/crdt-blocks.ts +27 -22
  142. package/src/utils/crdt.ts +2 -1
  143. package/src/utils/index.js +2 -0
  144. package/src/utils/save-crdt-doc.js +64 -0
  145. package/src/utils/test/clear-unchanged-edits.js +42 -0
  146. package/src/utils/test/crdt-blocks.ts +57 -2
  147. package/src/utils/test/rtc-rich-text-cursor-scope.test.js +2 -2
  148. package/src/utils/test/save-crdt-doc.js +185 -0
@@ -584,6 +584,14 @@ describe( 'PostEditorAwareness', () => {
584
584
  } );
585
585
 
586
586
  describe( 'convertSelectionStateToAbsolute', () => {
587
+ const defaultEditorBlocks = [
588
+ {
589
+ clientId: 'block-1',
590
+ name: 'core/paragraph',
591
+ innerBlocks: [],
592
+ },
593
+ ];
594
+
587
595
  test( 'should return nulls when relative position cannot be resolved', () => {
588
596
  const awareness = new PostEditorAwareness(
589
597
  doc,
@@ -611,8 +619,10 @@ describe( 'PostEditorAwareness', () => {
611
619
  },
612
620
  };
613
621
 
614
- const result =
615
- awareness.convertSelectionStateToAbsolute( selection );
622
+ const result = awareness.convertSelectionStateToAbsolute(
623
+ selection,
624
+ defaultEditorBlocks
625
+ );
616
626
 
617
627
  // Should return nulls when the relative position's type cannot be found
618
628
  expect( result.richTextOffset ).toBeNull();
@@ -651,8 +661,10 @@ describe( 'PostEditorAwareness', () => {
651
661
  },
652
662
  };
653
663
 
654
- const result =
655
- awareness.convertSelectionStateToAbsolute( selection );
664
+ const result = awareness.convertSelectionStateToAbsolute(
665
+ selection,
666
+ defaultEditorBlocks
667
+ );
656
668
 
657
669
  expect( result.richTextOffset ).toBe( 5 );
658
670
  expect( result.localClientId ).toBe( 'block-1' );
@@ -684,8 +696,10 @@ describe( 'PostEditorAwareness', () => {
684
696
  blockPosition,
685
697
  };
686
698
 
687
- const result =
688
- awareness.convertSelectionStateToAbsolute( selection );
699
+ const result = awareness.convertSelectionStateToAbsolute(
700
+ selection,
701
+ defaultEditorBlocks
702
+ );
689
703
 
690
704
  expect( result.richTextOffset ).toBeNull();
691
705
  expect( result.localClientId ).toBe( 'block-1' );
@@ -700,9 +714,10 @@ describe( 'PostEditorAwareness', () => {
700
714
  123
701
715
  );
702
716
 
703
- const result = awareness.convertSelectionStateToAbsolute( {
704
- type: SelectionType.None,
705
- } );
717
+ const result = awareness.convertSelectionStateToAbsolute(
718
+ { type: SelectionType.None },
719
+ []
720
+ );
706
721
 
707
722
  expect( result.attributeKey ).toBeNull();
708
723
  } );
@@ -737,8 +752,10 @@ describe( 'PostEditorAwareness', () => {
737
752
  },
738
753
  };
739
754
 
740
- const result =
741
- awareness.convertSelectionStateToAbsolute( selection );
755
+ const result = awareness.convertSelectionStateToAbsolute(
756
+ selection,
757
+ defaultEditorBlocks
758
+ );
742
759
 
743
760
  expect( result.attributeKey ).toBe( 'body.0.cells.0.content' );
744
761
  } );
@@ -905,13 +922,23 @@ describe( 'PostEditorAwareness', () => {
905
922
  } ),
906
923
  ] );
907
924
 
908
- mockBlockEditorStore( {
909
- blocks: [
910
- { clientId: 'local-0', innerBlocks: [] },
911
- { clientId: 'local-1', innerBlocks: [] },
912
- { clientId: 'local-2', innerBlocks: [] },
913
- ],
914
- } );
925
+ const editorBlocks = [
926
+ {
927
+ clientId: 'local-0',
928
+ name: 'core/paragraph',
929
+ innerBlocks: [],
930
+ },
931
+ {
932
+ clientId: 'local-1',
933
+ name: 'core/paragraph',
934
+ innerBlocks: [],
935
+ },
936
+ {
937
+ clientId: 'local-2',
938
+ name: 'core/paragraph',
939
+ innerBlocks: [],
940
+ },
941
+ ];
915
942
 
916
943
  const awareness = new PostEditorAwareness(
917
944
  nestedDoc,
@@ -942,8 +969,10 @@ describe( 'PostEditorAwareness', () => {
942
969
  },
943
970
  };
944
971
 
945
- const result =
946
- awareness.convertSelectionStateToAbsolute( selection );
972
+ const result = awareness.convertSelectionStateToAbsolute(
973
+ selection,
974
+ editorBlocks
975
+ );
947
976
 
948
977
  expect( result.richTextOffset ).toBe( 2 );
949
978
  expect( result.localClientId ).toBe( 'local-2' );
@@ -968,17 +997,24 @@ describe( 'PostEditorAwareness', () => {
968
997
 
969
998
  const nestedDoc = createTestDocWithBlocks( [ outerColumn ] );
970
999
 
971
- mockBlockEditorStore( {
972
- blocks: [
973
- {
974
- clientId: 'local-outer',
975
- innerBlocks: [
976
- { clientId: 'local-inner-0', innerBlocks: [] },
977
- { clientId: 'local-inner-1', innerBlocks: [] },
978
- ],
979
- },
980
- ],
981
- } );
1000
+ const editorBlocks = [
1001
+ {
1002
+ clientId: 'local-outer',
1003
+ name: 'core/column',
1004
+ innerBlocks: [
1005
+ {
1006
+ clientId: 'local-inner-0',
1007
+ name: 'core/paragraph',
1008
+ innerBlocks: [],
1009
+ },
1010
+ {
1011
+ clientId: 'local-inner-1',
1012
+ name: 'core/paragraph',
1013
+ innerBlocks: [],
1014
+ },
1015
+ ],
1016
+ },
1017
+ ];
982
1018
 
983
1019
  const awareness = new PostEditorAwareness(
984
1020
  nestedDoc,
@@ -1015,8 +1051,10 @@ describe( 'PostEditorAwareness', () => {
1015
1051
  },
1016
1052
  };
1017
1053
 
1018
- const result =
1019
- awareness.convertSelectionStateToAbsolute( selection );
1054
+ const result = awareness.convertSelectionStateToAbsolute(
1055
+ selection,
1056
+ editorBlocks
1057
+ );
1020
1058
 
1021
1059
  expect( result.richTextOffset ).toBe( 5 );
1022
1060
  expect( result.localClientId ).toBe( 'local-inner-1' );
@@ -1032,16 +1070,19 @@ describe( 'PostEditorAwareness', () => {
1032
1070
 
1033
1071
  const nestedDoc = createTestDocWithBlocks( [ outerColumn ] );
1034
1072
 
1035
- mockBlockEditorStore( {
1036
- blocks: [
1037
- {
1038
- clientId: 'local-col',
1039
- innerBlocks: [
1040
- { clientId: 'local-img', innerBlocks: [] },
1041
- ],
1042
- },
1043
- ],
1044
- } );
1073
+ const editorBlocks = [
1074
+ {
1075
+ clientId: 'local-col',
1076
+ name: 'core/column',
1077
+ innerBlocks: [
1078
+ {
1079
+ clientId: 'local-img',
1080
+ name: 'core/image',
1081
+ innerBlocks: [],
1082
+ },
1083
+ ],
1084
+ },
1085
+ ];
1045
1086
 
1046
1087
  const awareness = new PostEditorAwareness(
1047
1088
  nestedDoc,
@@ -1070,8 +1111,10 @@ describe( 'PostEditorAwareness', () => {
1070
1111
  blockPosition,
1071
1112
  };
1072
1113
 
1073
- const result =
1074
- awareness.convertSelectionStateToAbsolute( selection );
1114
+ const result = awareness.convertSelectionStateToAbsolute(
1115
+ selection,
1116
+ editorBlocks
1117
+ );
1075
1118
 
1076
1119
  expect( result.richTextOffset ).toBeNull();
1077
1120
  expect( result.localClientId ).toBe( 'local-img' );
@@ -1103,29 +1146,35 @@ describe( 'PostEditorAwareness', () => {
1103
1146
  outerColumns1,
1104
1147
  ] );
1105
1148
 
1106
- mockBlockEditorStore( {
1107
- blocks: [
1108
- { clientId: 'local-outer-0', innerBlocks: [] },
1109
- {
1110
- clientId: 'local-outer-1',
1111
- innerBlocks: [
1112
- {
1113
- clientId: 'local-mid',
1114
- innerBlocks: [
1115
- {
1116
- clientId: 'local-deep-0',
1117
- innerBlocks: [],
1118
- },
1119
- {
1120
- clientId: 'local-deep-1',
1121
- innerBlocks: [],
1122
- },
1123
- ],
1124
- },
1125
- ],
1126
- },
1127
- ],
1128
- } );
1149
+ const editorBlocks = [
1150
+ {
1151
+ clientId: 'local-outer-0',
1152
+ name: 'core/columns',
1153
+ innerBlocks: [],
1154
+ },
1155
+ {
1156
+ clientId: 'local-outer-1',
1157
+ name: 'core/columns',
1158
+ innerBlocks: [
1159
+ {
1160
+ clientId: 'local-mid',
1161
+ name: 'core/column',
1162
+ innerBlocks: [
1163
+ {
1164
+ clientId: 'local-deep-0',
1165
+ name: 'core/paragraph',
1166
+ innerBlocks: [],
1167
+ },
1168
+ {
1169
+ clientId: 'local-deep-1',
1170
+ name: 'core/paragraph',
1171
+ innerBlocks: [],
1172
+ },
1173
+ ],
1174
+ },
1175
+ ],
1176
+ },
1177
+ ];
1129
1178
 
1130
1179
  const awareness = new PostEditorAwareness(
1131
1180
  nestedDoc,
@@ -1164,8 +1213,10 @@ describe( 'PostEditorAwareness', () => {
1164
1213
  },
1165
1214
  };
1166
1215
 
1167
- const result =
1168
- awareness.convertSelectionStateToAbsolute( selection );
1216
+ const result = awareness.convertSelectionStateToAbsolute(
1217
+ selection,
1218
+ editorBlocks
1219
+ );
1169
1220
 
1170
1221
  expect( result.richTextOffset ).toBe( 7 );
1171
1222
  expect( result.localClientId ).toBe( 'local-deep-1' );
@@ -1175,6 +1226,14 @@ describe( 'PostEditorAwareness', () => {
1175
1226
  } );
1176
1227
 
1177
1228
  describe( 'convertSelectionStateToAbsolute with nested rich-text attributes', () => {
1229
+ const editorBlocks = [
1230
+ {
1231
+ clientId: 'local-nested-attrs',
1232
+ name: 'test/nested-rich-text',
1233
+ innerBlocks: [],
1234
+ },
1235
+ ];
1236
+
1178
1237
  test.each( NESTED_SELECTION_SEEDS )(
1179
1238
  'resolves fuzzed nested rich-text cursor (seed %i)',
1180
1239
  ( seed ) => {
@@ -1185,15 +1244,6 @@ describe( 'PostEditorAwareness', () => {
1185
1244
  );
1186
1245
  const nestedDoc = createTestDocWithBlocks( [ block ] );
1187
1246
 
1188
- mockBlockEditorStore( {
1189
- blocks: [
1190
- {
1191
- clientId: 'local-nested-attrs',
1192
- innerBlocks: [],
1193
- },
1194
- ],
1195
- } );
1196
-
1197
1247
  const target = rng.pick( targets );
1198
1248
  const initialOffset = rng.intBetween(
1199
1249
  1,
@@ -1233,8 +1283,10 @@ describe( 'PostEditorAwareness', () => {
1233
1283
  },
1234
1284
  };
1235
1285
 
1236
- const result =
1237
- awareness.convertSelectionStateToAbsolute( selection );
1286
+ const result = awareness.convertSelectionStateToAbsolute(
1287
+ selection,
1288
+ editorBlocks
1289
+ );
1238
1290
 
1239
1291
  expect( result.richTextOffset ).toBe( expectedOffset );
1240
1292
  expect( result.localClientId ).toBe( 'local-nested-attrs' );
@@ -1244,9 +1296,8 @@ describe( 'PostEditorAwareness', () => {
1244
1296
  );
1245
1297
  } );
1246
1298
 
1247
- describe( 'template mode (core/post-content handling)', () => {
1248
- test( 'should resolve cursor when getBlocks returns template tree with core/post-content', () => {
1249
- // Yjs doc has only the post content blocks (no template wrapper)
1299
+ describe( 'post content blocks resolution', () => {
1300
+ test( 'should resolve cursor with post content blocks', () => {
1250
1301
  const templateDoc = createTestDocWithBlocks( [
1251
1302
  createYBlock( 'yjs-para-0', 'core/paragraph', {
1252
1303
  textContent: 'Post paragraph 1',
@@ -1256,55 +1307,20 @@ describe( 'PostEditorAwareness', () => {
1256
1307
  } ),
1257
1308
  ] );
1258
1309
 
1259
- // In template mode, getBlocks() returns the full template tree.
1260
- // The Yjs paths are relative to post content, so the receiver needs
1261
- // to find core/post-content and navigate from there.
1262
- const postContentClientId = 'local-post-content';
1263
- const mockGetBlocks = jest
1264
- .fn()
1265
- .mockImplementation( ( rootClientId?: string ) => {
1266
- if ( rootClientId === postContentClientId ) {
1267
- // Controlled inner blocks of core/post-content
1268
- return [
1269
- {
1270
- clientId: 'local-para-0',
1271
- name: 'core/paragraph',
1272
- innerBlocks: [],
1273
- },
1274
- {
1275
- clientId: 'local-para-1',
1276
- name: 'core/paragraph',
1277
- innerBlocks: [],
1278
- },
1279
- ];
1280
- }
1281
- // Full template tree
1282
- return [
1283
- {
1284
- clientId: 'local-header',
1285
- name: 'core/template-part',
1286
- innerBlocks: [],
1287
- },
1288
- {
1289
- clientId: 'local-group',
1290
- name: 'core/group',
1291
- innerBlocks: [
1292
- {
1293
- clientId: postContentClientId,
1294
- name: 'core/post-content',
1295
- innerBlocks: [], // empty because they're controlled inner blocks
1296
- },
1297
- ],
1298
- },
1299
- {
1300
- clientId: 'local-footer',
1301
- name: 'core/template-part',
1302
- innerBlocks: [],
1303
- },
1304
- ];
1305
- } );
1306
-
1307
- mockBlockEditorStore( { getBlocks: mockGetBlocks } );
1310
+ // The caller provides post content blocks directly
1311
+ // (template detection is handled by usePostContentBlocks).
1312
+ const postContentBlocks = [
1313
+ {
1314
+ clientId: 'local-para-0',
1315
+ name: 'core/paragraph',
1316
+ innerBlocks: [],
1317
+ },
1318
+ {
1319
+ clientId: 'local-para-1',
1320
+ name: 'core/paragraph',
1321
+ innerBlocks: [],
1322
+ },
1323
+ ];
1308
1324
 
1309
1325
  const awareness = new PostEditorAwareness(
1310
1326
  templateDoc,
@@ -1335,55 +1351,29 @@ describe( 'PostEditorAwareness', () => {
1335
1351
  },
1336
1352
  };
1337
1353
 
1338
- const result =
1339
- awareness.convertSelectionStateToAbsolute( selection );
1354
+ const result = awareness.convertSelectionStateToAbsolute(
1355
+ selection,
1356
+ postContentBlocks
1357
+ );
1340
1358
 
1341
1359
  expect( result.richTextOffset ).toBe( 4 );
1342
- // Should resolve to the post-content inner block, not a template block
1343
1360
  expect( result.localClientId ).toBe( 'local-para-1' );
1344
- // Verify getBlocks was called with the post-content clientId
1345
- expect( mockGetBlocks ).toHaveBeenCalledWith( postContentClientId );
1346
1361
 
1347
1362
  templateDoc.destroy();
1348
1363
  } );
1349
1364
 
1350
- test( 'should resolve WholeBlock in template mode', () => {
1365
+ test( 'should resolve WholeBlock with post content blocks', () => {
1351
1366
  const templateDoc = createTestDocWithBlocks( [
1352
1367
  createYBlock( 'yjs-img', 'core/image' ),
1353
1368
  ] );
1354
1369
 
1355
- const postContentClientId = 'local-post-content';
1356
- const mockGetBlocks = jest
1357
- .fn()
1358
- .mockImplementation( ( rootClientId?: string ) => {
1359
- if ( rootClientId === postContentClientId ) {
1360
- return [
1361
- {
1362
- clientId: 'local-img',
1363
- name: 'core/image',
1364
- innerBlocks: [],
1365
- },
1366
- ];
1367
- }
1368
- return [
1369
- {
1370
- clientId: 'local-group',
1371
- name: 'core/group',
1372
- innerBlocks: [
1373
- {
1374
- clientId: postContentClientId,
1375
- name: 'core/post-content',
1376
- innerBlocks: [],
1377
- },
1378
- ],
1379
- },
1380
- ];
1381
- } );
1382
-
1383
- mockBlockEditorStore( {
1384
- getBlocks: mockGetBlocks,
1385
- getBlockName: 'core/image',
1386
- } );
1370
+ const postContentBlocks = [
1371
+ {
1372
+ clientId: 'local-img',
1373
+ name: 'core/image',
1374
+ innerBlocks: [],
1375
+ },
1376
+ ];
1387
1377
 
1388
1378
  const awareness = new PostEditorAwareness(
1389
1379
  templateDoc,
@@ -1407,8 +1397,10 @@ describe( 'PostEditorAwareness', () => {
1407
1397
  blockPosition,
1408
1398
  };
1409
1399
 
1410
- const result =
1411
- awareness.convertSelectionStateToAbsolute( selection );
1400
+ const result = awareness.convertSelectionStateToAbsolute(
1401
+ selection,
1402
+ postContentBlocks
1403
+ );
1412
1404
 
1413
1405
  expect( result.richTextOffset ).toBeNull();
1414
1406
  expect( result.localClientId ).toBe( 'local-img' );
@@ -1416,17 +1408,20 @@ describe( 'PostEditorAwareness', () => {
1416
1408
  templateDoc.destroy();
1417
1409
  } );
1418
1410
 
1419
- test( 'should fall back to root blocks when no core/post-content exists', () => {
1420
- // Normal mode (no template) — should use root blocks directly
1411
+ test( 'should resolve with root blocks directly', () => {
1421
1412
  const normalDoc = createTestDocWithBlocks( [
1422
1413
  createYBlock( 'yjs-para', 'core/paragraph', {
1423
1414
  textContent: 'Normal mode',
1424
1415
  } ),
1425
1416
  ] );
1426
1417
 
1427
- mockBlockEditorStore( {
1428
- blocks: [ { clientId: 'local-para', innerBlocks: [] } ],
1429
- } );
1418
+ const editorBlocks = [
1419
+ {
1420
+ clientId: 'local-para',
1421
+ name: 'core/paragraph',
1422
+ innerBlocks: [],
1423
+ },
1424
+ ];
1430
1425
 
1431
1426
  const awareness = new PostEditorAwareness(
1432
1427
  normalDoc,
@@ -1456,8 +1451,10 @@ describe( 'PostEditorAwareness', () => {
1456
1451
  },
1457
1452
  };
1458
1453
 
1459
- const result =
1460
- awareness.convertSelectionStateToAbsolute( selection );
1454
+ const result = awareness.convertSelectionStateToAbsolute(
1455
+ selection,
1456
+ editorBlocks
1457
+ );
1461
1458
 
1462
1459
  expect( result.richTextOffset ).toBe( 3 );
1463
1460
  expect( result.localClientId ).toBe( 'local-para' );
package/src/entities.js CHANGED
@@ -44,7 +44,7 @@ export const rootEntitiesConfig = [
44
44
  baseURL: '/',
45
45
  baseURLParams: {
46
46
  // Please also change the preload path when changing this.
47
- // @see lib/compat/wordpress-7.0/preload.php
47
+ // @see lib/compat/wordpress-7.1/preload.php
48
48
  _fields: [
49
49
  'description',
50
50
  'gmt_offset',
@@ -306,11 +306,14 @@ export const prePersistPostType = async (
306
306
  }
307
307
  }
308
308
 
309
- // Add meta for persisted CRDT document.
309
+ // Add meta for the persisted CRDT document during real post saves so the
310
+ // saved post and CRDT snapshot are committed in the same request. We don't
311
+ // want a post save to fail but a CRDT update to succeed or vice versa.
312
+ // CRDT repair uses /wp-sync/v1/save to avoid post-save side effects.
310
313
  if ( persistedRecord ) {
311
314
  const objectType = `postType/${ name }`;
312
315
  const objectId = persistedRecord.id;
313
- const serializedDoc = await getSyncManager()?.createPersistedCRDTDoc(
316
+ const serializedDoc = getSyncManager()?.createPersistedCRDTDoc(
314
317
  objectType,
315
318
  objectId
316
319
  );
@@ -406,6 +409,9 @@ async function loadPostTypeEntities() {
406
409
  * @type {import('@wordpress/sync').SyncConfig}
407
410
  */
408
411
  entity.syncConfig = {
412
+ // Save a CRDT document with this entity
413
+ supportsPersistence: true,
414
+
409
415
  /**
410
416
  * Apply changes from the local editor to the local CRDT document so
411
417
  * that those changes can be synced to other peers (via the provider).
@@ -458,6 +464,11 @@ async function loadPostTypeEntities() {
458
464
  null
459
465
  );
460
466
  },
467
+ shouldSync: () =>
468
+ ! (
469
+ Array.isArray( window._wpCollaborationDisabledPostTypes ) &&
470
+ window._wpCollaborationDisabledPostTypes.includes( name )
471
+ ),
461
472
  };
462
473
 
463
474
  return entity;
@@ -49,7 +49,7 @@ describe( 'useEntityRecord', () => {
49
49
  editedRecord: false,
50
50
  hasEdits: false,
51
51
  edits: {},
52
- record: undefined,
52
+ record: null,
53
53
  save: expect.any( Function ),
54
54
  hasResolved: false,
55
55
  hasStarted: false,
@@ -158,6 +158,10 @@ describe( 'useEntityRecord', () => {
158
158
  edits: {},
159
159
  record: null,
160
160
  save: expect.any( Function ),
161
+ hasResolved: false,
162
+ hasStarted: false,
163
+ isResolving: false,
164
+ status: 'IDLE',
161
165
  } );
162
166
 
163
167
  // The same delay.
@@ -28,6 +28,14 @@ jest.mock( '../../sync', () => ( {
28
28
  getSyncManager: jest.fn(),
29
29
  } ) );
30
30
 
31
+ const mockPostContentBlocks = [
32
+ { clientId: 'block-1', name: 'core/paragraph', innerBlocks: [] },
33
+ ];
34
+
35
+ jest.mock( '../../awareness/block-lookup', () => ( {
36
+ usePostContentBlocks: jest.fn( () => mockPostContentBlocks ),
37
+ } ) );
38
+
31
39
  const mockAvatarUrls = {
32
40
  '24': 'https://example.com/avatar-24.png',
33
41
  '48': 'https://example.com/avatar-48.png',
@@ -299,7 +307,7 @@ describe( 'use-post-editor-awareness-state hooks', () => {
299
307
  } );
300
308
  } );
301
309
 
302
- test( 'should call awareness.convertSelectionStateToAbsolute with selection', () => {
310
+ test( 'should call awareness.convertSelectionStateToAbsolute with selection and blocks', () => {
303
311
  const mockSelection: SelectionCursor = {
304
312
  type: SelectionType.Cursor,
305
313
  cursorPosition: {
@@ -321,7 +329,7 @@ describe( 'use-post-editor-awareness-state hooks', () => {
321
329
 
322
330
  expect(
323
331
  mockAwareness.convertSelectionStateToAbsolute
324
- ).toHaveBeenCalledWith( mockSelection );
332
+ ).toHaveBeenCalledWith( mockSelection, mockPostContentBlocks );
325
333
  expect( position ).toEqual( {
326
334
  richTextOffset: 10,
327
335
  localClientId: 'block-1',