@wordpress/core-data 7.40.2-next.v.202602241322.0 → 7.40.2
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/README.md +0 -27
- package/build/actions.cjs +9 -26
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +45 -6
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/entities.cjs +50 -23
- package/build/entities.cjs.map +2 -2
- package/build/entity-provider.cjs +15 -6
- package/build/entity-provider.cjs.map +2 -2
- package/build/hooks/use-entity-prop.cjs +33 -2
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-post-editor-awareness-state.cjs +120 -1
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/index.cjs +0 -3
- package/build/index.cjs.map +2 -2
- package/build/private-actions.cjs +27 -3
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +14 -5
- package/build/private-apis.cjs.map +3 -3
- package/build/private-selectors.cjs +22 -2
- package/build/private-selectors.cjs.map +2 -2
- package/build/queried-data/actions.cjs +1 -1
- package/build/queried-data/actions.cjs.map +2 -2
- package/build/queried-data/reducer.cjs +19 -13
- package/build/queried-data/reducer.cjs.map +2 -2
- package/build/reducer.cjs +19 -2
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +16 -6
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +0 -15
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +11 -5
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs.map +1 -1
- 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 +125 -49
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs +50 -19
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt-text.cjs +52 -0
- package/build/utils/crdt-text.cjs.map +7 -0
- package/build/utils/crdt-user-selections.cjs +16 -6
- package/build/utils/crdt-user-selections.cjs.map +3 -3
- package/build/utils/crdt-utils.cjs +54 -2
- package/build/utils/crdt-utils.cjs.map +2 -2
- package/build/utils/crdt.cjs +38 -29
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/actions.mjs +14 -26
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +47 -7
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +53 -24
- package/build-module/entities.mjs.map +2 -2
- package/build-module/entity-provider.mjs +15 -6
- package/build-module/entity-provider.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +34 -3
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +117 -1
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/index.mjs +0 -2
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-actions.mjs +24 -2
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +21 -6
- package/build-module/private-apis.mjs.map +3 -3
- package/build-module/private-selectors.mjs +19 -1
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/queried-data/actions.mjs +1 -1
- package/build-module/queried-data/actions.mjs.map +2 -2
- package/build-module/queried-data/reducer.mjs +19 -13
- package/build-module/queried-data/reducer.mjs.map +2 -2
- package/build-module/reducer.mjs +18 -2
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +16 -6
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +0 -14
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +7 -3
- package/build-module/sync.mjs.map +2 -2
- 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 +124 -49
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs +53 -20
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt-text.mjs +26 -0
- package/build-module/utils/crdt-text.mjs.map +7 -0
- package/build-module/utils/crdt-user-selections.mjs +16 -7
- package/build-module/utils/crdt-user-selections.mjs.map +3 -3
- 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 +42 -34
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/actions.d.ts +0 -11
- 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 +6 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/entities.d.ts +20 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts +11 -6
- package/build-types/entity-provider.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 +40 -7
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts +0 -3
- package/build-types/index.d.ts.map +1 -1
- package/build-types/parsed-blocks-cache.d.ts +10 -0
- package/build-types/parsed-blocks-cache.d.ts.map +1 -0
- package/build-types/private-actions.d.ts +12 -0
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-apis.d.ts +21 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +17 -0
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/queried-data/reducer.d.ts.map +1 -1
- package/build-types/reducer.d.ts +15 -0
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +1 -9
- 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 +11 -2
- 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 +12 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-selection.d.ts +10 -0
- package/build-types/utils/crdt-selection.d.ts.map +1 -1
- package/build-types/utils/crdt-text.d.ts +16 -0
- package/build-types/utils/crdt-text.d.ts.map +1 -0
- package/build-types/utils/crdt-user-selections.d.ts +17 -4
- 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 +14 -7
- package/build-types/utils/crdt.d.ts.map +1 -1
- 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/save-crdt-doc.d.ts +8 -0
- package/build-types/utils/save-crdt-doc.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 +26 -41
- package/src/awareness/post-editor-awareness.ts +106 -7
- package/src/awareness/test/post-editor-awareness.ts +50 -10
- package/src/awareness/types.ts +7 -1
- package/src/entities.js +62 -24
- package/src/entity-provider.js +24 -11
- package/src/hooks/test/use-post-editor-awareness-state.ts +446 -3
- package/src/hooks/use-entity-prop.js +43 -3
- package/src/hooks/use-post-editor-awareness-state.ts +229 -7
- package/src/index.js +0 -6
- package/src/private-actions.js +43 -1
- package/src/{private-apis.js → private-apis.ts} +21 -3
- package/src/private-selectors.ts +43 -0
- package/src/queried-data/actions.js +1 -1
- package/src/queried-data/reducer.js +26 -14
- package/src/reducer.js +36 -1
- package/src/resolvers.js +23 -7
- package/src/selectors.ts +1 -32
- package/src/sync.ts +6 -2
- package/src/test/entities.js +231 -14
- package/src/test/private-actions.js +1 -1
- package/src/test/resolvers.js +64 -11
- package/src/test/store.js +116 -0
- package/src/types.ts +21 -2
- package/src/utils/block-selection-history.ts +5 -2
- package/src/utils/crdt-blocks.ts +250 -81
- package/src/utils/crdt-selection.ts +92 -26
- package/src/utils/crdt-text.ts +43 -0
- package/src/utils/crdt-user-selections.ts +29 -8
- package/src/utils/crdt-utils.ts +99 -0
- package/src/utils/crdt.ts +72 -46
- package/src/utils/test/crdt-blocks.ts +1177 -22
- 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 +423 -60
|
@@ -12,7 +12,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
|
12
12
|
import { CRDT_RECORD_MAP_KEY } from '../sync';
|
|
13
13
|
import type { YPostRecord } from './crdt';
|
|
14
14
|
import type { YBlock, YBlocks } from './crdt-blocks';
|
|
15
|
-
import { getRootMap } from './crdt-utils';
|
|
15
|
+
import { getRootMap, richTextOffsetToHtmlIndex } from './crdt-utils';
|
|
16
16
|
import type {
|
|
17
17
|
AbsoluteBlockIndexPath,
|
|
18
18
|
WPBlockSelection,
|
|
@@ -36,6 +36,16 @@ export enum SelectionType {
|
|
|
36
36
|
WholeBlock = 'whole-block',
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The direction of a text selection, indicating where the caret sits.
|
|
41
|
+
*/
|
|
42
|
+
export enum SelectionDirection {
|
|
43
|
+
/** The caret is at the end of the selection (default / left-to-right). */
|
|
44
|
+
Forward = 'f',
|
|
45
|
+
/** The caret is at the start of the selection (right-to-left). */
|
|
46
|
+
Backward = 'b',
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
/**
|
|
40
50
|
* Converts WordPress block editor selection to a SelectionState.
|
|
41
51
|
*
|
|
@@ -44,16 +54,20 @@ export enum SelectionType {
|
|
|
44
54
|
* differ between the block-editor store and the Yjs document (e.g. in "Show
|
|
45
55
|
* Template" mode).
|
|
46
56
|
*
|
|
47
|
-
* @param selectionStart
|
|
48
|
-
* @param selectionEnd
|
|
49
|
-
* @param yDoc
|
|
57
|
+
* @param selectionStart - The start position of the selection
|
|
58
|
+
* @param selectionEnd - The end position of the selection
|
|
59
|
+
* @param yDoc - The Yjs document
|
|
60
|
+
* @param options - Optional parameters
|
|
61
|
+
* @param options.selectionDirection - The direction of the selection (forward or backward)
|
|
50
62
|
* @return The SelectionState
|
|
51
63
|
*/
|
|
52
64
|
export function getSelectionState(
|
|
53
65
|
selectionStart: WPBlockSelection,
|
|
54
66
|
selectionEnd: WPBlockSelection,
|
|
55
|
-
yDoc: Y.Doc
|
|
67
|
+
yDoc: Y.Doc,
|
|
68
|
+
options?: { selectionDirection?: SelectionDirection }
|
|
56
69
|
): SelectionState {
|
|
70
|
+
const { selectionDirection } = options ?? {};
|
|
57
71
|
const ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );
|
|
58
72
|
const yBlocks = ymap.get( 'blocks' );
|
|
59
73
|
|
|
@@ -122,6 +136,7 @@ export function getSelectionState(
|
|
|
122
136
|
type: SelectionType.SelectionInOneBlock,
|
|
123
137
|
cursorStartPosition,
|
|
124
138
|
cursorEndPosition,
|
|
139
|
+
selectionDirection,
|
|
125
140
|
};
|
|
126
141
|
}
|
|
127
142
|
|
|
@@ -137,6 +152,7 @@ export function getSelectionState(
|
|
|
137
152
|
type: SelectionType.SelectionInMultipleBlocks,
|
|
138
153
|
cursorStartPosition,
|
|
139
154
|
cursorEndPosition,
|
|
155
|
+
selectionDirection,
|
|
140
156
|
};
|
|
141
157
|
}
|
|
142
158
|
|
|
@@ -171,7 +187,7 @@ function getCursorPosition(
|
|
|
171
187
|
|
|
172
188
|
const relativePosition = Y.createRelativePositionFromTypeIndex(
|
|
173
189
|
currentYText,
|
|
174
|
-
selection.offset
|
|
190
|
+
richTextOffsetToHtmlIndex( currentYText.toString(), selection.offset )
|
|
175
191
|
);
|
|
176
192
|
|
|
177
193
|
return {
|
|
@@ -315,7 +331,9 @@ export function areSelectionsStatesEqual(
|
|
|
315
331
|
areCursorPositionsEqual(
|
|
316
332
|
selection1.cursorEndPosition,
|
|
317
333
|
( selection2 as SelectionInOneBlock ).cursorEndPosition
|
|
318
|
-
)
|
|
334
|
+
) &&
|
|
335
|
+
selection1.selectionDirection ===
|
|
336
|
+
( selection2 as SelectionInOneBlock ).selectionDirection
|
|
319
337
|
);
|
|
320
338
|
|
|
321
339
|
case SelectionType.SelectionInMultipleBlocks:
|
|
@@ -329,7 +347,10 @@ export function areSelectionsStatesEqual(
|
|
|
329
347
|
selection1.cursorEndPosition,
|
|
330
348
|
( selection2 as SelectionInMultipleBlocks )
|
|
331
349
|
.cursorEndPosition
|
|
332
|
-
)
|
|
350
|
+
) &&
|
|
351
|
+
selection1.selectionDirection ===
|
|
352
|
+
( selection2 as SelectionInMultipleBlocks )
|
|
353
|
+
.selectionDirection
|
|
333
354
|
);
|
|
334
355
|
case SelectionType.WholeBlock:
|
|
335
356
|
return Y.compareRelativePositions(
|
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
|
@@ -11,6 +11,8 @@ import { __unstableSerializeAndClean } from '@wordpress/blocks';
|
|
|
11
11
|
import {
|
|
12
12
|
type CRDTDoc,
|
|
13
13
|
type ObjectData,
|
|
14
|
+
type ObjectID,
|
|
15
|
+
type ObjectType,
|
|
14
16
|
type SyncConfig,
|
|
15
17
|
Y,
|
|
16
18
|
} from '@wordpress/sync';
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
*/
|
|
21
23
|
import { BaseAwareness } from '../awareness/base-awareness';
|
|
22
24
|
import {
|
|
25
|
+
deserializeBlockAttributes,
|
|
23
26
|
mergeCrdtBlocks,
|
|
24
27
|
mergeRichTextUpdate,
|
|
25
28
|
type Block,
|
|
@@ -27,14 +30,13 @@ import {
|
|
|
27
30
|
type YBlocks,
|
|
28
31
|
} from './crdt-blocks';
|
|
29
32
|
import { type Post } from '../entity-types/post';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
CRDT_DOC_META_PERSISTENCE_KEY,
|
|
33
|
-
CRDT_RECORD_MAP_KEY,
|
|
34
|
-
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
35
|
-
} from '../sync';
|
|
33
|
+
import { CRDT_DOC_META_PERSISTENCE_KEY, CRDT_RECORD_MAP_KEY } from '../sync';
|
|
36
34
|
import type { WPSelection } from '../types';
|
|
37
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
getSelectionHistory,
|
|
37
|
+
getShiftedSelection,
|
|
38
|
+
updateSelectionHistory,
|
|
39
|
+
} from './crdt-selection';
|
|
38
40
|
import {
|
|
39
41
|
createYMap,
|
|
40
42
|
getRootMap,
|
|
@@ -74,30 +76,11 @@ export interface YPostRecord extends YMapRecord {
|
|
|
74
76
|
title: Y.Text;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
const allowedPostProperties = new Set< string >( [
|
|
79
|
-
'author',
|
|
80
|
-
'blocks',
|
|
81
|
-
'content',
|
|
82
|
-
'categories',
|
|
83
|
-
'comment_status',
|
|
84
|
-
'date',
|
|
85
|
-
'excerpt',
|
|
86
|
-
'featured_media',
|
|
87
|
-
'format',
|
|
88
|
-
'meta',
|
|
89
|
-
'ping_status',
|
|
90
|
-
'slug',
|
|
91
|
-
'status',
|
|
92
|
-
'sticky',
|
|
93
|
-
'tags',
|
|
94
|
-
'template',
|
|
95
|
-
'title',
|
|
96
|
-
] );
|
|
79
|
+
export const POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';
|
|
97
80
|
|
|
98
81
|
// Post meta keys that should *not* be synced.
|
|
99
82
|
const disallowedPostMetaKeys = new Set< string >( [
|
|
100
|
-
|
|
83
|
+
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
101
84
|
] );
|
|
102
85
|
|
|
103
86
|
/**
|
|
@@ -137,18 +120,18 @@ function defaultApplyChangesToCRDTDoc(
|
|
|
137
120
|
*
|
|
138
121
|
* @param {CRDTDoc} ydoc
|
|
139
122
|
* @param {PostChanges} changes
|
|
140
|
-
* @param {
|
|
123
|
+
* @param {Set<string>} syncedProperties
|
|
141
124
|
* @return {void}
|
|
142
125
|
*/
|
|
143
126
|
export function applyPostChangesToCRDTDoc(
|
|
144
127
|
ydoc: CRDTDoc,
|
|
145
128
|
changes: PostChanges,
|
|
146
|
-
|
|
129
|
+
syncedProperties: Set< string >
|
|
147
130
|
): void {
|
|
148
131
|
const ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );
|
|
149
132
|
|
|
150
133
|
Object.keys( changes ).forEach( ( key ) => {
|
|
151
|
-
if ( !
|
|
134
|
+
if ( ! syncedProperties.has( key ) ) {
|
|
152
135
|
return;
|
|
153
136
|
}
|
|
154
137
|
|
|
@@ -287,15 +270,15 @@ function defaultGetChangesFromCRDTDoc( crdtDoc: CRDTDoc ): ObjectData {
|
|
|
287
270
|
* against the local record and determine if there are changes (edits) we want
|
|
288
271
|
* to dispatch.
|
|
289
272
|
*
|
|
290
|
-
* @param {CRDTDoc}
|
|
291
|
-
* @param {Post}
|
|
292
|
-
* @param {
|
|
273
|
+
* @param {CRDTDoc} ydoc
|
|
274
|
+
* @param {Post} editedRecord
|
|
275
|
+
* @param {Set<string>} syncedProperties
|
|
293
276
|
* @return {Partial<PostChanges>} The changes that should be applied to the local record.
|
|
294
277
|
*/
|
|
295
278
|
export function getPostChangesFromCRDTDoc(
|
|
296
279
|
ydoc: CRDTDoc,
|
|
297
280
|
editedRecord: Post,
|
|
298
|
-
|
|
281
|
+
syncedProperties: Set< string >
|
|
299
282
|
): PostChanges {
|
|
300
283
|
const ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );
|
|
301
284
|
|
|
@@ -303,7 +286,7 @@ export function getPostChangesFromCRDTDoc(
|
|
|
303
286
|
|
|
304
287
|
const changes = Object.fromEntries(
|
|
305
288
|
Object.entries( ymap.toJSON() ).filter( ( [ key, newValue ] ) => {
|
|
306
|
-
if ( !
|
|
289
|
+
if ( ! syncedProperties.has( key ) ) {
|
|
307
290
|
return false;
|
|
308
291
|
}
|
|
309
292
|
|
|
@@ -340,8 +323,6 @@ export function getPostChangesFromCRDTDoc(
|
|
|
340
323
|
);
|
|
341
324
|
}
|
|
342
325
|
|
|
343
|
-
// The consumers of blocks have memoization that renders optimization
|
|
344
|
-
// here unnecessary.
|
|
345
326
|
return true;
|
|
346
327
|
}
|
|
347
328
|
|
|
@@ -349,11 +330,8 @@ export function getPostChangesFromCRDTDoc(
|
|
|
349
330
|
// Do not overwrite a "floating" date. Borrowing logic from the
|
|
350
331
|
// isEditedPostDateFloating selector.
|
|
351
332
|
const currentDateIsFloating =
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
) &&
|
|
355
|
-
( null === currentValue ||
|
|
356
|
-
editedRecord.modified === currentValue );
|
|
333
|
+
null === currentValue ||
|
|
334
|
+
editedRecord.modified === currentValue;
|
|
357
335
|
|
|
358
336
|
if ( currentDateIsFloating ) {
|
|
359
337
|
return false;
|
|
@@ -363,17 +341,29 @@ export function getPostChangesFromCRDTDoc(
|
|
|
363
341
|
}
|
|
364
342
|
|
|
365
343
|
case 'meta': {
|
|
344
|
+
const currentMeta =
|
|
345
|
+
( currentValue as PostChanges[ 'meta' ] ) ?? {};
|
|
346
|
+
|
|
366
347
|
allowedMetaChanges = Object.fromEntries(
|
|
367
348
|
Object.entries( newValue ?? {} ).filter(
|
|
368
|
-
( [ metaKey ] ) =>
|
|
369
|
-
|
|
349
|
+
( [ metaKey ] ) => {
|
|
350
|
+
if ( disallowedPostMetaKeys.has( metaKey ) ) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Ignore meta keys that are no longer registered
|
|
355
|
+
// for this post (absent from the REST response).
|
|
356
|
+
// Without this, orphaned CRDT meta would mark
|
|
357
|
+
// the post permanently dirty.
|
|
358
|
+
return metaKey in currentMeta;
|
|
359
|
+
}
|
|
370
360
|
)
|
|
371
361
|
);
|
|
372
362
|
|
|
373
363
|
// Merge the allowed meta changes with the current meta values since
|
|
374
364
|
// not all meta properties are synced.
|
|
375
365
|
const mergedValue = {
|
|
376
|
-
...
|
|
366
|
+
...currentMeta,
|
|
377
367
|
...allowedMetaChanges,
|
|
378
368
|
};
|
|
379
369
|
|
|
@@ -407,6 +397,15 @@ export function getPostChangesFromCRDTDoc(
|
|
|
407
397
|
} )
|
|
408
398
|
);
|
|
409
399
|
|
|
400
|
+
// Blocks extracted from the CRDT document have rich-text attributes as
|
|
401
|
+
// plain strings (from Y.Text.toJSON()). Convert them back to RichTextData
|
|
402
|
+
// so block edit components receive the same types as locally-created blocks.
|
|
403
|
+
if ( changes.blocks ) {
|
|
404
|
+
changes.blocks = deserializeBlockAttributes(
|
|
405
|
+
changes.blocks as Block[]
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
410
409
|
// Meta changes must be merged with the edited record since not all meta
|
|
411
410
|
// properties are synced.
|
|
412
411
|
if ( 'object' === typeof changes.meta ) {
|
|
@@ -416,6 +415,20 @@ export function getPostChangesFromCRDTDoc(
|
|
|
416
415
|
};
|
|
417
416
|
}
|
|
418
417
|
|
|
418
|
+
// When remote content changes are detected, recalculate the local user's
|
|
419
|
+
// selection using Y.RelativePosition to account for text shifts. The ydoc
|
|
420
|
+
// has already been updated with remote content at this point, so converting
|
|
421
|
+
// relative positions to absolute gives corrected offsets. Including the
|
|
422
|
+
// selection in PostChanges ensures it dispatches atomically with content.
|
|
423
|
+
const selectionHistory = getSelectionHistory( ydoc );
|
|
424
|
+
const shiftedSelection = getShiftedSelection( ydoc, selectionHistory );
|
|
425
|
+
if ( shiftedSelection ) {
|
|
426
|
+
changes.selection = {
|
|
427
|
+
...shiftedSelection,
|
|
428
|
+
initialPosition: 0,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
419
432
|
return changes;
|
|
420
433
|
}
|
|
421
434
|
|
|
@@ -429,6 +442,19 @@ export const defaultSyncConfig: SyncConfig = {
|
|
|
429
442
|
getChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc,
|
|
430
443
|
};
|
|
431
444
|
|
|
445
|
+
/**
|
|
446
|
+
* This default collection sync config can be used to sync entity collections
|
|
447
|
+
* (e.g., block comments) where we are not interested in merging changes at the
|
|
448
|
+
* individual record level, but instead want to replace the entire collection
|
|
449
|
+
* when changes are detected.
|
|
450
|
+
*/
|
|
451
|
+
export const defaultCollectionSyncConfig: SyncConfig = {
|
|
452
|
+
applyChangesToCRDTDoc: () => {},
|
|
453
|
+
getChangesFromCRDTDoc: () => ( {} ),
|
|
454
|
+
shouldSync: ( _: ObjectType, objectId: ObjectID | null ) =>
|
|
455
|
+
null === objectId,
|
|
456
|
+
};
|
|
457
|
+
|
|
432
458
|
/**
|
|
433
459
|
* Extract the raw string value from a property that may be a string or an object
|
|
434
460
|
* with a `raw` property (`RenderedText`).
|