@wordpress/core-data 7.41.2-next.v.202603102151.0 → 7.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/README.md +19 -0
- package/build/actions.cjs +17 -25
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +46 -6
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/entities.cjs +33 -7
- package/build/entities.cjs.map +2 -2
- package/build/hooks/use-entity-prop.cjs +2 -1
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-post-editor-awareness-state.cjs +84 -3
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/index.cjs +3 -0
- package/build/index.cjs.map +2 -2
- package/build/private-apis.cjs +3 -1
- package/build/private-apis.cjs.map +2 -2
- package/build/queried-data/get-query-parts.cjs +7 -0
- package/build/queried-data/get-query-parts.cjs.map +2 -2
- package/build/queried-data/selectors.cjs +19 -5
- package/build/queried-data/selectors.cjs.map +2 -2
- package/build/reducer.cjs +6 -0
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +110 -74
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +29 -0
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +3 -0
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs +16 -0
- package/build/types.cjs.map +3 -3
- package/build/utils/block-selection-history.cjs +1 -1
- package/build/utils/block-selection-history.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +17 -3
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs +4 -1
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +9 -6
- package/build/utils/crdt-user-selections.cjs.map +2 -2
- package/build/utils/crdt-utils.cjs +54 -2
- package/build/utils/crdt-utils.cjs.map +2 -2
- package/build/utils/crdt.cjs +4 -23
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +3 -0
- package/build/utils/index.cjs.map +2 -2
- package/build/utils/normalize-query-for-resolution.cjs +35 -0
- package/build/utils/normalize-query-for-resolution.cjs.map +7 -0
- package/build-module/actions.mjs +17 -25
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +46 -6
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +33 -7
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +2 -1
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +81 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/index.mjs +2 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-apis.mjs +6 -2
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/queried-data/get-query-parts.mjs +7 -0
- package/build-module/queried-data/get-query-parts.mjs.map +2 -2
- package/build-module/queried-data/selectors.mjs +19 -5
- package/build-module/queried-data/selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +6 -0
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +112 -75
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +28 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +2 -0
- package/build-module/sync.mjs.map +2 -2
- package/build-module/types.mjs +9 -0
- package/build-module/types.mjs.map +4 -4
- package/build-module/utils/block-selection-history.mjs +5 -2
- package/build-module/utils/block-selection-history.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +17 -3
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs +8 -2
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +10 -7
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-module/utils/crdt-utils.mjs +51 -1
- package/build-module/utils/crdt-utils.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +4 -23
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +2 -0
- package/build-module/utils/index.mjs.map +2 -2
- package/build-module/utils/normalize-query-for-resolution.mjs +14 -0
- package/build-module/utils/normalize-query-for-resolution.mjs.map +7 -0
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +2 -2
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +1 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/entities.d.ts +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +34 -10
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts +2 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/queried-data/get-query-parts.d.ts +7 -0
- package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
- package/build-types/queried-data/selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +2 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +17 -0
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/sync.d.ts +2 -2
- package/build-types/sync.d.ts.map +1 -1
- package/build-types/types.d.ts +18 -1
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/block-selection-history.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-selection.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts +9 -5
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt-utils.d.ts +20 -0
- package/build-types/utils/crdt-utils.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +6 -7
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +1 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -0
- package/build-types/utils/test/crdt-utils.d.ts +2 -0
- package/build-types/utils/test/crdt-utils.d.ts.map +1 -0
- package/package.json +18 -18
- package/src/actions.js +25 -40
- package/src/awareness/post-editor-awareness.ts +106 -7
- package/src/awareness/test/post-editor-awareness.ts +50 -10
- package/src/awareness/types.ts +1 -1
- package/src/entities.js +38 -6
- package/src/hooks/test/use-post-editor-awareness-state.ts +446 -3
- package/src/hooks/use-entity-prop.js +2 -0
- package/src/hooks/use-post-editor-awareness-state.ts +160 -8
- package/src/index.js +1 -0
- package/src/private-apis.js +6 -2
- package/src/queried-data/get-query-parts.js +13 -0
- package/src/queried-data/selectors.js +33 -8
- package/src/queried-data/test/get-query-parts.js +34 -0
- package/src/queried-data/test/selectors.js +183 -0
- package/src/reducer.js +11 -0
- package/src/resolvers.js +136 -88
- package/src/selectors.ts +56 -0
- package/src/sync.ts +2 -0
- package/src/test/entities.js +185 -1
- package/src/test/resolvers.js +64 -11
- package/src/test/selectors.js +150 -0
- package/src/test/store.js +66 -0
- package/src/types.ts +26 -1
- package/src/utils/block-selection-history.ts +5 -2
- package/src/utils/crdt-blocks.ts +32 -3
- package/src/utils/crdt-selection.ts +8 -2
- package/src/utils/crdt-user-selections.ts +20 -8
- package/src/utils/crdt-utils.ts +99 -0
- package/src/utils/crdt.ts +8 -32
- package/src/utils/index.js +1 -0
- package/src/utils/normalize-query-for-resolution.js +23 -0
- package/src/utils/test/crdt-blocks.ts +146 -0
- package/src/utils/test/crdt-user-selections.ts +5 -0
- package/src/utils/test/crdt-utils.ts +387 -0
- package/src/utils/test/crdt.ts +120 -53
package/src/utils/crdt-utils.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { Y } from '@wordpress/sync';
|
|
5
|
+
import { create, insert, toHTMLString } from '@wordpress/rich-text';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Internal dependencies
|
|
@@ -104,6 +105,104 @@ export function findBlockByClientIdInDoc(
|
|
|
104
105
|
return findBlockByClientIdInBlocks( blockId, blocks );
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
// Marker for insertion.
|
|
109
|
+
const MARKER_START = 0xe000;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Pick a marker character that does not appear in `text`. Returns the marker
|
|
113
|
+
* or `null` if all candidates are present (extremely unlikely in practice).
|
|
114
|
+
*
|
|
115
|
+
* @param text The string to check for existing marker characters.
|
|
116
|
+
*/
|
|
117
|
+
function pickMarker( text: string ): string | null {
|
|
118
|
+
const tryCount = 0x10;
|
|
119
|
+
|
|
120
|
+
// Scan the unicode private use area for the first code point not present
|
|
121
|
+
// in the text.
|
|
122
|
+
for ( let code = MARKER_START; code < MARKER_START + tryCount; code++ ) {
|
|
123
|
+
const candidate = String.fromCharCode( code );
|
|
124
|
+
|
|
125
|
+
if ( ! text.includes( candidate ) ) {
|
|
126
|
+
return candidate;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Convert an HTML character index (counting tag characters) to a rich-text
|
|
135
|
+
* offset (counting only text characters). Used on read paths where Y.Text
|
|
136
|
+
* resolves to an HTML index but the block editor expects a text offset.
|
|
137
|
+
*
|
|
138
|
+
* @param html The full HTML string from Y.Text.
|
|
139
|
+
* @param htmlIndex The HTML character index.
|
|
140
|
+
* @return The corresponding rich-text offset.
|
|
141
|
+
*/
|
|
142
|
+
export function htmlIndexToRichTextOffset(
|
|
143
|
+
html: string,
|
|
144
|
+
htmlIndex: number
|
|
145
|
+
): number {
|
|
146
|
+
if ( ! html.includes( '<' ) && ! html.includes( '&' ) ) {
|
|
147
|
+
return htmlIndex;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const marker = pickMarker( html );
|
|
151
|
+
if ( ! marker ) {
|
|
152
|
+
return htmlIndex;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Insert marker and let create() do the parsing.
|
|
156
|
+
const withMarker =
|
|
157
|
+
html.slice( 0, htmlIndex ) + marker + html.slice( htmlIndex );
|
|
158
|
+
const value = create( { html: withMarker } );
|
|
159
|
+
const markerPos = value.text.indexOf( marker );
|
|
160
|
+
|
|
161
|
+
return markerPos === -1 ? htmlIndex : markerPos;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Convert a rich-text offset (counting only text characters) to an HTML
|
|
166
|
+
* character index (counting tag characters). Used on write paths where the
|
|
167
|
+
* block editor provides a text offset but Y.Text expects an HTML index.
|
|
168
|
+
*
|
|
169
|
+
* @param html The full HTML string from Y.Text.
|
|
170
|
+
* @param richTextOffset The rich-text text offset.
|
|
171
|
+
* @return The corresponding HTML character index.
|
|
172
|
+
*/
|
|
173
|
+
export function richTextOffsetToHtmlIndex(
|
|
174
|
+
html: string,
|
|
175
|
+
richTextOffset: number
|
|
176
|
+
): number {
|
|
177
|
+
if ( ! html.includes( '<' ) && ! html.includes( '&' ) ) {
|
|
178
|
+
return richTextOffset;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const marker = pickMarker( html );
|
|
182
|
+
if ( ! marker ) {
|
|
183
|
+
return richTextOffset;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const value = create( { html } );
|
|
187
|
+
const markerValue = create( { text: marker } );
|
|
188
|
+
// The marker must inherit the formatting at the insertion point so that
|
|
189
|
+
// toHTMLString does not split surrounding tags (e.g. <strong>) around it.
|
|
190
|
+
if ( value.formats[ richTextOffset ] ) {
|
|
191
|
+
markerValue.formats[ 0 ] = value.formats[ richTextOffset ];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const withMarker = insert(
|
|
195
|
+
value,
|
|
196
|
+
markerValue,
|
|
197
|
+
richTextOffset,
|
|
198
|
+
richTextOffset
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const htmlWithMarker = toHTMLString( { value: withMarker } );
|
|
202
|
+
const markerIndex = htmlWithMarker.indexOf( marker );
|
|
203
|
+
return markerIndex === -1 ? richTextOffset : markerIndex;
|
|
204
|
+
}
|
|
205
|
+
|
|
107
206
|
function findBlockByClientIdInBlocks(
|
|
108
207
|
blockId: string,
|
|
109
208
|
blocks: YBlocks
|
package/src/utils/crdt.ts
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
type YBlocks,
|
|
28
28
|
} from './crdt-blocks';
|
|
29
29
|
import { type Post } from '../entity-types/post';
|
|
30
|
-
import { type Type } from '../entity-types';
|
|
31
30
|
import { CRDT_DOC_META_PERSISTENCE_KEY, CRDT_RECORD_MAP_KEY } from '../sync';
|
|
32
31
|
import type { WPSelection } from '../types';
|
|
33
32
|
import {
|
|
@@ -76,27 +75,6 @@ export interface YPostRecord extends YMapRecord {
|
|
|
76
75
|
|
|
77
76
|
export const POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';
|
|
78
77
|
|
|
79
|
-
// Properties that are allowed to be synced for a post.
|
|
80
|
-
const allowedPostProperties = new Set< string >( [
|
|
81
|
-
'author',
|
|
82
|
-
'blocks',
|
|
83
|
-
'content',
|
|
84
|
-
'categories',
|
|
85
|
-
'comment_status',
|
|
86
|
-
'date',
|
|
87
|
-
'excerpt',
|
|
88
|
-
'featured_media',
|
|
89
|
-
'format',
|
|
90
|
-
'meta',
|
|
91
|
-
'ping_status',
|
|
92
|
-
'slug',
|
|
93
|
-
'status',
|
|
94
|
-
'sticky',
|
|
95
|
-
'tags',
|
|
96
|
-
'template',
|
|
97
|
-
'title',
|
|
98
|
-
] );
|
|
99
|
-
|
|
100
78
|
// Post meta keys that should *not* be synced.
|
|
101
79
|
const disallowedPostMetaKeys = new Set< string >( [
|
|
102
80
|
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
@@ -139,18 +117,18 @@ function defaultApplyChangesToCRDTDoc(
|
|
|
139
117
|
*
|
|
140
118
|
* @param {CRDTDoc} ydoc
|
|
141
119
|
* @param {PostChanges} changes
|
|
142
|
-
* @param {
|
|
120
|
+
* @param {Set<string>} syncedProperties
|
|
143
121
|
* @return {void}
|
|
144
122
|
*/
|
|
145
123
|
export function applyPostChangesToCRDTDoc(
|
|
146
124
|
ydoc: CRDTDoc,
|
|
147
125
|
changes: PostChanges,
|
|
148
|
-
|
|
126
|
+
syncedProperties: Set< string >
|
|
149
127
|
): void {
|
|
150
128
|
const ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );
|
|
151
129
|
|
|
152
130
|
Object.keys( changes ).forEach( ( key ) => {
|
|
153
|
-
if ( !
|
|
131
|
+
if ( ! syncedProperties.has( key ) ) {
|
|
154
132
|
return;
|
|
155
133
|
}
|
|
156
134
|
|
|
@@ -289,15 +267,15 @@ function defaultGetChangesFromCRDTDoc( crdtDoc: CRDTDoc ): ObjectData {
|
|
|
289
267
|
* against the local record and determine if there are changes (edits) we want
|
|
290
268
|
* to dispatch.
|
|
291
269
|
*
|
|
292
|
-
* @param {CRDTDoc}
|
|
293
|
-
* @param {Post}
|
|
294
|
-
* @param {
|
|
270
|
+
* @param {CRDTDoc} ydoc
|
|
271
|
+
* @param {Post} editedRecord
|
|
272
|
+
* @param {Set<string>} syncedProperties
|
|
295
273
|
* @return {Partial<PostChanges>} The changes that should be applied to the local record.
|
|
296
274
|
*/
|
|
297
275
|
export function getPostChangesFromCRDTDoc(
|
|
298
276
|
ydoc: CRDTDoc,
|
|
299
277
|
editedRecord: Post,
|
|
300
|
-
|
|
278
|
+
syncedProperties: Set< string >
|
|
301
279
|
): PostChanges {
|
|
302
280
|
const ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );
|
|
303
281
|
|
|
@@ -305,7 +283,7 @@ export function getPostChangesFromCRDTDoc(
|
|
|
305
283
|
|
|
306
284
|
const changes = Object.fromEntries(
|
|
307
285
|
Object.entries( ymap.toJSON() ).filter( ( [ key, newValue ] ) => {
|
|
308
|
-
if ( !
|
|
286
|
+
if ( ! syncedProperties.has( key ) ) {
|
|
309
287
|
return false;
|
|
310
288
|
}
|
|
311
289
|
|
|
@@ -342,8 +320,6 @@ export function getPostChangesFromCRDTDoc(
|
|
|
342
320
|
);
|
|
343
321
|
}
|
|
344
322
|
|
|
345
|
-
// The consumers of blocks have memoization that renders optimization
|
|
346
|
-
// here unnecessary.
|
|
347
323
|
return true;
|
|
348
324
|
}
|
|
349
325
|
|
package/src/utils/index.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a copy of `query` filtered to only the keys that affect resolution
|
|
3
|
+
* identity (`context` and `_fields`), or `undefined` when none are present.
|
|
4
|
+
* This mirrors the normalisation that the data store applies when keying
|
|
5
|
+
* resolved records, so that `finishResolutions` receives args that match the
|
|
6
|
+
* keys used by callers who omit pagination params.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} query The raw query object.
|
|
9
|
+
* @return {Object|undefined} Normalised query or undefined.
|
|
10
|
+
*/
|
|
11
|
+
export default function normalizeQueryForResolution( query ) {
|
|
12
|
+
if ( ! query ) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const entries = Object.entries( query ).filter(
|
|
17
|
+
( [ k, v ] ) =>
|
|
18
|
+
( k === 'context' || k === '_fields' ) &&
|
|
19
|
+
v !== undefined &&
|
|
20
|
+
v !== null
|
|
21
|
+
);
|
|
22
|
+
return entries.length > 0 ? Object.fromEntries( entries ) : undefined;
|
|
23
|
+
}
|
|
@@ -38,9 +38,24 @@ jest.mock( '@wordpress/blocks', () => ( {
|
|
|
38
38
|
url: { type: 'string' },
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
name: 'core/table',
|
|
43
|
+
attributes: {
|
|
44
|
+
hasFixedLayout: { type: 'boolean' },
|
|
45
|
+
caption: { type: 'rich-text' },
|
|
46
|
+
head: { type: 'array' },
|
|
47
|
+
body: { type: 'array' },
|
|
48
|
+
foot: { type: 'array' },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
41
51
|
],
|
|
42
52
|
} ) );
|
|
43
53
|
|
|
54
|
+
/**
|
|
55
|
+
* WordPress dependencies
|
|
56
|
+
*/
|
|
57
|
+
import { RichTextData } from '@wordpress/rich-text';
|
|
58
|
+
|
|
44
59
|
/**
|
|
45
60
|
* Internal dependencies
|
|
46
61
|
*/
|
|
@@ -1089,6 +1104,137 @@ describe( 'crdt-blocks', () => {
|
|
|
1089
1104
|
} );
|
|
1090
1105
|
} );
|
|
1091
1106
|
|
|
1107
|
+
describe( 'table block', () => {
|
|
1108
|
+
it( 'preserves table cell content through CRDT round-trip', () => {
|
|
1109
|
+
const tableBlocks: Block[] = [
|
|
1110
|
+
{
|
|
1111
|
+
name: 'core/table',
|
|
1112
|
+
attributes: {
|
|
1113
|
+
hasFixedLayout: true,
|
|
1114
|
+
body: [
|
|
1115
|
+
{
|
|
1116
|
+
cells: [
|
|
1117
|
+
{
|
|
1118
|
+
content:
|
|
1119
|
+
RichTextData.fromPlainText( '1' ),
|
|
1120
|
+
tag: 'td',
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
content:
|
|
1124
|
+
RichTextData.fromPlainText( '2' ),
|
|
1125
|
+
tag: 'td',
|
|
1126
|
+
},
|
|
1127
|
+
],
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
cells: [
|
|
1131
|
+
{
|
|
1132
|
+
content:
|
|
1133
|
+
RichTextData.fromPlainText( '3' ),
|
|
1134
|
+
tag: 'td',
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
content:
|
|
1138
|
+
RichTextData.fromPlainText( '4' ),
|
|
1139
|
+
tag: 'td',
|
|
1140
|
+
},
|
|
1141
|
+
],
|
|
1142
|
+
},
|
|
1143
|
+
],
|
|
1144
|
+
},
|
|
1145
|
+
innerBlocks: [],
|
|
1146
|
+
},
|
|
1147
|
+
];
|
|
1148
|
+
|
|
1149
|
+
mergeCrdtBlocks( yblocks, tableBlocks, null );
|
|
1150
|
+
|
|
1151
|
+
// Simulate a CRDT encode/decode cycle (persistence or sync).
|
|
1152
|
+
const encoded = Y.encodeStateAsUpdate( doc );
|
|
1153
|
+
const doc2 = new Y.Doc();
|
|
1154
|
+
Y.applyUpdate( doc2, encoded );
|
|
1155
|
+
|
|
1156
|
+
const yblocks2 = doc2.getArray< YBlock >();
|
|
1157
|
+
expect( yblocks2.length ).toBe( 1 );
|
|
1158
|
+
|
|
1159
|
+
const block = yblocks2.get( 0 );
|
|
1160
|
+
const attrs = block.get( 'attributes' ) as YBlockAttributes;
|
|
1161
|
+
const body = attrs.get( 'body' ) as {
|
|
1162
|
+
cells: { content: string; tag: string }[];
|
|
1163
|
+
}[];
|
|
1164
|
+
|
|
1165
|
+
expect( body ).toHaveLength( 2 );
|
|
1166
|
+
expect( body[ 0 ].cells[ 0 ].content ).toBe( '1' );
|
|
1167
|
+
expect( body[ 0 ].cells[ 1 ].content ).toBe( '2' );
|
|
1168
|
+
expect( body[ 1 ].cells[ 0 ].content ).toBe( '3' );
|
|
1169
|
+
expect( body[ 1 ].cells[ 1 ].content ).toBe( '4' );
|
|
1170
|
+
|
|
1171
|
+
doc2.destroy();
|
|
1172
|
+
} );
|
|
1173
|
+
|
|
1174
|
+
it( 'preserves table cell content with HTML formatting', () => {
|
|
1175
|
+
const tableBlocks: Block[] = [
|
|
1176
|
+
{
|
|
1177
|
+
name: 'core/table',
|
|
1178
|
+
attributes: {
|
|
1179
|
+
hasFixedLayout: true,
|
|
1180
|
+
head: [
|
|
1181
|
+
{
|
|
1182
|
+
cells: [
|
|
1183
|
+
{
|
|
1184
|
+
content: RichTextData.fromHTMLString(
|
|
1185
|
+
'<strong>Header</strong>'
|
|
1186
|
+
),
|
|
1187
|
+
tag: 'th',
|
|
1188
|
+
},
|
|
1189
|
+
],
|
|
1190
|
+
},
|
|
1191
|
+
],
|
|
1192
|
+
body: [
|
|
1193
|
+
{
|
|
1194
|
+
cells: [
|
|
1195
|
+
{
|
|
1196
|
+
content: RichTextData.fromHTMLString(
|
|
1197
|
+
'<a href="https://example.com">Link</a>'
|
|
1198
|
+
),
|
|
1199
|
+
tag: 'td',
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
},
|
|
1203
|
+
],
|
|
1204
|
+
},
|
|
1205
|
+
innerBlocks: [],
|
|
1206
|
+
},
|
|
1207
|
+
];
|
|
1208
|
+
|
|
1209
|
+
mergeCrdtBlocks( yblocks, tableBlocks, null );
|
|
1210
|
+
|
|
1211
|
+
// Round-trip through encode/decode.
|
|
1212
|
+
const encoded = Y.encodeStateAsUpdate( doc );
|
|
1213
|
+
const doc2 = new Y.Doc();
|
|
1214
|
+
Y.applyUpdate( doc2, encoded );
|
|
1215
|
+
|
|
1216
|
+
const yblocks2 = doc2.getArray< YBlock >();
|
|
1217
|
+
const block = yblocks2.get( 0 );
|
|
1218
|
+
const attrs = block.get( 'attributes' ) as YBlockAttributes;
|
|
1219
|
+
|
|
1220
|
+
const head = attrs.get( 'head' ) as {
|
|
1221
|
+
cells: { content: string }[];
|
|
1222
|
+
}[];
|
|
1223
|
+
expect( head[ 0 ].cells[ 0 ].content ).toBe(
|
|
1224
|
+
'<strong>Header</strong>'
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
const body = attrs.get( 'body' ) as {
|
|
1228
|
+
cells: { content: string }[];
|
|
1229
|
+
}[];
|
|
1230
|
+
expect( body[ 0 ].cells[ 0 ].content ).toBe(
|
|
1231
|
+
'<a href="https://example.com">Link</a>'
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1234
|
+
doc2.destroy();
|
|
1235
|
+
} );
|
|
1236
|
+
} );
|
|
1237
|
+
|
|
1092
1238
|
describe( 'emoji handling', () => {
|
|
1093
1239
|
// Emoji like 😀 (U+1F600) are surrogate pairs in UTF-16 (.length === 2).
|
|
1094
1240
|
// The CRDT sync must preserve them without corruption (no U+FFFD / '�').
|
|
@@ -16,6 +16,11 @@ import { CRDT_RECORD_MAP_KEY } from '../../sync';
|
|
|
16
16
|
|
|
17
17
|
jest.mock( '@wordpress/data', () => ( {
|
|
18
18
|
select: jest.fn(),
|
|
19
|
+
// Needed because @wordpress/rich-text initialises its store at import time.
|
|
20
|
+
combineReducers: jest.fn( () => jest.fn( () => ( {} ) ) ),
|
|
21
|
+
createReduxStore: jest.fn( () => ( {} ) ),
|
|
22
|
+
register: jest.fn(),
|
|
23
|
+
createSelector: ( selector: Function ) => selector,
|
|
19
24
|
} ) );
|
|
20
25
|
|
|
21
26
|
jest.mock( '@wordpress/block-editor', () => ( {
|