@wordpress/core-data 7.32.0 → 7.32.1-next.ff1cebbba.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 (383) hide show
  1. package/build/actions.js +375 -632
  2. package/build/actions.js.map +7 -1
  3. package/build/batch/create-batch.js +49 -62
  4. package/build/batch/create-batch.js.map +7 -1
  5. package/build/batch/default-processor.js +43 -39
  6. package/build/batch/default-processor.js.map +7 -1
  7. package/build/batch/index.js +38 -17
  8. package/build/batch/index.js.map +7 -1
  9. package/build/dynamic-entities.js +30 -32
  10. package/build/dynamic-entities.js.map +7 -1
  11. package/build/entities.js +298 -410
  12. package/build/entities.js.map +7 -1
  13. package/build/entity-context.js +29 -12
  14. package/build/entity-context.js.map +7 -1
  15. package/build/entity-provider.js +38 -46
  16. package/build/entity-provider.js.map +7 -1
  17. package/build/entity-types/attachment.js +16 -5
  18. package/build/entity-types/attachment.js.map +7 -1
  19. package/build/entity-types/base-entity-records.js +16 -42
  20. package/build/entity-types/base-entity-records.js.map +7 -1
  21. package/build/entity-types/base.js +16 -5
  22. package/build/entity-types/base.js.map +7 -1
  23. package/build/entity-types/comment.js +16 -5
  24. package/build/entity-types/comment.js.map +7 -1
  25. package/build/entity-types/global-styles-revision.js +16 -5
  26. package/build/entity-types/global-styles-revision.js.map +7 -1
  27. package/build/entity-types/helpers.js +16 -5
  28. package/build/entity-types/helpers.js.map +7 -1
  29. package/build/entity-types/index.js +16 -5
  30. package/build/entity-types/index.js.map +7 -1
  31. package/build/entity-types/menu-location.js +16 -5
  32. package/build/entity-types/menu-location.js.map +7 -1
  33. package/build/entity-types/nav-menu-item.js +16 -5
  34. package/build/entity-types/nav-menu-item.js.map +7 -1
  35. package/build/entity-types/nav-menu.js +16 -5
  36. package/build/entity-types/nav-menu.js.map +7 -1
  37. package/build/entity-types/page.js +16 -5
  38. package/build/entity-types/page.js.map +7 -1
  39. package/build/entity-types/plugin.js +16 -5
  40. package/build/entity-types/plugin.js.map +7 -1
  41. package/build/entity-types/post-revision.js +16 -5
  42. package/build/entity-types/post-revision.js.map +7 -1
  43. package/build/entity-types/post-status.js +16 -5
  44. package/build/entity-types/post-status.js.map +7 -1
  45. package/build/entity-types/post.js +16 -5
  46. package/build/entity-types/post.js.map +7 -1
  47. package/build/entity-types/settings.js +16 -5
  48. package/build/entity-types/settings.js.map +7 -1
  49. package/build/entity-types/sidebar.js +16 -5
  50. package/build/entity-types/sidebar.js.map +7 -1
  51. package/build/entity-types/taxonomy.js +16 -5
  52. package/build/entity-types/taxonomy.js.map +7 -1
  53. package/build/entity-types/term.js +16 -5
  54. package/build/entity-types/term.js.map +7 -1
  55. package/build/entity-types/theme.js +16 -5
  56. package/build/entity-types/theme.js.map +7 -1
  57. package/build/entity-types/type.js +16 -5
  58. package/build/entity-types/type.js.map +7 -1
  59. package/build/entity-types/user.js +16 -5
  60. package/build/entity-types/user.js.map +7 -1
  61. package/build/entity-types/widget-type.js +16 -5
  62. package/build/entity-types/widget-type.js.map +7 -1
  63. package/build/entity-types/widget.js +16 -5
  64. package/build/entity-types/widget.js.map +7 -1
  65. package/build/entity-types/wp-template-part.js +16 -5
  66. package/build/entity-types/wp-template-part.js.map +7 -1
  67. package/build/entity-types/wp-template.js +16 -5
  68. package/build/entity-types/wp-template.js.map +7 -1
  69. package/build/fetch/__experimental-fetch-link-suggestions.js +141 -154
  70. package/build/fetch/__experimental-fetch-link-suggestions.js.map +7 -1
  71. package/build/fetch/__experimental-fetch-url-data.js +47 -59
  72. package/build/fetch/__experimental-fetch-url-data.js.map +7 -1
  73. package/build/fetch/index.js +53 -32
  74. package/build/fetch/index.js.map +7 -1
  75. package/build/footnotes/get-footnotes-order.js +38 -25
  76. package/build/footnotes/get-footnotes-order.js.map +7 -1
  77. package/build/footnotes/get-rich-text-values-cached.js +26 -27
  78. package/build/footnotes/get-rich-text-values-cached.js.map +7 -1
  79. package/build/footnotes/index.js +68 -55
  80. package/build/footnotes/index.js.map +7 -1
  81. package/build/hooks/constants.js +33 -12
  82. package/build/hooks/constants.js.map +7 -1
  83. package/build/hooks/index.js +59 -68
  84. package/build/hooks/index.js.map +7 -1
  85. package/build/hooks/memoize.js +34 -12
  86. package/build/hooks/memoize.js.map +7 -1
  87. package/build/hooks/use-entity-block-editor.js +111 -119
  88. package/build/hooks/use-entity-block-editor.js.map +7 -1
  89. package/build/hooks/use-entity-id.js +25 -23
  90. package/build/hooks/use-entity-id.js.map +7 -1
  91. package/build/hooks/use-entity-prop.js +59 -60
  92. package/build/hooks/use-entity-prop.js.map +7 -1
  93. package/build/hooks/use-entity-record.js +95 -155
  94. package/build/hooks/use-entity-record.js.map +7 -1
  95. package/build/hooks/use-entity-records.js +131 -139
  96. package/build/hooks/use-entity-records.js.map +7 -1
  97. package/build/hooks/use-query-select.js +65 -84
  98. package/build/hooks/use-query-select.js.map +7 -1
  99. package/build/hooks/use-resource-permissions.js +92 -145
  100. package/build/hooks/use-resource-permissions.js.map +7 -1
  101. package/build/index.js +96 -153
  102. package/build/index.js.map +7 -1
  103. package/build/lock-unlock.js +31 -14
  104. package/build/lock-unlock.js.map +7 -1
  105. package/build/locks/actions.js +36 -19
  106. package/build/locks/actions.js.map +7 -1
  107. package/build/locks/engine.js +48 -47
  108. package/build/locks/engine.js.map +7 -1
  109. package/build/locks/reducer.js +54 -63
  110. package/build/locks/reducer.js.map +7 -1
  111. package/build/locks/selectors.js +35 -30
  112. package/build/locks/selectors.js.map +7 -1
  113. package/build/locks/utils.js +37 -16
  114. package/build/locks/utils.js.map +7 -1
  115. package/build/name.js +27 -12
  116. package/build/name.js.map +7 -1
  117. package/build/private-actions.js +67 -75
  118. package/build/private-actions.js.map +7 -1
  119. package/build/private-apis.js +33 -16
  120. package/build/private-apis.js.map +7 -1
  121. package/build/private-selectors.js +204 -184
  122. package/build/private-selectors.js.map +7 -1
  123. package/build/queried-data/actions.js +32 -41
  124. package/build/queried-data/actions.js.map +7 -1
  125. package/build/queried-data/get-query-parts.js +41 -79
  126. package/build/queried-data/get-query-parts.js.map +7 -1
  127. package/build/queried-data/index.js +39 -36
  128. package/build/queried-data/index.js.map +7 -1
  129. package/build/queried-data/reducer.js +162 -193
  130. package/build/queried-data/reducer.js.map +7 -1
  131. package/build/queried-data/selectors.js +57 -85
  132. package/build/queried-data/selectors.js.map +7 -1
  133. package/build/reducer.js +279 -404
  134. package/build/reducer.js.map +7 -1
  135. package/build/resolvers.js +553 -600
  136. package/build/resolvers.js.map +7 -1
  137. package/build/selectors.js +456 -981
  138. package/build/selectors.js.map +7 -1
  139. package/build/sync.js +34 -22
  140. package/build/sync.js.map +7 -1
  141. package/build/types.js +16 -5
  142. package/build/types.js.map +7 -1
  143. package/build/utils/conservative-map-item.js +34 -27
  144. package/build/utils/conservative-map-item.js.map +7 -1
  145. package/build/utils/crdt-blocks.js +289 -0
  146. package/build/utils/crdt-blocks.js.map +7 -0
  147. package/build/utils/crdt.js +202 -0
  148. package/build/utils/crdt.js.map +7 -0
  149. package/build/utils/forward-resolver.js +24 -16
  150. package/build/utils/forward-resolver.js.map +7 -1
  151. package/build/utils/get-nested-value.js +26 -21
  152. package/build/utils/get-nested-value.js.map +7 -1
  153. package/build/utils/get-normalized-comma-separable.js +25 -17
  154. package/build/utils/get-normalized-comma-separable.js.map +7 -1
  155. package/build/utils/if-matching-action.js +25 -19
  156. package/build/utils/if-matching-action.js.map +7 -1
  157. package/build/utils/index.js +77 -108
  158. package/build/utils/index.js.map +7 -1
  159. package/build/utils/is-numeric-id.js +22 -12
  160. package/build/utils/is-numeric-id.js.map +7 -1
  161. package/build/utils/is-raw-attribute.js +22 -13
  162. package/build/utils/is-raw-attribute.js.map +7 -1
  163. package/build/utils/log-entity-deprecation.js +37 -38
  164. package/build/utils/log-entity-deprecation.js.map +7 -1
  165. package/build/utils/on-sub-key.js +30 -24
  166. package/build/utils/on-sub-key.js.map +7 -1
  167. package/build/utils/receive-intermediate-results.js +29 -6
  168. package/build/utils/receive-intermediate-results.js.map +7 -1
  169. package/build/utils/replace-action.js +24 -17
  170. package/build/utils/replace-action.js.map +7 -1
  171. package/build/utils/set-nested-value.js +25 -30
  172. package/build/utils/set-nested-value.js.map +7 -1
  173. package/build/utils/user-permissions.js +41 -13
  174. package/build/utils/user-permissions.js.map +7 -1
  175. package/build/utils/with-weak-map-cache.js +26 -22
  176. package/build/utils/with-weak-map-cache.js.map +7 -1
  177. package/build-module/actions.js +322 -601
  178. package/build-module/actions.js.map +7 -1
  179. package/build-module/batch/create-batch.js +21 -57
  180. package/build-module/batch/create-batch.js.map +7 -1
  181. package/build-module/batch/default-processor.js +14 -33
  182. package/build-module/batch/default-processor.js.map +7 -1
  183. package/build-module/batch/index.js +7 -3
  184. package/build-module/batch/index.js.map +7 -1
  185. package/build-module/dynamic-entities.js +7 -28
  186. package/build-module/dynamic-entities.js.map +7 -1
  187. package/build-module/entities.js +263 -399
  188. package/build-module/entities.js.map +7 -1
  189. package/build-module/entity-context.js +7 -7
  190. package/build-module/entity-context.js.map +7 -1
  191. package/build-module/entity-provider.js +19 -42
  192. package/build-module/entity-provider.js.map +7 -1
  193. package/build-module/entity-types/attachment.js +1 -2
  194. package/build-module/entity-types/attachment.js.map +7 -1
  195. package/build-module/entity-types/base-entity-records.js +1 -37
  196. package/build-module/entity-types/base-entity-records.js.map +7 -1
  197. package/build-module/entity-types/base.js +1 -2
  198. package/build-module/entity-types/base.js.map +7 -1
  199. package/build-module/entity-types/comment.js +1 -2
  200. package/build-module/entity-types/comment.js.map +7 -1
  201. package/build-module/entity-types/global-styles-revision.js +1 -2
  202. package/build-module/entity-types/global-styles-revision.js.map +7 -1
  203. package/build-module/entity-types/helpers.js +1 -2
  204. package/build-module/entity-types/helpers.js.map +7 -1
  205. package/build-module/entity-types/index.js +1 -2
  206. package/build-module/entity-types/index.js.map +7 -1
  207. package/build-module/entity-types/menu-location.js +1 -2
  208. package/build-module/entity-types/menu-location.js.map +7 -1
  209. package/build-module/entity-types/nav-menu-item.js +1 -2
  210. package/build-module/entity-types/nav-menu-item.js.map +7 -1
  211. package/build-module/entity-types/nav-menu.js +1 -2
  212. package/build-module/entity-types/nav-menu.js.map +7 -1
  213. package/build-module/entity-types/page.js +1 -2
  214. package/build-module/entity-types/page.js.map +7 -1
  215. package/build-module/entity-types/plugin.js +1 -2
  216. package/build-module/entity-types/plugin.js.map +7 -1
  217. package/build-module/entity-types/post-revision.js +1 -2
  218. package/build-module/entity-types/post-revision.js.map +7 -1
  219. package/build-module/entity-types/post-status.js +1 -2
  220. package/build-module/entity-types/post-status.js.map +7 -1
  221. package/build-module/entity-types/post.js +1 -2
  222. package/build-module/entity-types/post.js.map +7 -1
  223. package/build-module/entity-types/settings.js +1 -2
  224. package/build-module/entity-types/settings.js.map +7 -1
  225. package/build-module/entity-types/sidebar.js +1 -2
  226. package/build-module/entity-types/sidebar.js.map +7 -1
  227. package/build-module/entity-types/taxonomy.js +1 -2
  228. package/build-module/entity-types/taxonomy.js.map +7 -1
  229. package/build-module/entity-types/term.js +1 -2
  230. package/build-module/entity-types/term.js.map +7 -1
  231. package/build-module/entity-types/theme.js +1 -2
  232. package/build-module/entity-types/theme.js.map +7 -1
  233. package/build-module/entity-types/type.js +1 -2
  234. package/build-module/entity-types/type.js.map +7 -1
  235. package/build-module/entity-types/user.js +1 -2
  236. package/build-module/entity-types/user.js.map +7 -1
  237. package/build-module/entity-types/widget-type.js +1 -2
  238. package/build-module/entity-types/widget-type.js.map +7 -1
  239. package/build-module/entity-types/widget.js +1 -2
  240. package/build-module/entity-types/widget.js.map +7 -1
  241. package/build-module/entity-types/wp-template-part.js +1 -2
  242. package/build-module/entity-types/wp-template-part.js.map +7 -1
  243. package/build-module/entity-types/wp-template.js +1 -2
  244. package/build-module/entity-types/wp-template.js.map +7 -1
  245. package/build-module/fetch/__experimental-fetch-link-suggestions.js +111 -149
  246. package/build-module/fetch/__experimental-fetch-link-suggestions.js.map +7 -1
  247. package/build-module/fetch/__experimental-fetch-url-data.js +20 -49
  248. package/build-module/fetch/__experimental-fetch-url-data.js.map +7 -1
  249. package/build-module/fetch/index.js +20 -15
  250. package/build-module/fetch/index.js.map +7 -1
  251. package/build-module/footnotes/get-footnotes-order.js +10 -19
  252. package/build-module/footnotes/get-footnotes-order.js.map +7 -1
  253. package/build-module/footnotes/get-rich-text-values-cached.js +8 -23
  254. package/build-module/footnotes/get-rich-text-values-cached.js.map +7 -1
  255. package/build-module/footnotes/index.js +34 -47
  256. package/build-module/footnotes/index.js.map +7 -1
  257. package/build-module/hooks/constants.js +11 -8
  258. package/build-module/hooks/constants.js.map +7 -1
  259. package/build-module/hooks/index.js +27 -15
  260. package/build-module/hooks/index.js.map +7 -1
  261. package/build-module/hooks/memoize.js +6 -8
  262. package/build-module/hooks/memoize.js.map +7 -1
  263. package/build-module/hooks/use-entity-block-editor.js +80 -110
  264. package/build-module/hooks/use-entity-block-editor.js.map +7 -1
  265. package/build-module/hooks/use-entity-id.js +7 -19
  266. package/build-module/hooks/use-entity-id.js.map +7 -1
  267. package/build-module/hooks/use-entity-prop.js +31 -55
  268. package/build-module/hooks/use-entity-prop.js.map +7 -1
  269. package/build-module/hooks/use-entity-record.js +63 -148
  270. package/build-module/hooks/use-entity-record.js.map +7 -1
  271. package/build-module/hooks/use-entity-records.js +98 -131
  272. package/build-module/hooks/use-entity-records.js.map +7 -1
  273. package/build-module/hooks/use-query-select.js +27 -71
  274. package/build-module/hooks/use-query-select.js.map +7 -1
  275. package/build-module/hooks/use-resource-permissions.js +57 -136
  276. package/build-module/hooks/use-resource-permissions.js.map +7 -1
  277. package/build-module/index.js +49 -71
  278. package/build-module/index.js.map +7 -1
  279. package/build-module/lock-unlock.js +8 -7
  280. package/build-module/lock-unlock.js.map +7 -1
  281. package/build-module/locks/actions.js +8 -13
  282. package/build-module/locks/actions.js.map +7 -1
  283. package/build-module/locks/engine.js +17 -38
  284. package/build-module/locks/engine.js.map +7 -1
  285. package/build-module/locks/reducer.js +37 -59
  286. package/build-module/locks/reducer.js.map +7 -1
  287. package/build-module/locks/selectors.js +16 -23
  288. package/build-module/locks/selectors.js.map +7 -1
  289. package/build-module/locks/utils.js +15 -12
  290. package/build-module/locks/utils.js.map +7 -1
  291. package/build-module/name.js +5 -8
  292. package/build-module/name.js.map +7 -1
  293. package/build-module/private-actions.js +35 -69
  294. package/build-module/private-actions.js.map +7 -1
  295. package/build-module/private-apis.js +8 -8
  296. package/build-module/private-apis.js.map +7 -1
  297. package/build-module/private-selectors.js +167 -174
  298. package/build-module/private-selectors.js.map +7 -1
  299. package/build-module/queried-data/actions.js +11 -38
  300. package/build-module/queried-data/actions.js.map +7 -1
  301. package/build-module/queried-data/get-query-parts.js +20 -75
  302. package/build-module/queried-data/get-query-parts.js.map +7 -1
  303. package/build-module/queried-data/index.js +7 -4
  304. package/build-module/queried-data/index.js.map +7 -1
  305. package/build-module/queried-data/reducer.js +134 -185
  306. package/build-module/queried-data/reducer.js.map +7 -1
  307. package/build-module/queried-data/selectors.js +23 -78
  308. package/build-module/queried-data/selectors.js.map +7 -1
  309. package/build-module/reducer.js +243 -393
  310. package/build-module/reducer.js.map +7 -1
  311. package/build-module/resolvers.js +478 -549
  312. package/build-module/resolvers.js.map +7 -1
  313. package/build-module/selectors.js +410 -953
  314. package/build-module/selectors.js.map +7 -1
  315. package/build-module/sync.js +14 -17
  316. package/build-module/sync.js.map +7 -1
  317. package/build-module/types.js +1 -2
  318. package/build-module/types.js.map +7 -1
  319. package/build-module/utils/conservative-map-item.js +6 -22
  320. package/build-module/utils/conservative-map-item.js.map +7 -1
  321. package/build-module/utils/crdt-blocks.js +255 -0
  322. package/build-module/utils/crdt-blocks.js.map +7 -0
  323. package/build-module/utils/crdt.js +167 -0
  324. package/build-module/utils/crdt.js.map +7 -0
  325. package/build-module/utils/forward-resolver.js +6 -12
  326. package/build-module/utils/forward-resolver.js.map +7 -1
  327. package/build-module/utils/get-nested-value.js +9 -18
  328. package/build-module/utils/get-nested-value.js.map +7 -1
  329. package/build-module/utils/get-normalized-comma-separable.js +7 -13
  330. package/build-module/utils/get-normalized-comma-separable.js.map +7 -1
  331. package/build-module/utils/if-matching-action.js +7 -15
  332. package/build-module/utils/if-matching-action.js.map +7 -1
  333. package/build-module/utils/index.js +35 -14
  334. package/build-module/utils/index.js.map +7 -1
  335. package/build-module/utils/is-numeric-id.js +5 -9
  336. package/build-module/utils/is-numeric-id.js.map +7 -1
  337. package/build-module/utils/is-raw-attribute.js +5 -10
  338. package/build-module/utils/is-raw-attribute.js.map +7 -1
  339. package/build-module/utils/log-entity-deprecation.js +8 -31
  340. package/build-module/utils/log-entity-deprecation.js.map +7 -1
  341. package/build-module/utils/on-sub-key.js +8 -19
  342. package/build-module/utils/on-sub-key.js.map +7 -1
  343. package/build-module/utils/receive-intermediate-results.js +7 -2
  344. package/build-module/utils/receive-intermediate-results.js.map +7 -1
  345. package/build-module/utils/replace-action.js +6 -13
  346. package/build-module/utils/replace-action.js.map +7 -1
  347. package/build-module/utils/set-nested-value.js +8 -27
  348. package/build-module/utils/set-nested-value.js.map +7 -1
  349. package/build-module/utils/user-permissions.js +19 -9
  350. package/build-module/utils/user-permissions.js.map +7 -1
  351. package/build-module/utils/with-weak-map-cache.js +8 -18
  352. package/build-module/utils/with-weak-map-cache.js.map +7 -1
  353. package/build-types/actions.d.ts.map +1 -1
  354. package/build-types/entities.d.ts +0 -56
  355. package/build-types/entities.d.ts.map +1 -1
  356. package/build-types/index.d.ts.map +1 -1
  357. package/build-types/private-selectors.d.ts.map +1 -1
  358. package/build-types/resolvers.d.ts +3 -0
  359. package/build-types/resolvers.d.ts.map +1 -1
  360. package/build-types/selectors.d.ts.map +1 -1
  361. package/build-types/sync.d.ts +6 -1
  362. package/build-types/sync.d.ts.map +1 -1
  363. package/build-types/types.d.ts +9 -0
  364. package/build-types/types.d.ts.map +1 -1
  365. package/build-types/utils/crdt-blocks.d.ts +30 -0
  366. package/build-types/utils/crdt-blocks.d.ts.map +1 -0
  367. package/build-types/utils/crdt.d.ts +49 -0
  368. package/build-types/utils/crdt.d.ts.map +1 -0
  369. package/package.json +26 -19
  370. package/src/actions.js +56 -74
  371. package/src/entities.js +59 -113
  372. package/src/private-selectors.ts +32 -7
  373. package/src/resolvers.js +173 -120
  374. package/src/selectors.ts +0 -13
  375. package/src/sync.ts +12 -0
  376. package/src/test/resolvers.js +183 -0
  377. package/src/types.ts +12 -0
  378. package/src/utils/crdt-blocks.ts +503 -0
  379. package/src/utils/crdt.ts +310 -0
  380. package/src/utils/test/crdt-blocks.ts +375 -0
  381. package/src/utils/test/crdt.ts +254 -0
  382. package/tsconfig.tsbuildinfo +1 -1
  383. package/src/sync.js +0 -27
