@wordpress/core-data 7.40.1 → 7.40.2-next.v.202602271551.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.cjs +23 -29
- package/build/actions.cjs.map +2 -2
- package/build/awareness/block-lookup.cjs +103 -0
- package/build/awareness/block-lookup.cjs.map +7 -0
- package/build/awareness/post-editor-awareness.cjs +45 -7
- package/build/awareness/post-editor-awareness.cjs.map +3 -3
- package/build/entities.cjs +63 -59
- package/build/entities.cjs.map +2 -2
- package/build/entity-types/icon.cjs +19 -0
- package/build/entity-types/icon.cjs.map +7 -0
- package/build/entity-types/index.cjs.map +1 -1
- package/build/hooks/use-post-editor-awareness-state.cjs +12 -8
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/private-actions.cjs +0 -8
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +1 -1
- package/build/private-apis.cjs.map +1 -1
- package/build/private-selectors.cjs +1 -9
- package/build/private-selectors.cjs.map +2 -2
- package/build/reducer.cjs +0 -10
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +116 -125
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +1 -7
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs.map +1 -1
- package/build/utils/crdt-blocks.cjs +50 -31
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs +47 -19
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +78 -22
- package/build/utils/crdt-user-selections.cjs.map +3 -3
- package/build/utils/crdt.cjs +12 -1
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/actions.mjs +23 -29
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/block-lookup.mjs +77 -0
- package/build-module/awareness/block-lookup.mjs.map +7 -0
- package/build-module/awareness/post-editor-awareness.mjs +47 -8
- package/build-module/awareness/post-editor-awareness.mjs.map +3 -3
- package/build-module/entities.mjs +65 -60
- package/build-module/entities.mjs.map +2 -2
- package/build-module/entity-types/icon.mjs +1 -0
- package/build-module/entity-types/icon.mjs.map +7 -0
- package/build-module/hooks/use-post-editor-awareness-state.mjs +10 -6
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/private-actions.mjs +0 -7
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +2 -2
- package/build-module/private-apis.mjs.map +1 -1
- package/build-module/private-selectors.mjs +2 -12
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +0 -9
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +116 -124
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +1 -5
- package/build-module/sync.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +50 -31
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs +46 -19
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +77 -22
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +16 -6
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/block-lookup.d.ts +29 -0
- package/build-types/awareness/block-lookup.d.ts.map +1 -0
- package/build-types/awareness/post-editor-awareness.d.ts +18 -5
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/test/block-lookup.d.ts +2 -0
- package/build-types/awareness/test/block-lookup.d.ts.map +1 -0
- package/build-types/entities.d.ts +16 -0
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-types/icon.d.ts +25 -0
- package/build-types/entity-types/icon.d.ts.map +1 -0
- package/build-types/entity-types/index.d.ts +3 -2
- package/build-types/entity-types/index.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +11 -6
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-actions.d.ts +0 -8
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +1 -8
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts +0 -11
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +0 -3
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +0 -6
- 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 +14 -5
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts +1 -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-user-selections.d.ts +21 -4
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +1 -0
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/test/crdt-user-selections.d.ts +2 -0
- package/build-types/utils/test/crdt-user-selections.d.ts.map +1 -0
- package/package.json +18 -18
- package/src/actions.js +39 -45
- package/src/awareness/block-lookup.ts +169 -0
- package/src/awareness/post-editor-awareness.ts +68 -11
- package/src/awareness/test/block-lookup.ts +504 -0
- package/src/awareness/test/post-editor-awareness.ts +662 -38
- package/src/entities.js +71 -62
- package/src/entity-types/icon.ts +30 -0
- package/src/entity-types/index.ts +3 -0
- package/src/hooks/test/use-post-editor-awareness-state.ts +21 -14
- package/src/hooks/use-post-editor-awareness-state.ts +22 -13
- package/src/private-actions.js +0 -14
- package/src/private-apis.js +2 -2
- package/src/private-selectors.ts +3 -22
- package/src/reducer.js +0 -17
- package/src/resolvers.js +158 -171
- package/src/selectors.ts +0 -7
- package/src/sync.ts +0 -4
- package/src/test/entities.js +39 -10
- package/src/test/resolvers.js +155 -81
- package/src/types.ts +23 -5
- package/src/utils/crdt-blocks.ts +113 -47
- package/src/utils/crdt-selection.ts +87 -25
- package/src/utils/crdt-user-selections.ts +129 -47
- package/src/utils/crdt.ts +23 -7
- package/src/utils/test/crdt-blocks.ts +591 -0
- package/src/utils/test/crdt-user-selections.ts +894 -0
- package/src/utils/test/crdt.ts +136 -10
|
@@ -88,6 +88,41 @@ function convertYSelectionToBlockSelection(
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Convert a YFullSelection to a WPSelection by resolving relative positions
|
|
93
|
+
* and verifying the blocks exist in the document.
|
|
94
|
+
* @param yFullSelection The YFullSelection to convert
|
|
95
|
+
* @param ydoc The Y.Doc to resolve positions against
|
|
96
|
+
* @return The converted WPSelection, or null if the conversion fails
|
|
97
|
+
*/
|
|
98
|
+
function convertYFullSelectionToWPSelection(
|
|
99
|
+
yFullSelection: YFullSelection,
|
|
100
|
+
ydoc: Y.Doc
|
|
101
|
+
): WPSelection | null {
|
|
102
|
+
const { start, end } = yFullSelection;
|
|
103
|
+
const startBlock = findBlockByClientIdInDoc( start.clientId, ydoc );
|
|
104
|
+
const endBlock = findBlockByClientIdInDoc( end.clientId, ydoc );
|
|
105
|
+
|
|
106
|
+
if ( ! startBlock || ! endBlock ) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const startBlockSelection = convertYSelectionToBlockSelection(
|
|
111
|
+
start,
|
|
112
|
+
ydoc
|
|
113
|
+
);
|
|
114
|
+
const endBlockSelection = convertYSelectionToBlockSelection( end, ydoc );
|
|
115
|
+
|
|
116
|
+
if ( startBlockSelection === null || endBlockSelection === null ) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
selectionStart: startBlockSelection,
|
|
122
|
+
selectionEnd: endBlockSelection,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
91
126
|
/**
|
|
92
127
|
* Given a Y.Doc and a selection history, find the most recent selection
|
|
93
128
|
* that exists in the document. Skip any selections that are not in the document.
|
|
@@ -99,34 +134,14 @@ function findSelectionFromHistory(
|
|
|
99
134
|
ydoc: Y.Doc,
|
|
100
135
|
selectionHistory: YFullSelection[]
|
|
101
136
|
): WPSelection | null {
|
|
102
|
-
// Try each position until we find one that exists in the document
|
|
103
137
|
for ( const positionToTry of selectionHistory ) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const endBlock = findBlockByClientIdInDoc( end.clientId, ydoc );
|
|
107
|
-
|
|
108
|
-
if ( ! startBlock || ! endBlock ) {
|
|
109
|
-
// This block no longer exists, skip it.
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const startBlockSelection = convertYSelectionToBlockSelection(
|
|
114
|
-
start,
|
|
138
|
+
const result = convertYFullSelectionToWPSelection(
|
|
139
|
+
positionToTry,
|
|
115
140
|
ydoc
|
|
116
141
|
);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ydoc
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
if ( startBlockSelection === null || endBlockSelection === null ) {
|
|
123
|
-
continue;
|
|
142
|
+
if ( result !== null ) {
|
|
143
|
+
return result;
|
|
124
144
|
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
selectionStart: startBlockSelection,
|
|
128
|
-
selectionEnd: endBlockSelection,
|
|
129
|
-
};
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
return null;
|
|
@@ -170,7 +185,9 @@ export function restoreSelection(
|
|
|
170
185
|
const isBeginningOfEmptyBlock =
|
|
171
186
|
0 === selectionStart.offset &&
|
|
172
187
|
0 === selectionEnd.offset &&
|
|
173
|
-
isBlockEmpty
|
|
188
|
+
isBlockEmpty &&
|
|
189
|
+
! selectionStart.attributeKey &&
|
|
190
|
+
! selectionEnd.attributeKey;
|
|
174
191
|
|
|
175
192
|
if ( isBeginningOfEmptyBlock ) {
|
|
176
193
|
// Case 2a: When the content in a block has been removed after an
|
|
@@ -203,3 +220,48 @@ export function restoreSelection(
|
|
|
203
220
|
resetSelection( selectionEnd, selectionEnd, 0 );
|
|
204
221
|
}
|
|
205
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* If the latest selection has been shifted by remote edits, resolve and return
|
|
226
|
+
* it as a WPSelection. Returns null when the history is empty or neither
|
|
227
|
+
* endpoint has moved.
|
|
228
|
+
*
|
|
229
|
+
* @param ydoc The Y.Doc to resolve positions against
|
|
230
|
+
* @param selectionHistory The selection history to check
|
|
231
|
+
* @return The shifted WPSelection, or null if nothing moved.
|
|
232
|
+
*/
|
|
233
|
+
export function getShiftedSelection(
|
|
234
|
+
ydoc: Y.Doc,
|
|
235
|
+
selectionHistory: YFullSelection[]
|
|
236
|
+
): WPSelection | null {
|
|
237
|
+
if ( selectionHistory.length === 0 ) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const { start, end } = selectionHistory[ 0 ];
|
|
242
|
+
|
|
243
|
+
// Block-level selections have no offset that can shift.
|
|
244
|
+
if (
|
|
245
|
+
start.type === YSelectionType.BlockSelection ||
|
|
246
|
+
end.type === YSelectionType.BlockSelection
|
|
247
|
+
) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const selectionStart = convertYSelectionToBlockSelection( start, ydoc );
|
|
252
|
+
const selectionEnd = convertYSelectionToBlockSelection( end, ydoc );
|
|
253
|
+
|
|
254
|
+
if ( ! selectionStart || ! selectionEnd ) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Only dispatch if at least one endpoint actually moved.
|
|
259
|
+
const startShifted = selectionStart.offset !== start.offset;
|
|
260
|
+
const endShifted = selectionEnd.offset !== end.offset;
|
|
261
|
+
|
|
262
|
+
if ( ! startShifted && ! endShifted ) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { selectionStart, selectionEnd };
|
|
267
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
+
import { select } from '@wordpress/data';
|
|
4
5
|
import { Y } from '@wordpress/sync';
|
|
6
|
+
// @ts-ignore No exported types for block editor store selectors.
|
|
7
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Internal dependencies
|
|
@@ -11,6 +14,7 @@ import type { YPostRecord } from './crdt';
|
|
|
11
14
|
import type { YBlock, YBlocks } from './crdt-blocks';
|
|
12
15
|
import { getRootMap } from './crdt-utils';
|
|
13
16
|
import type {
|
|
17
|
+
AbsoluteBlockIndexPath,
|
|
14
18
|
WPBlockSelection,
|
|
15
19
|
SelectionState,
|
|
16
20
|
SelectionNone,
|
|
@@ -35,6 +39,11 @@ export enum SelectionType {
|
|
|
35
39
|
/**
|
|
36
40
|
* Converts WordPress block editor selection to a SelectionState.
|
|
37
41
|
*
|
|
42
|
+
* Uses getBlockPathForLocalClientId to locate blocks in the Yjs document by
|
|
43
|
+
* their tree position (index path) rather than clientId, since clientIds may
|
|
44
|
+
* differ between the block-editor store and the Yjs document (e.g. in "Show
|
|
45
|
+
* Template" mode).
|
|
46
|
+
*
|
|
38
47
|
* @param selectionStart - The start position of the selection
|
|
39
48
|
* @param selectionEnd - The end position of the selection
|
|
40
49
|
* @param yDoc - The Yjs document
|
|
@@ -46,15 +55,15 @@ export function getSelectionState(
|
|
|
46
55
|
yDoc: Y.Doc
|
|
47
56
|
): SelectionState {
|
|
48
57
|
const ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );
|
|
49
|
-
const yBlocks = ymap.get( 'blocks' )
|
|
58
|
+
const yBlocks = ymap.get( 'blocks' );
|
|
50
59
|
|
|
51
60
|
const isSelectionEmpty = Object.keys( selectionStart ).length === 0;
|
|
52
61
|
const noSelection: SelectionNone = {
|
|
53
62
|
type: SelectionType.None,
|
|
54
63
|
};
|
|
55
64
|
|
|
56
|
-
if ( isSelectionEmpty ) {
|
|
57
|
-
// Case 1: No selection
|
|
65
|
+
if ( isSelectionEmpty || ! yBlocks ) {
|
|
66
|
+
// Case 1: No selection, or no blocks in the document.
|
|
58
67
|
return noSelection;
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -70,9 +79,18 @@ export function getSelectionState(
|
|
|
70
79
|
|
|
71
80
|
if ( isSelectionAWholeBlock ) {
|
|
72
81
|
// Case 2: A whole block is selected.
|
|
82
|
+
const path = getBlockPathForLocalClientId( selectionStart.clientId );
|
|
83
|
+
const blockPosition = path
|
|
84
|
+
? createRelativePositionForBlockPath( path, yBlocks )
|
|
85
|
+
: null;
|
|
86
|
+
|
|
87
|
+
if ( ! blockPosition ) {
|
|
88
|
+
return noSelection;
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
return {
|
|
74
92
|
type: SelectionType.WholeBlock,
|
|
75
|
-
|
|
93
|
+
blockPosition,
|
|
76
94
|
};
|
|
77
95
|
} else if ( isCursorOnly ) {
|
|
78
96
|
// Case 3: Cursor only, no text selected
|
|
@@ -85,7 +103,6 @@ export function getSelectionState(
|
|
|
85
103
|
|
|
86
104
|
return {
|
|
87
105
|
type: SelectionType.Cursor,
|
|
88
|
-
blockId: selectionStart.clientId,
|
|
89
106
|
cursorPosition,
|
|
90
107
|
};
|
|
91
108
|
} else if ( isSelectionInOneBlock ) {
|
|
@@ -103,13 +120,12 @@ export function getSelectionState(
|
|
|
103
120
|
|
|
104
121
|
return {
|
|
105
122
|
type: SelectionType.SelectionInOneBlock,
|
|
106
|
-
blockId: selectionStart.clientId,
|
|
107
123
|
cursorStartPosition,
|
|
108
124
|
cursorEndPosition,
|
|
109
125
|
};
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
//
|
|
128
|
+
// Case 5: Selection in multiple blocks
|
|
113
129
|
const cursorStartPosition = getCursorPosition( selectionStart, yBlocks );
|
|
114
130
|
const cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );
|
|
115
131
|
if ( ! cursorStartPosition || ! cursorEndPosition ) {
|
|
@@ -119,8 +135,6 @@ export function getSelectionState(
|
|
|
119
135
|
|
|
120
136
|
return {
|
|
121
137
|
type: SelectionType.SelectionInMultipleBlocks,
|
|
122
|
-
blockStartId: selectionStart.clientId,
|
|
123
|
-
blockEndId: selectionEnd.clientId,
|
|
124
138
|
cursorStartPosition,
|
|
125
139
|
cursorEndPosition,
|
|
126
140
|
};
|
|
@@ -137,7 +151,8 @@ function getCursorPosition(
|
|
|
137
151
|
selection: WPBlockSelection,
|
|
138
152
|
blocks: YBlocks
|
|
139
153
|
): CursorPosition | null {
|
|
140
|
-
const
|
|
154
|
+
const path = getBlockPathForLocalClientId( selection.clientId );
|
|
155
|
+
const block = path ? findBlockByPath( path, blocks ) : null;
|
|
141
156
|
if (
|
|
142
157
|
! block ||
|
|
143
158
|
! selection.attributeKey ||
|
|
@@ -147,7 +162,12 @@ function getCursorPosition(
|
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
const attributes = block.get( 'attributes' );
|
|
150
|
-
const currentYText = attributes?.get( selection.attributeKey )
|
|
165
|
+
const currentYText = attributes?.get( selection.attributeKey );
|
|
166
|
+
|
|
167
|
+
// If the attribute is not a Y.Text, return null.
|
|
168
|
+
if ( ! ( currentYText instanceof Y.Text ) ) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
151
171
|
|
|
152
172
|
const relativePosition = Y.createRelativePositionFromTypeIndex(
|
|
153
173
|
currentYText,
|
|
@@ -161,32 +181,103 @@ function getCursorPosition(
|
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
/**
|
|
164
|
-
*
|
|
184
|
+
* Resolves a local block-editor clientId to its index path relative to the
|
|
185
|
+
* post content blocks. This allows finding the corresponding block in the Yjs
|
|
186
|
+
* document even when clientIds differ (e.g. in "Show Template" mode where
|
|
187
|
+
* blocks are cloned).
|
|
188
|
+
*
|
|
189
|
+
* In template mode, the block tree includes template parts and wrapper blocks
|
|
190
|
+
* around a core/post-content block. The Yjs document only contains the post
|
|
191
|
+
* content blocks, so we stop the upward walk when the parent is
|
|
192
|
+
* core/post-content (its inner blocks correspond to the Yjs root blocks).
|
|
193
|
+
*
|
|
194
|
+
* @param clientId - The local block-editor clientId to resolve.
|
|
195
|
+
* @return The index path from root, or null if not resolvable.
|
|
196
|
+
*/
|
|
197
|
+
export function getBlockPathForLocalClientId(
|
|
198
|
+
clientId: string
|
|
199
|
+
): AbsoluteBlockIndexPath | null {
|
|
200
|
+
const { getBlockIndex, getBlockRootClientId, getBlockName } =
|
|
201
|
+
select( blockEditorStore );
|
|
202
|
+
|
|
203
|
+
const path: AbsoluteBlockIndexPath = [];
|
|
204
|
+
let current: string | null = clientId;
|
|
205
|
+
while ( current ) {
|
|
206
|
+
const index = getBlockIndex( current );
|
|
207
|
+
if ( index === -1 ) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
path.unshift( index );
|
|
211
|
+
const parent = getBlockRootClientId( current );
|
|
212
|
+
if ( ! parent ) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
// If the parent is core/post-content, stop here — the Yjs doc
|
|
216
|
+
// root blocks correspond to post-content's inner blocks.
|
|
217
|
+
const parentName = getBlockName( parent );
|
|
218
|
+
if ( parentName === 'core/post-content' ) {
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
current = parent;
|
|
222
|
+
}
|
|
223
|
+
return path.length > 0 ? path : null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Find a block by navigating a tree index path in the Yjs block hierarchy.
|
|
165
228
|
*
|
|
166
|
-
* @param
|
|
167
|
-
* @param blocks
|
|
168
|
-
* @return The block if found, null otherwise.
|
|
229
|
+
* @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
|
|
230
|
+
* @param blocks - The root-level Yjs blocks array.
|
|
231
|
+
* @return The block Y.Map if found, null otherwise.
|
|
169
232
|
*/
|
|
170
|
-
function
|
|
171
|
-
|
|
233
|
+
function findBlockByPath(
|
|
234
|
+
path: AbsoluteBlockIndexPath,
|
|
172
235
|
blocks: YBlocks
|
|
173
236
|
): YBlock | null {
|
|
174
|
-
|
|
175
|
-
|
|
237
|
+
let currentBlocks = blocks;
|
|
238
|
+
for ( let i = 0; i < path.length; i++ ) {
|
|
239
|
+
if ( path[ i ] >= currentBlocks.length ) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const block = currentBlocks.get( path[ i ] );
|
|
243
|
+
if ( ! block ) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
if ( i === path.length - 1 ) {
|
|
176
247
|
return block;
|
|
177
248
|
}
|
|
249
|
+
currentBlocks =
|
|
250
|
+
block.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
178
254
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Create a Y.RelativePosition for a block by navigating a tree index path.
|
|
257
|
+
*
|
|
258
|
+
* @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
|
|
259
|
+
* @param blocks - The root-level Yjs blocks array.
|
|
260
|
+
* @return A Y.RelativePosition for the block, or null if the path is invalid.
|
|
261
|
+
*/
|
|
262
|
+
function createRelativePositionForBlockPath(
|
|
263
|
+
path: AbsoluteBlockIndexPath,
|
|
264
|
+
blocks: YBlocks
|
|
265
|
+
): Y.RelativePosition | null {
|
|
266
|
+
let currentBlocks = blocks;
|
|
267
|
+
for ( let i = 0; i < path.length; i++ ) {
|
|
268
|
+
if ( path[ i ] >= currentBlocks.length ) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
if ( i === path.length - 1 ) {
|
|
272
|
+
return Y.createRelativePositionFromTypeIndex(
|
|
273
|
+
currentBlocks,
|
|
274
|
+
path[ i ]
|
|
275
|
+
);
|
|
187
276
|
}
|
|
277
|
+
const block = currentBlocks.get( path[ i ] );
|
|
278
|
+
currentBlocks =
|
|
279
|
+
block?.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );
|
|
188
280
|
}
|
|
189
|
-
|
|
190
281
|
return null;
|
|
191
282
|
}
|
|
192
283
|
|
|
@@ -210,19 +301,13 @@ export function areSelectionsStatesEqual(
|
|
|
210
301
|
return true;
|
|
211
302
|
|
|
212
303
|
case SelectionType.Cursor:
|
|
213
|
-
return (
|
|
214
|
-
selection1.
|
|
215
|
-
|
|
216
|
-
areCursorPositionsEqual(
|
|
217
|
-
selection1.cursorPosition,
|
|
218
|
-
( selection2 as SelectionCursor ).cursorPosition
|
|
219
|
-
)
|
|
304
|
+
return areCursorPositionsEqual(
|
|
305
|
+
selection1.cursorPosition,
|
|
306
|
+
( selection2 as SelectionCursor ).cursorPosition
|
|
220
307
|
);
|
|
221
308
|
|
|
222
309
|
case SelectionType.SelectionInOneBlock:
|
|
223
310
|
return (
|
|
224
|
-
selection1.blockId ===
|
|
225
|
-
( selection2 as SelectionInOneBlock ).blockId &&
|
|
226
311
|
areCursorPositionsEqual(
|
|
227
312
|
selection1.cursorStartPosition,
|
|
228
313
|
( selection2 as SelectionInOneBlock ).cursorStartPosition
|
|
@@ -235,10 +320,6 @@ export function areSelectionsStatesEqual(
|
|
|
235
320
|
|
|
236
321
|
case SelectionType.SelectionInMultipleBlocks:
|
|
237
322
|
return (
|
|
238
|
-
selection1.blockStartId ===
|
|
239
|
-
( selection2 as SelectionInMultipleBlocks ).blockStartId &&
|
|
240
|
-
selection1.blockEndId ===
|
|
241
|
-
( selection2 as SelectionInMultipleBlocks ).blockEndId &&
|
|
242
323
|
areCursorPositionsEqual(
|
|
243
324
|
selection1.cursorStartPosition,
|
|
244
325
|
( selection2 as SelectionInMultipleBlocks )
|
|
@@ -251,9 +332,9 @@ export function areSelectionsStatesEqual(
|
|
|
251
332
|
)
|
|
252
333
|
);
|
|
253
334
|
case SelectionType.WholeBlock:
|
|
254
|
-
return (
|
|
255
|
-
selection1.
|
|
256
|
-
( selection2 as SelectionWholeBlock ).
|
|
335
|
+
return Y.compareRelativePositions(
|
|
336
|
+
selection1.blockPosition,
|
|
337
|
+
( selection2 as SelectionWholeBlock ).blockPosition
|
|
257
338
|
);
|
|
258
339
|
|
|
259
340
|
default:
|
|
@@ -272,9 +353,10 @@ function areCursorPositionsEqual(
|
|
|
272
353
|
cursorPosition1: CursorPosition,
|
|
273
354
|
cursorPosition2: CursorPosition
|
|
274
355
|
): boolean {
|
|
275
|
-
const isRelativePositionEqual =
|
|
276
|
-
|
|
277
|
-
|
|
356
|
+
const isRelativePositionEqual = Y.compareRelativePositions(
|
|
357
|
+
cursorPosition1.relativePosition,
|
|
358
|
+
cursorPosition2.relativePosition
|
|
359
|
+
);
|
|
278
360
|
|
|
279
361
|
// Ensure a change in calculated absolute offset results in a treating the cursor as modified.
|
|
280
362
|
// This is necessary because Y.Text relative positions can remain the same after text changes.
|
package/src/utils/crdt.ts
CHANGED
|
@@ -28,13 +28,13 @@ import {
|
|
|
28
28
|
} from './crdt-blocks';
|
|
29
29
|
import { type Post } from '../entity-types/post';
|
|
30
30
|
import { type Type } from '../entity-types';
|
|
31
|
-
import {
|
|
32
|
-
CRDT_DOC_META_PERSISTENCE_KEY,
|
|
33
|
-
CRDT_RECORD_MAP_KEY,
|
|
34
|
-
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
35
|
-
} from '../sync';
|
|
31
|
+
import { CRDT_DOC_META_PERSISTENCE_KEY, CRDT_RECORD_MAP_KEY } from '../sync';
|
|
36
32
|
import type { WPSelection } from '../types';
|
|
37
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
getSelectionHistory,
|
|
35
|
+
getShiftedSelection,
|
|
36
|
+
updateSelectionHistory,
|
|
37
|
+
} from './crdt-selection';
|
|
38
38
|
import {
|
|
39
39
|
createYMap,
|
|
40
40
|
getRootMap,
|
|
@@ -74,6 +74,8 @@ export interface YPostRecord extends YMapRecord {
|
|
|
74
74
|
title: Y.Text;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
export const POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';
|
|
78
|
+
|
|
77
79
|
// Properties that are allowed to be synced for a post.
|
|
78
80
|
const allowedPostProperties = new Set< string >( [
|
|
79
81
|
'author',
|
|
@@ -97,7 +99,7 @@ const allowedPostProperties = new Set< string >( [
|
|
|
97
99
|
|
|
98
100
|
// Post meta keys that should *not* be synced.
|
|
99
101
|
const disallowedPostMetaKeys = new Set< string >( [
|
|
100
|
-
|
|
102
|
+
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
101
103
|
] );
|
|
102
104
|
|
|
103
105
|
/**
|
|
@@ -416,6 +418,20 @@ export function getPostChangesFromCRDTDoc(
|
|
|
416
418
|
};
|
|
417
419
|
}
|
|
418
420
|
|
|
421
|
+
// When remote content changes are detected, recalculate the local user's
|
|
422
|
+
// selection using Y.RelativePosition to account for text shifts. The ydoc
|
|
423
|
+
// has already been updated with remote content at this point, so converting
|
|
424
|
+
// relative positions to absolute gives corrected offsets. Including the
|
|
425
|
+
// selection in PostChanges ensures it dispatches atomically with content.
|
|
426
|
+
const selectionHistory = getSelectionHistory( ydoc );
|
|
427
|
+
const shiftedSelection = getShiftedSelection( ydoc, selectionHistory );
|
|
428
|
+
if ( shiftedSelection ) {
|
|
429
|
+
changes.selection = {
|
|
430
|
+
...shiftedSelection,
|
|
431
|
+
initialPosition: 0,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
419
435
|
return changes;
|
|
420
436
|
}
|
|
421
437
|
|