@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.
- package/build/actions.js +375 -632
- package/build/actions.js.map +7 -1
- package/build/batch/create-batch.js +49 -62
- package/build/batch/create-batch.js.map +7 -1
- package/build/batch/default-processor.js +43 -39
- package/build/batch/default-processor.js.map +7 -1
- package/build/batch/index.js +38 -17
- package/build/batch/index.js.map +7 -1
- package/build/dynamic-entities.js +30 -32
- package/build/dynamic-entities.js.map +7 -1
- package/build/entities.js +298 -410
- package/build/entities.js.map +7 -1
- package/build/entity-context.js +29 -12
- package/build/entity-context.js.map +7 -1
- package/build/entity-provider.js +38 -46
- package/build/entity-provider.js.map +7 -1
- package/build/entity-types/attachment.js +16 -5
- package/build/entity-types/attachment.js.map +7 -1
- package/build/entity-types/base-entity-records.js +16 -42
- package/build/entity-types/base-entity-records.js.map +7 -1
- package/build/entity-types/base.js +16 -5
- package/build/entity-types/base.js.map +7 -1
- package/build/entity-types/comment.js +16 -5
- package/build/entity-types/comment.js.map +7 -1
- package/build/entity-types/global-styles-revision.js +16 -5
- package/build/entity-types/global-styles-revision.js.map +7 -1
- package/build/entity-types/helpers.js +16 -5
- package/build/entity-types/helpers.js.map +7 -1
- package/build/entity-types/index.js +16 -5
- package/build/entity-types/index.js.map +7 -1
- package/build/entity-types/menu-location.js +16 -5
- package/build/entity-types/menu-location.js.map +7 -1
- package/build/entity-types/nav-menu-item.js +16 -5
- package/build/entity-types/nav-menu-item.js.map +7 -1
- package/build/entity-types/nav-menu.js +16 -5
- package/build/entity-types/nav-menu.js.map +7 -1
- package/build/entity-types/page.js +16 -5
- package/build/entity-types/page.js.map +7 -1
- package/build/entity-types/plugin.js +16 -5
- package/build/entity-types/plugin.js.map +7 -1
- package/build/entity-types/post-revision.js +16 -5
- package/build/entity-types/post-revision.js.map +7 -1
- package/build/entity-types/post-status.js +16 -5
- package/build/entity-types/post-status.js.map +7 -1
- package/build/entity-types/post.js +16 -5
- package/build/entity-types/post.js.map +7 -1
- package/build/entity-types/settings.js +16 -5
- package/build/entity-types/settings.js.map +7 -1
- package/build/entity-types/sidebar.js +16 -5
- package/build/entity-types/sidebar.js.map +7 -1
- package/build/entity-types/taxonomy.js +16 -5
- package/build/entity-types/taxonomy.js.map +7 -1
- package/build/entity-types/term.js +16 -5
- package/build/entity-types/term.js.map +7 -1
- package/build/entity-types/theme.js +16 -5
- package/build/entity-types/theme.js.map +7 -1
- package/build/entity-types/type.js +16 -5
- package/build/entity-types/type.js.map +7 -1
- package/build/entity-types/user.js +16 -5
- package/build/entity-types/user.js.map +7 -1
- package/build/entity-types/widget-type.js +16 -5
- package/build/entity-types/widget-type.js.map +7 -1
- package/build/entity-types/widget.js +16 -5
- package/build/entity-types/widget.js.map +7 -1
- package/build/entity-types/wp-template-part.js +16 -5
- package/build/entity-types/wp-template-part.js.map +7 -1
- package/build/entity-types/wp-template.js +16 -5
- package/build/entity-types/wp-template.js.map +7 -1
- package/build/fetch/__experimental-fetch-link-suggestions.js +141 -154
- package/build/fetch/__experimental-fetch-link-suggestions.js.map +7 -1
- package/build/fetch/__experimental-fetch-url-data.js +47 -59
- package/build/fetch/__experimental-fetch-url-data.js.map +7 -1
- package/build/fetch/index.js +53 -32
- package/build/fetch/index.js.map +7 -1
- package/build/footnotes/get-footnotes-order.js +38 -25
- package/build/footnotes/get-footnotes-order.js.map +7 -1
- package/build/footnotes/get-rich-text-values-cached.js +26 -27
- package/build/footnotes/get-rich-text-values-cached.js.map +7 -1
- package/build/footnotes/index.js +68 -55
- package/build/footnotes/index.js.map +7 -1
- package/build/hooks/constants.js +33 -12
- package/build/hooks/constants.js.map +7 -1
- package/build/hooks/index.js +59 -68
- package/build/hooks/index.js.map +7 -1
- package/build/hooks/memoize.js +34 -12
- package/build/hooks/memoize.js.map +7 -1
- package/build/hooks/use-entity-block-editor.js +111 -119
- package/build/hooks/use-entity-block-editor.js.map +7 -1
- package/build/hooks/use-entity-id.js +25 -23
- package/build/hooks/use-entity-id.js.map +7 -1
- package/build/hooks/use-entity-prop.js +59 -60
- package/build/hooks/use-entity-prop.js.map +7 -1
- package/build/hooks/use-entity-record.js +95 -155
- package/build/hooks/use-entity-record.js.map +7 -1
- package/build/hooks/use-entity-records.js +131 -139
- package/build/hooks/use-entity-records.js.map +7 -1
- package/build/hooks/use-query-select.js +65 -84
- package/build/hooks/use-query-select.js.map +7 -1
- package/build/hooks/use-resource-permissions.js +92 -145
- package/build/hooks/use-resource-permissions.js.map +7 -1
- package/build/index.js +96 -153
- package/build/index.js.map +7 -1
- package/build/lock-unlock.js +31 -14
- package/build/lock-unlock.js.map +7 -1
- package/build/locks/actions.js +36 -19
- package/build/locks/actions.js.map +7 -1
- package/build/locks/engine.js +48 -47
- package/build/locks/engine.js.map +7 -1
- package/build/locks/reducer.js +54 -63
- package/build/locks/reducer.js.map +7 -1
- package/build/locks/selectors.js +35 -30
- package/build/locks/selectors.js.map +7 -1
- package/build/locks/utils.js +37 -16
- package/build/locks/utils.js.map +7 -1
- package/build/name.js +27 -12
- package/build/name.js.map +7 -1
- package/build/private-actions.js +67 -75
- package/build/private-actions.js.map +7 -1
- package/build/private-apis.js +33 -16
- package/build/private-apis.js.map +7 -1
- package/build/private-selectors.js +204 -184
- package/build/private-selectors.js.map +7 -1
- package/build/queried-data/actions.js +32 -41
- package/build/queried-data/actions.js.map +7 -1
- package/build/queried-data/get-query-parts.js +41 -79
- package/build/queried-data/get-query-parts.js.map +7 -1
- package/build/queried-data/index.js +39 -36
- package/build/queried-data/index.js.map +7 -1
- package/build/queried-data/reducer.js +162 -193
- package/build/queried-data/reducer.js.map +7 -1
- package/build/queried-data/selectors.js +57 -85
- package/build/queried-data/selectors.js.map +7 -1
- package/build/reducer.js +279 -404
- package/build/reducer.js.map +7 -1
- package/build/resolvers.js +553 -600
- package/build/resolvers.js.map +7 -1
- package/build/selectors.js +456 -981
- package/build/selectors.js.map +7 -1
- package/build/sync.js +34 -22
- package/build/sync.js.map +7 -1
- package/build/types.js +16 -5
- package/build/types.js.map +7 -1
- package/build/utils/conservative-map-item.js +34 -27
- package/build/utils/conservative-map-item.js.map +7 -1
- package/build/utils/crdt-blocks.js +289 -0
- package/build/utils/crdt-blocks.js.map +7 -0
- package/build/utils/crdt.js +202 -0
- package/build/utils/crdt.js.map +7 -0
- package/build/utils/forward-resolver.js +24 -16
- package/build/utils/forward-resolver.js.map +7 -1
- package/build/utils/get-nested-value.js +26 -21
- package/build/utils/get-nested-value.js.map +7 -1
- package/build/utils/get-normalized-comma-separable.js +25 -17
- package/build/utils/get-normalized-comma-separable.js.map +7 -1
- package/build/utils/if-matching-action.js +25 -19
- package/build/utils/if-matching-action.js.map +7 -1
- package/build/utils/index.js +77 -108
- package/build/utils/index.js.map +7 -1
- package/build/utils/is-numeric-id.js +22 -12
- package/build/utils/is-numeric-id.js.map +7 -1
- package/build/utils/is-raw-attribute.js +22 -13
- package/build/utils/is-raw-attribute.js.map +7 -1
- package/build/utils/log-entity-deprecation.js +37 -38
- package/build/utils/log-entity-deprecation.js.map +7 -1
- package/build/utils/on-sub-key.js +30 -24
- package/build/utils/on-sub-key.js.map +7 -1
- package/build/utils/receive-intermediate-results.js +29 -6
- package/build/utils/receive-intermediate-results.js.map +7 -1
- package/build/utils/replace-action.js +24 -17
- package/build/utils/replace-action.js.map +7 -1
- package/build/utils/set-nested-value.js +25 -30
- package/build/utils/set-nested-value.js.map +7 -1
- package/build/utils/user-permissions.js +41 -13
- package/build/utils/user-permissions.js.map +7 -1
- package/build/utils/with-weak-map-cache.js +26 -22
- package/build/utils/with-weak-map-cache.js.map +7 -1
- package/build-module/actions.js +322 -601
- package/build-module/actions.js.map +7 -1
- package/build-module/batch/create-batch.js +21 -57
- package/build-module/batch/create-batch.js.map +7 -1
- package/build-module/batch/default-processor.js +14 -33
- package/build-module/batch/default-processor.js.map +7 -1
- package/build-module/batch/index.js +7 -3
- package/build-module/batch/index.js.map +7 -1
- package/build-module/dynamic-entities.js +7 -28
- package/build-module/dynamic-entities.js.map +7 -1
- package/build-module/entities.js +263 -399
- package/build-module/entities.js.map +7 -1
- package/build-module/entity-context.js +7 -7
- package/build-module/entity-context.js.map +7 -1
- package/build-module/entity-provider.js +19 -42
- package/build-module/entity-provider.js.map +7 -1
- package/build-module/entity-types/attachment.js +1 -2
- package/build-module/entity-types/attachment.js.map +7 -1
- package/build-module/entity-types/base-entity-records.js +1 -37
- package/build-module/entity-types/base-entity-records.js.map +7 -1
- package/build-module/entity-types/base.js +1 -2
- package/build-module/entity-types/base.js.map +7 -1
- package/build-module/entity-types/comment.js +1 -2
- package/build-module/entity-types/comment.js.map +7 -1
- package/build-module/entity-types/global-styles-revision.js +1 -2
- package/build-module/entity-types/global-styles-revision.js.map +7 -1
- package/build-module/entity-types/helpers.js +1 -2
- package/build-module/entity-types/helpers.js.map +7 -1
- package/build-module/entity-types/index.js +1 -2
- package/build-module/entity-types/index.js.map +7 -1
- package/build-module/entity-types/menu-location.js +1 -2
- package/build-module/entity-types/menu-location.js.map +7 -1
- package/build-module/entity-types/nav-menu-item.js +1 -2
- package/build-module/entity-types/nav-menu-item.js.map +7 -1
- package/build-module/entity-types/nav-menu.js +1 -2
- package/build-module/entity-types/nav-menu.js.map +7 -1
- package/build-module/entity-types/page.js +1 -2
- package/build-module/entity-types/page.js.map +7 -1
- package/build-module/entity-types/plugin.js +1 -2
- package/build-module/entity-types/plugin.js.map +7 -1
- package/build-module/entity-types/post-revision.js +1 -2
- package/build-module/entity-types/post-revision.js.map +7 -1
- package/build-module/entity-types/post-status.js +1 -2
- package/build-module/entity-types/post-status.js.map +7 -1
- package/build-module/entity-types/post.js +1 -2
- package/build-module/entity-types/post.js.map +7 -1
- package/build-module/entity-types/settings.js +1 -2
- package/build-module/entity-types/settings.js.map +7 -1
- package/build-module/entity-types/sidebar.js +1 -2
- package/build-module/entity-types/sidebar.js.map +7 -1
- package/build-module/entity-types/taxonomy.js +1 -2
- package/build-module/entity-types/taxonomy.js.map +7 -1
- package/build-module/entity-types/term.js +1 -2
- package/build-module/entity-types/term.js.map +7 -1
- package/build-module/entity-types/theme.js +1 -2
- package/build-module/entity-types/theme.js.map +7 -1
- package/build-module/entity-types/type.js +1 -2
- package/build-module/entity-types/type.js.map +7 -1
- package/build-module/entity-types/user.js +1 -2
- package/build-module/entity-types/user.js.map +7 -1
- package/build-module/entity-types/widget-type.js +1 -2
- package/build-module/entity-types/widget-type.js.map +7 -1
- package/build-module/entity-types/widget.js +1 -2
- package/build-module/entity-types/widget.js.map +7 -1
- package/build-module/entity-types/wp-template-part.js +1 -2
- package/build-module/entity-types/wp-template-part.js.map +7 -1
- package/build-module/entity-types/wp-template.js +1 -2
- package/build-module/entity-types/wp-template.js.map +7 -1
- package/build-module/fetch/__experimental-fetch-link-suggestions.js +111 -149
- package/build-module/fetch/__experimental-fetch-link-suggestions.js.map +7 -1
- package/build-module/fetch/__experimental-fetch-url-data.js +20 -49
- package/build-module/fetch/__experimental-fetch-url-data.js.map +7 -1
- package/build-module/fetch/index.js +20 -15
- package/build-module/fetch/index.js.map +7 -1
- package/build-module/footnotes/get-footnotes-order.js +10 -19
- package/build-module/footnotes/get-footnotes-order.js.map +7 -1
- package/build-module/footnotes/get-rich-text-values-cached.js +8 -23
- package/build-module/footnotes/get-rich-text-values-cached.js.map +7 -1
- package/build-module/footnotes/index.js +34 -47
- package/build-module/footnotes/index.js.map +7 -1
- package/build-module/hooks/constants.js +11 -8
- package/build-module/hooks/constants.js.map +7 -1
- package/build-module/hooks/index.js +27 -15
- package/build-module/hooks/index.js.map +7 -1
- package/build-module/hooks/memoize.js +6 -8
- package/build-module/hooks/memoize.js.map +7 -1
- package/build-module/hooks/use-entity-block-editor.js +80 -110
- package/build-module/hooks/use-entity-block-editor.js.map +7 -1
- package/build-module/hooks/use-entity-id.js +7 -19
- package/build-module/hooks/use-entity-id.js.map +7 -1
- package/build-module/hooks/use-entity-prop.js +31 -55
- package/build-module/hooks/use-entity-prop.js.map +7 -1
- package/build-module/hooks/use-entity-record.js +63 -148
- package/build-module/hooks/use-entity-record.js.map +7 -1
- package/build-module/hooks/use-entity-records.js +98 -131
- package/build-module/hooks/use-entity-records.js.map +7 -1
- package/build-module/hooks/use-query-select.js +27 -71
- package/build-module/hooks/use-query-select.js.map +7 -1
- package/build-module/hooks/use-resource-permissions.js +57 -136
- package/build-module/hooks/use-resource-permissions.js.map +7 -1
- package/build-module/index.js +49 -71
- package/build-module/index.js.map +7 -1
- package/build-module/lock-unlock.js +8 -7
- package/build-module/lock-unlock.js.map +7 -1
- package/build-module/locks/actions.js +8 -13
- package/build-module/locks/actions.js.map +7 -1
- package/build-module/locks/engine.js +17 -38
- package/build-module/locks/engine.js.map +7 -1
- package/build-module/locks/reducer.js +37 -59
- package/build-module/locks/reducer.js.map +7 -1
- package/build-module/locks/selectors.js +16 -23
- package/build-module/locks/selectors.js.map +7 -1
- package/build-module/locks/utils.js +15 -12
- package/build-module/locks/utils.js.map +7 -1
- package/build-module/name.js +5 -8
- package/build-module/name.js.map +7 -1
- package/build-module/private-actions.js +35 -69
- package/build-module/private-actions.js.map +7 -1
- package/build-module/private-apis.js +8 -8
- package/build-module/private-apis.js.map +7 -1
- package/build-module/private-selectors.js +167 -174
- package/build-module/private-selectors.js.map +7 -1
- package/build-module/queried-data/actions.js +11 -38
- package/build-module/queried-data/actions.js.map +7 -1
- package/build-module/queried-data/get-query-parts.js +20 -75
- package/build-module/queried-data/get-query-parts.js.map +7 -1
- package/build-module/queried-data/index.js +7 -4
- package/build-module/queried-data/index.js.map +7 -1
- package/build-module/queried-data/reducer.js +134 -185
- package/build-module/queried-data/reducer.js.map +7 -1
- package/build-module/queried-data/selectors.js +23 -78
- package/build-module/queried-data/selectors.js.map +7 -1
- package/build-module/reducer.js +243 -393
- package/build-module/reducer.js.map +7 -1
- package/build-module/resolvers.js +478 -549
- package/build-module/resolvers.js.map +7 -1
- package/build-module/selectors.js +410 -953
- package/build-module/selectors.js.map +7 -1
- package/build-module/sync.js +14 -17
- package/build-module/sync.js.map +7 -1
- package/build-module/types.js +1 -2
- package/build-module/types.js.map +7 -1
- package/build-module/utils/conservative-map-item.js +6 -22
- package/build-module/utils/conservative-map-item.js.map +7 -1
- package/build-module/utils/crdt-blocks.js +255 -0
- package/build-module/utils/crdt-blocks.js.map +7 -0
- package/build-module/utils/crdt.js +167 -0
- package/build-module/utils/crdt.js.map +7 -0
- package/build-module/utils/forward-resolver.js +6 -12
- package/build-module/utils/forward-resolver.js.map +7 -1
- package/build-module/utils/get-nested-value.js +9 -18
- package/build-module/utils/get-nested-value.js.map +7 -1
- package/build-module/utils/get-normalized-comma-separable.js +7 -13
- package/build-module/utils/get-normalized-comma-separable.js.map +7 -1
- package/build-module/utils/if-matching-action.js +7 -15
- package/build-module/utils/if-matching-action.js.map +7 -1
- package/build-module/utils/index.js +35 -14
- package/build-module/utils/index.js.map +7 -1
- package/build-module/utils/is-numeric-id.js +5 -9
- package/build-module/utils/is-numeric-id.js.map +7 -1
- package/build-module/utils/is-raw-attribute.js +5 -10
- package/build-module/utils/is-raw-attribute.js.map +7 -1
- package/build-module/utils/log-entity-deprecation.js +8 -31
- package/build-module/utils/log-entity-deprecation.js.map +7 -1
- package/build-module/utils/on-sub-key.js +8 -19
- package/build-module/utils/on-sub-key.js.map +7 -1
- package/build-module/utils/receive-intermediate-results.js +7 -2
- package/build-module/utils/receive-intermediate-results.js.map +7 -1
- package/build-module/utils/replace-action.js +6 -13
- package/build-module/utils/replace-action.js.map +7 -1
- package/build-module/utils/set-nested-value.js +8 -27
- package/build-module/utils/set-nested-value.js.map +7 -1
- package/build-module/utils/user-permissions.js +19 -9
- package/build-module/utils/user-permissions.js.map +7 -1
- package/build-module/utils/with-weak-map-cache.js +8 -18
- package/build-module/utils/with-weak-map-cache.js.map +7 -1
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/entities.d.ts +0 -56
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +3 -0
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/sync.d.ts +6 -1
- package/build-types/sync.d.ts.map +1 -1
- package/build-types/types.d.ts +9 -0
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts +30 -0
- package/build-types/utils/crdt-blocks.d.ts.map +1 -0
- package/build-types/utils/crdt.d.ts +49 -0
- package/build-types/utils/crdt.d.ts.map +1 -0
- package/package.json +26 -19
- package/src/actions.js +56 -74
- package/src/entities.js +59 -113
- package/src/private-selectors.ts +32 -7
- package/src/resolvers.js +173 -120
- package/src/selectors.ts +0 -13
- package/src/sync.ts +12 -0
- package/src/test/resolvers.js +183 -0
- package/src/types.ts +12 -0
- package/src/utils/crdt-blocks.ts +503 -0
- package/src/utils/crdt.ts +310 -0
- package/src/utils/test/crdt-blocks.ts +375 -0
- package/src/utils/test/crdt.ts +254 -0
- package/tsconfig.tsbuildinfo +1 -1
- 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
|
+
} );
|