@@ -0,0 +1,310 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import fastDeepEqual from 'fast-deep-equal/es6';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { type CRDTDoc, type ObjectData, Y } from '@wordpress/sync';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import {
15
+ mergeCrdtBlocks,
16
+ type Block,
17
+ type YBlock,
18
+ type YBlocks,
19
+ } from './crdt-blocks';
20
+ import { type Post } from '../entity-types/post';
21
+ import { type Type } from '../entity-types';
22
+ import { CRDT_RECORD_MAP_KEY } from '../sync';
23
+ import type { WPBlockSelection, WPSelection } from '../types';
24
+
25
+ export type PostChanges = Partial< Post > & {
26
+ blocks?: Block[];
27
+ excerpt?: Post[ 'excerpt' ] | string;
28
+ selection?: WPSelection;
29
+ title?: Post[ 'title' ] | string;
30
+ };
31
+
32
+ // Hold a reference to the last known selection to help compute Y.Text deltas.
33
+ let lastSelection: WPBlockSelection | null = null;
34
+
35
+ // Properties that are allowed to be synced for a post.
36
+ const allowedPostProperties = new Set< string >( [
37
+ 'author',
38
+ 'blocks',
39
+ 'comment_status',
40
+ 'date',
41
+ 'excerpt',
42
+ 'featured_media',
43
+ 'format',
44
+ 'ping_status',
45
+ 'slug',
46
+ 'status',
47
+ 'sticky',
48
+ 'tags',
49
+ 'template',
50
+ 'title',
51
+ ] );
52
+
53
+ /**
54
+ * Given a set of local changes to a generic entity record, apply those changes
55
+ * to the local Y.Doc.
56
+ *
57
+ * @param {CRDTDoc} ydoc
58
+ * @param {Partial< ObjectData >} changes
59
+ * @return {void}
60
+ */
61
+ export function defaultApplyChangesToCRDTDoc(
62
+ ydoc: CRDTDoc,
63
+ changes: ObjectData
64
+ ): void {
65
+ const ymap = ydoc.getMap( CRDT_RECORD_MAP_KEY );
66
+
67
+ Object.entries( changes ).forEach( ( [ key, newValue ] ) => {
68
+ // Cannot serialize function values, so cannot sync them.
69
+ if ( 'function' === typeof newValue ) {
70
+ return;
71
+ }
72
+
73
+ // Set the value in the root document.
74
+ function setValue< T = unknown >( updatedValue: T ): void {
75
+ ymap.set( key, updatedValue );
76
+ }
77
+
78
+ switch ( key ) {
79
+ // Add support for additional data types here.
80
+
81
+ default: {
82
+ const currentValue = ymap.get( key );
83
+ mergeValue( currentValue, newValue, setValue );
84
+ }
85
+ }
86
+ } );
87
+ }
88
+
89
+ /**
90
+ * Given a set of local changes to a post record, apply those changes to the
91
+ * local Y.Doc.
92
+ *
93
+ * @param {CRDTDoc} ydoc
94
+ * @param {PostChanges} changes
95
+ * @param {Type} postType
96
+ * @return {void}
97
+ */
98
+ export function applyPostChangesToCRDTDoc(
99
+ ydoc: CRDTDoc,
100
+ changes: PostChanges,
101
+ postType: Type // eslint-disable-line @typescript-eslint/no-unused-vars
102
+ ): void {
103
+ const ymap = ydoc.getMap( CRDT_RECORD_MAP_KEY );
104
+
105
+ Object.entries( changes ).forEach( ( [ key, newValue ] ) => {
106
+ if ( ! allowedPostProperties.has( key ) ) {
107
+ return;
108
+ }
109
+
110
+ // Cannot serialize function values, so cannot sync them.
111
+ if ( 'function' === typeof newValue ) {
112
+ return;
113
+ }
114
+
115
+ // Set the value in the root document.
116
+ function setValue< T = unknown >( updatedValue: T ): void {
117
+ ymap.set( key, updatedValue );
118
+ }
119
+
120
+ switch ( key ) {
121
+ case 'blocks': {
122
+ let currentBlocks = ymap.get( 'blocks' ) as YBlocks;
123
+
124
+ // Initialize.
125
+ if ( ! ( currentBlocks instanceof Y.Array ) ) {
126
+ currentBlocks = new Y.Array< YBlock >();
127
+ setValue( currentBlocks );
128
+ }
129
+
130
+ // Block[] from local changes.
131
+ const newBlocks = ( newValue as PostChanges[ 'blocks' ] ) ?? [];
132
+
133
+ // Merge blocks does not need `setValue` because it is operating on a
134
+ // Yjs type that is already in the Y.Doc.
135
+ mergeCrdtBlocks( currentBlocks, newBlocks, lastSelection );
136
+ break;
137
+ }
138
+
139
+ case 'excerpt': {
140
+ const currentValue = ymap.get( 'excerpt' ) as
141
+ | string
142
+ | undefined;
143
+ const rawNewValue = getRawValue( newValue );
144
+
145
+ mergeValue( currentValue, rawNewValue, setValue );
146
+ break;
147
+ }
148
+
149
+ case 'slug': {
150
+ // Do not sync an empty slug. This indicates that the post is using
151
+ // the default auto-generated slug.
152
+ if ( ! newValue ) {
153
+ break;
154
+ }
155
+
156
+ const currentValue = ymap.get( 'slug' ) as string;
157
+ mergeValue( currentValue, newValue, setValue );
158
+ break;
159
+ }
160
+
161
+ case 'title': {
162
+ const currentValue = ymap.get( 'title' ) as string | undefined;
163
+
164
+ // Copy logic from prePersistPostType to ensure that the "Auto
165
+ // Draft" template title is not synced.
166
+ let rawNewValue = getRawValue( newValue );
167
+ if ( ! currentValue && 'Auto Draft' === rawNewValue ) {
168
+ rawNewValue = '';
169
+ }
170
+
171
+ mergeValue( currentValue, rawNewValue, setValue );
172
+ break;
173
+ }
174
+
175
+ // Add support for additional data types here.
176
+
177
+ default: {
178
+ const currentValue = ymap.get( key );
179
+ mergeValue( currentValue, newValue, setValue );
180
+ }
181
+ }
182
+ } );
183
+
184
+ // Update the lastSelection for use in computing Y.Text deltas.
185
+ if ( 'selection' in changes ) {
186
+ lastSelection = changes.selection?.selectionStart ?? null;
187
+ }
188
+ }
189
+
190
+ export function defaultGetChangesFromCRDTDoc( crdtDoc: CRDTDoc ): ObjectData {
191
+ return crdtDoc.getMap( CRDT_RECORD_MAP_KEY ).toJSON();
192
+ }
193
+
194
+ /**
195
+ * Given a local Y.Doc that *may* contain changes from remote peers, compare
196
+ * against the local record and determine if there are changes (edits) we want
197
+ * to dispatch.
198
+ *
199
+ * @param {CRDTDoc} ydoc
200
+ * @param {Post} editedRecord
201
+ * @param {Type} postType
202
+ * @return {Partial<PostChanges>} The changes that should be applied to the local record.
203
+ */
204
+ export function getPostChangesFromCRDTDoc(
205
+ ydoc: CRDTDoc,
206
+ editedRecord: Post,
207
+ postType: Type // eslint-disable-line @typescript-eslint/no-unused-vars
208
+ ): PostChanges {
209
+ const ymap = ydoc.getMap( CRDT_RECORD_MAP_KEY );
210
+
211
+ return Object.fromEntries(
212
+ Object.entries( ymap.toJSON() ).filter( ( [ key, newValue ] ) => {
213
+ if ( ! allowedPostProperties.has( key ) ) {
214
+ return false;
215
+ }
216
+
217
+ const currentValue = editedRecord[ key ];
218
+
219
+ switch ( key ) {
220
+ case 'blocks': {
221
+ // The consumers of blocks have memoization that renders optimization
222
+ // here unnecessary.
223
+ return true;
224
+ }
225
+
226
+ case 'date': {
227
+ // Do not sync an empty date if our current value is a "floating" date.
228
+ // Borrowing logic from the isEditedPostDateFloating selector.
229
+ const currentDateIsFloating =
230
+ [ 'draft', 'auto-draft', 'pending' ].includes(
231
+ ymap.get( 'status' ) as string
232
+ ) &&
233
+ ( null === currentValue ||
234
+ editedRecord.modified === currentValue );
235
+
236
+ if ( ! newValue && currentDateIsFloating ) {
237
+ return false;
238
+ }
239
+
240
+ return haveValuesChanged( currentValue, newValue );
241
+ }
242
+
243
+ case 'status': {
244
+ // Do not sync an invalid status.
245
+ if ( 'auto-draft' === newValue ) {
246
+ return false;
247
+ }
248
+
249
+ return haveValuesChanged( currentValue, newValue );
250
+ }
251
+
252
+ case 'excerpt':
253
+ case 'title': {
254
+ return haveValuesChanged(
255
+ getRawValue( currentValue ),
256
+ newValue
257
+ );
258
+ }
259
+
260
+ // Add support for additional data types here.
261
+
262
+ default: {
263
+ return haveValuesChanged( currentValue, newValue );
264
+ }
265
+ }
266
+ } )
267
+ );
268
+ }
269
+
270
+ /**
271
+ * Extract the raw string value from a property that may be a string or an object
272
+ * with a `raw` property (`RenderedText`).
273
+ *
274
+ * @param {unknown} value The value to extract from.
275
+ * @return {string|undefined} The raw string value, or undefined if it could not be determined.
276
+ */
277
+ function getRawValue( value?: unknown ): string | undefined {
278
+ // Value may be a string property or a nested object with a `raw` property.
279
+ if ( 'string' === typeof value ) {
280
+ return value;
281
+ }
282
+
283
+ if (
284
+ value &&
285
+ 'object' === typeof value &&
286
+ 'raw' in value &&
287
+ 'string' === typeof value.raw
288
+ ) {
289
+ return value.raw;
290
+ }
291
+
292
+ return undefined;
293
+ }
294
+
295
+ function haveValuesChanged< ValueType = any >(
296
+ currentValue: ValueType,
297
+ newValue: ValueType
298
+ ): boolean {
299
+ return ! fastDeepEqual( currentValue, newValue );
300
+ }
301
+
302
+ function mergeValue< ValueType = any >(
303
+ currentValue: ValueType,
304
+ newValue: ValueType,
305
+ setValue: ( value: ValueType ) => void
306
+ ): void {
307
+ if ( haveValuesChanged< ValueType >( currentValue, newValue ) ) {
308
+ setValue( newValue );
309
+ }
310
+ }
@@ -0,0 +1,375 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Y } from '@wordpress/sync';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import { describe, expect, it, jest, beforeEach } from '@jest/globals';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import {
15
+ mergeCrdtBlocks,
16
+ type Block,
17
+ type YBlock,
18
+ type YBlocks,
19
+ type YBlockAttributes,
20
+ } from '../crdt-blocks';
21
+
22
+ describe( 'crdt-blocks', () => {
23
+ let doc: Y.Doc;
24
+ let yblocks: Y.Array< YBlock >;
25
+
26
+ beforeEach( () => {
27
+ doc = new Y.Doc();
28
+ yblocks = doc.getArray< YBlock >();
29
+ jest.clearAllMocks();
30
+ } );
31
+
32
+ afterEach( () => {
33
+ doc.destroy();
34
+ } );
35
+
36
+ describe( 'mergeCrdtBlocks', () => {
37
+ it( 'inserts new blocks into empty Y.Array', () => {
38
+ const incomingBlocks: Block[] = [
39
+ {
40
+ name: 'core/paragraph',
41
+ attributes: { content: 'Hello World' },
42
+ innerBlocks: [],
43
+ },
44
+ ];
45
+
46
+ mergeCrdtBlocks( yblocks, incomingBlocks, null );
47
+
48
+ expect( yblocks.length ).toBe( 1 );
49
+ const block = yblocks.get( 0 );
50
+ expect( block.get( 'name' ) ).toBe( 'core/paragraph' );
51
+ const content = (
52
+ block.get( 'attributes' ) as YBlockAttributes
53
+ ).get( 'content' ) as Y.Text;
54
+ expect( content.toString() ).toBe( 'Hello World' );
55
+ } );
56
+
57
+ it( 'updates existing blocks when content changes', () => {
58
+ const initialBlocks: Block[] = [
59
+ {
60
+ name: 'core/paragraph',
61
+ attributes: { content: 'Initial content' },
62
+ innerBlocks: [],
63
+ clientId: 'block-1',
64
+ },
65
+ ];
66
+
67
+ mergeCrdtBlocks( yblocks, initialBlocks, null );
68
+
69
+ const updatedBlocks: Block[] = [
70
+ {
71
+ name: 'core/paragraph',
72
+ attributes: { content: 'Updated content' },
73
+ innerBlocks: [],
74
+ clientId: 'block-1',
75
+ },
76
+ ];
77
+
78
+ mergeCrdtBlocks( yblocks, updatedBlocks, null );
79
+
80
+ expect( yblocks.length ).toBe( 1 );
81
+ const block = yblocks.get( 0 );
82
+ const content = (
83
+ block.get( 'attributes' ) as YBlockAttributes
84
+ ).get( 'content' ) as Y.Text;
85
+ expect( content.toString() ).toBe( 'Updated content' );
86
+ } );
87
+
88
+ it( 'deletes blocks that are removed', () => {
89
+ const initialBlocks: Block[] = [
90
+ {
91
+ name: 'core/paragraph',
92
+ attributes: { content: 'Block 1' },
93
+ innerBlocks: [],
94
+ clientId: 'block-1',
95
+ },
96
+ {
97
+ name: 'core/paragraph',
98
+ attributes: { content: 'Block 2' },
99
+ innerBlocks: [],
100
+ clientId: 'block-2',
101
+ },
102
+ ];
103
+
104
+ mergeCrdtBlocks( yblocks, initialBlocks, null );
105
+ expect( yblocks.length ).toBe( 2 );
106
+
107
+ const updatedBlocks: Block[] = [
108
+ {
109
+ name: 'core/paragraph',
110
+ attributes: { content: 'Block 1' },
111
+ innerBlocks: [],
112
+ clientId: 'block-1',
113
+ },
114
+ ];
115
+
116
+ mergeCrdtBlocks( yblocks, updatedBlocks, null );
117
+
118
+ expect( yblocks.length ).toBe( 1 );
119
+ const block = yblocks.get( 0 );
120
+ const content = (
121
+ block.get( 'attributes' ) as YBlockAttributes
122
+ ).get( 'content' ) as Y.Text;
123
+ expect( content.toString() ).toBe( 'Block 1' );
124
+ } );
125
+
126
+ it( 'handles innerBlocks recursively', () => {
127
+ const blocksWithInner: Block[] = [
128
+ {
129
+ name: 'core/group',
130
+ attributes: {},
131
+ innerBlocks: [
132
+ {
133
+ name: 'core/paragraph',
134
+ attributes: { content: 'Inner paragraph' },
135
+ innerBlocks: [],
136
+ },
137
+ ],
138
+ },
139
+ ];
140
+
141
+ mergeCrdtBlocks( yblocks, blocksWithInner, null );
142
+
143
+ expect( yblocks.length ).toBe( 1 );
144
+ const block = yblocks.get( 0 );
145
+ const innerBlocks = block.get( 'innerBlocks' ) as YBlocks;
146
+ expect( innerBlocks.length ).toBe( 1 );
147
+ const innerBlock = innerBlocks.get( 0 );
148
+ expect( innerBlock.get( 'name' ) ).toBe( 'core/paragraph' );
149
+ } );
150
+
151
+ it( 'skips gallery blocks with unuploaded images (blob attributes)', () => {
152
+ const galleryWithBlobs: Block[] = [
153
+ {
154
+ name: 'core/gallery',
155
+ attributes: {},
156
+ innerBlocks: [
157
+ {
158
+ name: 'core/image',
159
+ attributes: {
160
+ url: 'http://example.com/image.jpg',
161
+ blob: 'blob:...',
162
+ },
163
+ innerBlocks: [],
164
+ },
165
+ ],
166
+ },
167
+ ];
168
+
169
+ mergeCrdtBlocks( yblocks, galleryWithBlobs, null );
170
+
171
+ // Gallery block should not be synced because it has blob attributes
172
+ expect( yblocks.length ).toBe( 0 );
173
+ } );
174
+
175
+ it( 'syncs gallery blocks without blob attributes', () => {
176
+ const galleryWithoutBlobs: Block[] = [
177
+ {
178
+ name: 'core/gallery',
179
+ attributes: {},
180
+ innerBlocks: [
181
+ {
182
+ name: 'core/image',
183
+ attributes: {
184
+ url: 'http://example.com/image.jpg',
185
+ },
186
+ innerBlocks: [],
187
+ },
188
+ ],
189
+ },
190
+ ];
191
+
192
+ mergeCrdtBlocks( yblocks, galleryWithoutBlobs, null );
193
+
194
+ expect( yblocks.length ).toBe( 1 );
195
+ const block = yblocks.get( 0 );
196
+ expect( block.get( 'name' ) ).toBe( 'core/gallery' );
197
+ } );
198
+
199
+ it( 'handles block reordering', () => {
200
+ const initialBlocks: Block[] = [
201
+ {
202
+ name: 'core/paragraph',
203
+ attributes: { content: 'First' },
204
+ innerBlocks: [],
205
+ clientId: 'block-1',
206
+ },
207
+ {
208
+ name: 'core/paragraph',
209
+ attributes: { content: 'Second' },
210
+ innerBlocks: [],
211
+ clientId: 'block-2',
212
+ },
213
+ ];
214
+
215
+ mergeCrdtBlocks( yblocks, initialBlocks, null );
216
+
217
+ // Reorder blocks
218
+ const reorderedBlocks: Block[] = [
219
+ {
220
+ name: 'core/paragraph',
221
+ attributes: { content: 'Second' },
222
+ innerBlocks: [],
223
+ clientId: 'block-2',
224
+ },
225
+ {
226
+ name: 'core/paragraph',
227
+ attributes: { content: 'First' },
228
+ innerBlocks: [],
229
+ clientId: 'block-1',
230
+ },
231
+ ];
232
+
233
+ mergeCrdtBlocks( yblocks, reorderedBlocks, null );
234
+
235
+ expect( yblocks.length ).toBe( 2 );
236
+ const block0 = yblocks.get( 0 );
237
+ const content0 = (
238
+ block0.get( 'attributes' ) as YBlockAttributes
239
+ ).get( 'content' ) as Y.Text;
240
+ expect( content0.toString() ).toBe( 'Second' );
241
+
242
+ const block1 = yblocks.get( 1 );
243
+ const content1 = (
244
+ block1.get( 'attributes' ) as YBlockAttributes
245
+ ).get( 'content' ) as Y.Text;
246
+ expect( content1.toString() ).toBe( 'First' );
247
+ } );
248
+
249
+ it( 'creates Y.Text for rich-text attributes', () => {
250
+ const blocks: Block[] = [
251
+ {
252
+ name: 'core/paragraph',
253
+ attributes: { content: 'Rich text content' },
254
+ innerBlocks: [],
255
+ },
256
+ ];
257
+
258
+ mergeCrdtBlocks( yblocks, blocks, null );
259
+
260
+ const block = yblocks.get( 0 );
261
+ const contentAttr = (
262
+ block.get( 'attributes' ) as YBlockAttributes
263
+ ).get( 'content' ) as Y.Text;
264
+ expect( contentAttr.toString() ).toBe( 'Rich text content' );
265
+ } );
266
+
267
+ it( 'removes duplicate clientIds', () => {
268
+ const blocksWithDuplicateIds: Block[] = [
269
+ {
270
+ name: 'core/paragraph',
271
+ attributes: { content: 'First' },
272
+ innerBlocks: [],
273
+ clientId: 'duplicate-id',
274
+ },
275
+ {
276
+ name: 'core/paragraph',
277
+ attributes: { content: 'Second' },
278
+ innerBlocks: [],
279
+ clientId: 'duplicate-id',
280
+ },
281
+ ];
282
+
283
+ mergeCrdtBlocks( yblocks, blocksWithDuplicateIds, null );
284
+
285
+ const block0 = yblocks.get( 0 );
286
+ const clientId1 = block0.get( 'clientId' );
287
+ const block1 = yblocks.get( 1 );
288
+ const clientId2 = block1.get( 'clientId' );
289
+
290
+ expect( clientId1 ).not.toBe( clientId2 );
291
+ } );
292
+
293
+ it( 'handles attribute deletion', () => {
294
+ const initialBlocks: Block[] = [
295
+ {
296
+ name: 'core/heading',
297
+ attributes: {
298
+ content: 'Heading',
299
+ level: 2,
300
+ },
301
+ innerBlocks: [],
302
+ },
303
+ ];
304
+
305
+ mergeCrdtBlocks( yblocks, initialBlocks, null );
306
+
307
+ const updatedBlocks: Block[] = [
308
+ {
309
+ name: 'core/heading',
310
+ attributes: {
311
+ content: 'Heading',
312
+ },
313
+ innerBlocks: [],
314
+ },
315
+ ];
316
+
317
+ mergeCrdtBlocks( yblocks, updatedBlocks, null );
318
+
319
+ const block = yblocks.get( 0 );
320
+ const attributes = block.get( 'attributes' ) as YBlockAttributes;
321
+ expect( attributes.has( 'level' ) ).toBe( false );
322
+ expect( attributes.has( 'content' ) ).toBe( true );
323
+ } );
324
+
325
+ it( 'preserves blocks that match from both left and right', () => {
326
+ const initialBlocks: Block[] = [
327
+ {
328
+ name: 'core/paragraph',
329
+ attributes: { content: 'First' },
330
+ innerBlocks: [],
331
+ },
332
+ {
333
+ name: 'core/paragraph',
334
+ attributes: { content: 'Middle' },
335
+ innerBlocks: [],
336
+ },
337
+ {
338
+ name: 'core/paragraph',
339
+ attributes: { content: 'Last' },
340
+ innerBlocks: [],
341
+ },
342
+ ];
343
+
344
+ mergeCrdtBlocks( yblocks, initialBlocks, null );
345
+
346
+ // Update only the middle block
347
+ const updatedBlocks: Block[] = [
348
+ {
349
+ name: 'core/paragraph',
350
+ attributes: { content: 'First' },
351
+ innerBlocks: [],
352
+ },
353
+ {
354
+ name: 'core/paragraph',
355
+ attributes: { content: 'Updated Middle' },
356
+ innerBlocks: [],
357
+ },
358
+ {
359
+ name: 'core/paragraph',
360
+ attributes: { content: 'Last' },
361
+ innerBlocks: [],
362
+ },
363
+ ];
364
+
365
+ mergeCrdtBlocks( yblocks, updatedBlocks, null );
366
+
367
+ expect( yblocks.length ).toBe( 3 );
368
+ const block = yblocks.get( 1 );
369
+ const content = (
370
+ block.get( 'attributes' ) as YBlockAttributes
371
+ ).get( 'content' ) as Y.Text;
372
+ expect( content.toString() ).toBe( 'Updated Middle' );
373
+ } );
374
+ } );
375
+ } );