@wordpress/core-data 7.38.0 → 7.38.1-next.v.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/awareness/config.cjs +34 -0
- package/build/awareness/config.cjs.map +7 -0
- package/build/awareness/post-editor-awareness.cjs +144 -0
- package/build/awareness/post-editor-awareness.cjs.map +7 -0
- package/build/awareness/types.cjs +19 -0
- package/build/awareness/types.cjs.map +7 -0
- package/build/awareness/utils.cjs +116 -0
- package/build/awareness/utils.cjs.map +7 -0
- package/build/entities.cjs +13 -0
- package/build/entities.cjs.map +2 -2
- package/build/resolvers.cjs +3 -2
- package/build/resolvers.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +171 -0
- package/build/utils/crdt-user-selections.cjs.map +7 -0
- package/build-module/awareness/config.mjs +8 -0
- package/build-module/awareness/config.mjs.map +7 -0
- package/build-module/awareness/post-editor-awareness.mjs +125 -0
- package/build-module/awareness/post-editor-awareness.mjs.map +7 -0
- package/build-module/awareness/types.mjs +1 -0
- package/build-module/awareness/types.mjs.map +7 -0
- package/build-module/awareness/utils.mjs +90 -0
- package/build-module/awareness/utils.mjs.map +7 -0
- package/build-module/entities.mjs +13 -0
- package/build-module/entities.mjs.map +2 -2
- package/build-module/resolvers.mjs +3 -2
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +144 -0
- package/build-module/utils/crdt-user-selections.mjs.map +7 -0
- package/build-types/awareness/config.d.ts +9 -0
- package/build-types/awareness/config.d.ts.map +1 -0
- package/build-types/awareness/post-editor-awareness.d.ts +39 -0
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -0
- package/build-types/awareness/types.d.ts +32 -0
- package/build-types/awareness/types.d.ts.map +1 -0
- package/build-types/awareness/utils.d.ts +22 -0
- package/build-types/awareness/utils.d.ts.map +1 -0
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts +66 -0
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -0
- package/package.json +19 -18
- package/src/awareness/config.ts +9 -0
- package/src/awareness/post-editor-awareness.ts +187 -0
- package/src/awareness/types.ts +38 -0
- package/src/awareness/utils.ts +159 -0
- package/src/entities.js +14 -0
- package/src/resolvers.js +2 -1
- package/src/test/entity-provider.js +2 -0
- package/src/utils/crdt-user-selections.ts +332 -0
- package/src/utils/test/crdt-blocks.ts +11 -4
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// packages/core-data/src/utils/crdt-user-selections.ts
|
|
2
|
+
import { Y, CRDT_RECORD_MAP_KEY } from "@wordpress/sync";
|
|
3
|
+
import { getRootMap } from "./crdt-utils.mjs";
|
|
4
|
+
var SelectionType = /* @__PURE__ */ ((SelectionType2) => {
|
|
5
|
+
SelectionType2["None"] = "none";
|
|
6
|
+
SelectionType2["Cursor"] = "cursor";
|
|
7
|
+
SelectionType2["SelectionInOneBlock"] = "selection-in-one-block";
|
|
8
|
+
SelectionType2["SelectionInMultipleBlocks"] = "selection-in-multiple-blocks";
|
|
9
|
+
SelectionType2["WholeBlock"] = "whole-block";
|
|
10
|
+
return SelectionType2;
|
|
11
|
+
})(SelectionType || {});
|
|
12
|
+
function getSelectionState(selectionStart, selectionEnd, yDoc) {
|
|
13
|
+
const ymap = getRootMap(yDoc, CRDT_RECORD_MAP_KEY);
|
|
14
|
+
const yBlocks = ymap.get("blocks") ?? new Y.Array();
|
|
15
|
+
const isSelectionEmpty = Object.keys(selectionStart).length === 0;
|
|
16
|
+
const noSelection = {
|
|
17
|
+
type: "none" /* None */
|
|
18
|
+
};
|
|
19
|
+
if (isSelectionEmpty) {
|
|
20
|
+
return noSelection;
|
|
21
|
+
}
|
|
22
|
+
const isSelectionInOneBlock = selectionStart.clientId === selectionEnd.clientId;
|
|
23
|
+
const isCursorOnly = isSelectionInOneBlock && selectionStart.offset === selectionEnd.offset;
|
|
24
|
+
const isSelectionAWholeBlock = isSelectionInOneBlock && selectionStart.offset === void 0 && selectionEnd.offset === void 0;
|
|
25
|
+
if (isSelectionAWholeBlock) {
|
|
26
|
+
return {
|
|
27
|
+
type: "whole-block" /* WholeBlock */,
|
|
28
|
+
blockId: selectionStart.clientId
|
|
29
|
+
};
|
|
30
|
+
} else if (isCursorOnly) {
|
|
31
|
+
const cursorPosition = getCursorPosition(selectionStart, yBlocks);
|
|
32
|
+
if (!cursorPosition) {
|
|
33
|
+
return noSelection;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
type: "cursor" /* Cursor */,
|
|
37
|
+
blockId: selectionStart.clientId,
|
|
38
|
+
cursorPosition
|
|
39
|
+
};
|
|
40
|
+
} else if (isSelectionInOneBlock) {
|
|
41
|
+
const cursorStartPosition2 = getCursorPosition(
|
|
42
|
+
selectionStart,
|
|
43
|
+
yBlocks
|
|
44
|
+
);
|
|
45
|
+
const cursorEndPosition2 = getCursorPosition(selectionEnd, yBlocks);
|
|
46
|
+
if (!cursorStartPosition2 || !cursorEndPosition2) {
|
|
47
|
+
return noSelection;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
type: "selection-in-one-block" /* SelectionInOneBlock */,
|
|
51
|
+
blockId: selectionStart.clientId,
|
|
52
|
+
cursorStartPosition: cursorStartPosition2,
|
|
53
|
+
cursorEndPosition: cursorEndPosition2
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const cursorStartPosition = getCursorPosition(selectionStart, yBlocks);
|
|
57
|
+
const cursorEndPosition = getCursorPosition(selectionEnd, yBlocks);
|
|
58
|
+
if (!cursorStartPosition || !cursorEndPosition) {
|
|
59
|
+
return noSelection;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
type: "selection-in-multiple-blocks" /* SelectionInMultipleBlocks */,
|
|
63
|
+
blockStartId: selectionStart.clientId,
|
|
64
|
+
blockEndId: selectionEnd.clientId,
|
|
65
|
+
cursorStartPosition,
|
|
66
|
+
cursorEndPosition
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function getCursorPosition(selection, blocks) {
|
|
70
|
+
const block = findBlockByClientId(selection.clientId, blocks);
|
|
71
|
+
if (!block) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const attributes = block.get("attributes");
|
|
75
|
+
const currentYText = attributes.get(selection.attributeKey);
|
|
76
|
+
const relativePosition = Y.createRelativePositionFromTypeIndex(
|
|
77
|
+
currentYText,
|
|
78
|
+
selection.offset
|
|
79
|
+
);
|
|
80
|
+
return {
|
|
81
|
+
relativePosition,
|
|
82
|
+
absoluteOffset: selection.offset
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function findBlockByClientId(blockId, blocks) {
|
|
86
|
+
for (const block of blocks) {
|
|
87
|
+
if (block.get("clientId") === blockId) {
|
|
88
|
+
return block;
|
|
89
|
+
}
|
|
90
|
+
const innerBlocks = block.get("innerBlocks");
|
|
91
|
+
if (innerBlocks && innerBlocks.length > 0) {
|
|
92
|
+
const innerBlock = findBlockByClientId(blockId, innerBlocks);
|
|
93
|
+
if (innerBlock) {
|
|
94
|
+
return innerBlock;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function areSelectionsStatesEqual(selection1, selection2) {
|
|
101
|
+
if (selection1.type !== selection2.type) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
switch (selection1.type) {
|
|
105
|
+
case "none" /* None */:
|
|
106
|
+
return true;
|
|
107
|
+
case "cursor" /* Cursor */:
|
|
108
|
+
return selection1.blockId === selection2.blockId && areCursorPositionsEqual(
|
|
109
|
+
selection1.cursorPosition,
|
|
110
|
+
selection2.cursorPosition
|
|
111
|
+
);
|
|
112
|
+
case "selection-in-one-block" /* SelectionInOneBlock */:
|
|
113
|
+
return selection1.blockId === selection2.blockId && areCursorPositionsEqual(
|
|
114
|
+
selection1.cursorStartPosition,
|
|
115
|
+
selection2.cursorStartPosition
|
|
116
|
+
) && areCursorPositionsEqual(
|
|
117
|
+
selection1.cursorEndPosition,
|
|
118
|
+
selection2.cursorEndPosition
|
|
119
|
+
);
|
|
120
|
+
case "selection-in-multiple-blocks" /* SelectionInMultipleBlocks */:
|
|
121
|
+
return selection1.blockStartId === selection2.blockStartId && selection1.blockEndId === selection2.blockEndId && areCursorPositionsEqual(
|
|
122
|
+
selection1.cursorStartPosition,
|
|
123
|
+
selection2.cursorStartPosition
|
|
124
|
+
) && areCursorPositionsEqual(
|
|
125
|
+
selection1.cursorEndPosition,
|
|
126
|
+
selection2.cursorEndPosition
|
|
127
|
+
);
|
|
128
|
+
case "whole-block" /* WholeBlock */:
|
|
129
|
+
return selection1.blockId === selection2.blockId;
|
|
130
|
+
default:
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function areCursorPositionsEqual(cursorPosition1, cursorPosition2) {
|
|
135
|
+
const isRelativePositionEqual = JSON.stringify(cursorPosition1.relativePosition) === JSON.stringify(cursorPosition2.relativePosition);
|
|
136
|
+
const isAbsoluteOffsetEqual = cursorPosition1.absoluteOffset === cursorPosition2.absoluteOffset;
|
|
137
|
+
return isRelativePositionEqual && isAbsoluteOffsetEqual;
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
SelectionType,
|
|
141
|
+
areSelectionsStatesEqual,
|
|
142
|
+
getSelectionState
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=crdt-user-selections.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/crdt-user-selections.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport { Y, CRDT_RECORD_MAP_KEY } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport type { YPostRecord } from './crdt';\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport { getRootMap } from './crdt-utils';\nimport type { WPBlockSelection } from '../types';\n\n/**\n * The type of selection.\n */\nexport enum SelectionType {\n\tNone = 'none',\n\tCursor = 'cursor',\n\tSelectionInOneBlock = 'selection-in-one-block',\n\tSelectionInMultipleBlocks = 'selection-in-multiple-blocks',\n\tWholeBlock = 'whole-block',\n}\n\n/**\n * The position of the cursor.\n */\nexport type CursorPosition = {\n\trelativePosition: Y.RelativePosition;\n\n\t// Also store the absolute offset index of the cursor from the perspective\n\t// of the user who is updating the selection.\n\t//\n\t// Do not use this value directly, instead use `createAbsolutePositionFromRelativePosition()`\n\t// on relativePosition for the most up-to-date positioning.\n\t//\n\t// This is used because local Y.Text changes (e.g. adding or deleting a character)\n\t// can result in the same relative position if it is pinned to an unchanged\n\t// character. With both of these values as editor state, a change in perceived\n\t// position will always result in a redraw.\n\tabsoluteOffset: number;\n};\n\nexport type SelectionNone = {\n\t// The user has not made a selection.\n\ttype: SelectionType.None;\n};\n\nexport type SelectionCursor = {\n\t// The user has a cursor position in a block with no text highlighted.\n\ttype: SelectionType.Cursor;\n\tblockId: string;\n\tcursorPosition: CursorPosition;\n};\n\nexport type SelectionInOneBlock = {\n\t// The user has highlighted text in a single block.\n\ttype: SelectionType.SelectionInOneBlock;\n\tblockId: string;\n\tcursorStartPosition: CursorPosition;\n\tcursorEndPosition: CursorPosition;\n};\n\nexport type SelectionInMultipleBlocks = {\n\t// The user has highlighted text over multiple blocks.\n\ttype: SelectionType.SelectionInMultipleBlocks;\n\tblockStartId: string;\n\tblockEndId: string;\n\tcursorStartPosition: CursorPosition;\n\tcursorEndPosition: CursorPosition;\n};\n\nexport type SelectionWholeBlock = {\n\t// The user has a non-text block selected, like an image block.\n\ttype: SelectionType.WholeBlock;\n\tblockId: string;\n};\n\nexport type SelectionState =\n\t| SelectionNone\n\t| SelectionCursor\n\t| SelectionInOneBlock\n\t| SelectionInMultipleBlocks\n\t| SelectionWholeBlock;\n\n/**\n * Converts WordPress block editor selection to a SelectionState.\n *\n * @param selectionStart - The start position of the selection\n * @param selectionEnd - The end position of the selection\n * @param yDoc - The Yjs document\n * @return The SelectionState\n */\nexport function getSelectionState(\n\tselectionStart: WPBlockSelection,\n\tselectionEnd: WPBlockSelection,\n\tyDoc: Y.Doc\n): SelectionState {\n\tconst ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );\n\tconst yBlocks = ymap.get( 'blocks' ) ?? new Y.Array< YBlock >();\n\n\tconst isSelectionEmpty = Object.keys( selectionStart ).length === 0;\n\tconst noSelection: SelectionNone = {\n\t\ttype: SelectionType.None,\n\t};\n\n\tif ( isSelectionEmpty ) {\n\t\t// Case 1: No selection\n\t\treturn noSelection;\n\t}\n\n\t// When the page initially loads, selectionStart can contain an empty object `{}`.\n\tconst isSelectionInOneBlock =\n\t\tselectionStart.clientId === selectionEnd.clientId;\n\tconst isCursorOnly =\n\t\tisSelectionInOneBlock && selectionStart.offset === selectionEnd.offset;\n\tconst isSelectionAWholeBlock =\n\t\tisSelectionInOneBlock &&\n\t\tselectionStart.offset === undefined &&\n\t\tselectionEnd.offset === undefined;\n\n\tif ( isSelectionAWholeBlock ) {\n\t\t// Case 2: A whole block is selected.\n\t\treturn {\n\t\t\ttype: SelectionType.WholeBlock,\n\t\t\tblockId: selectionStart.clientId,\n\t\t};\n\t} else if ( isCursorOnly ) {\n\t\t// Case 3: Cursor only, no text selected\n\t\tconst cursorPosition = getCursorPosition( selectionStart, yBlocks );\n\n\t\tif ( ! cursorPosition ) {\n\t\t\t// If we can't find the cursor position in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.Cursor,\n\t\t\tblockId: selectionStart.clientId,\n\t\t\tcursorPosition,\n\t\t};\n\t} else if ( isSelectionInOneBlock ) {\n\t\t// Case 4: Selection in a single block\n\t\tconst cursorStartPosition = getCursorPosition(\n\t\t\tselectionStart,\n\t\t\tyBlocks\n\t\t);\n\t\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\n\t\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.SelectionInOneBlock,\n\t\t\tblockId: selectionStart.clientId,\n\t\t\tcursorStartPosition,\n\t\t\tcursorEndPosition,\n\t\t};\n\t}\n\n\t// Caes 5: Selection in multiple blocks\n\tconst cursorStartPosition = getCursorPosition( selectionStart, yBlocks );\n\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\treturn noSelection;\n\t}\n\n\treturn {\n\t\ttype: SelectionType.SelectionInMultipleBlocks,\n\t\tblockStartId: selectionStart.clientId,\n\t\tblockEndId: selectionEnd.clientId,\n\t\tcursorStartPosition,\n\t\tcursorEndPosition,\n\t};\n}\n\n/**\n * Get the cursor position from a selection.\n *\n * @param selection - The selection.\n * @param blocks - The blocks to search through.\n * @return The cursor position, or null if not found.\n */\nfunction getCursorPosition(\n\tselection: WPBlockSelection,\n\tblocks: YBlocks\n): CursorPosition | null {\n\tconst block = findBlockByClientId( selection.clientId, blocks );\n\tif ( ! block ) {\n\t\treturn null;\n\t}\n\n\tconst attributes = block.get( 'attributes' ) as Y.Map< Y.Text >;\n\tconst currentYText = attributes.get( selection.attributeKey ) as Y.Text;\n\n\tconst relativePosition = Y.createRelativePositionFromTypeIndex(\n\t\tcurrentYText,\n\t\tselection.offset\n\t);\n\n\treturn {\n\t\trelativePosition,\n\t\tabsoluteOffset: selection.offset,\n\t};\n}\n\n/**\n * Find a block by its client ID.\n *\n * @param blockId - The client ID of the block.\n * @param blocks - The blocks to search through.\n * @return The block if found, null otherwise.\n */\nfunction findBlockByClientId(\n\tblockId: string,\n\tblocks: YBlocks\n): YBlock | null {\n\tfor ( const block of blocks ) {\n\t\tif ( block.get( 'clientId' ) === blockId ) {\n\t\t\treturn block;\n\t\t}\n\n\t\tconst innerBlocks = block.get( 'innerBlocks' );\n\n\t\tif ( innerBlocks && innerBlocks.length > 0 ) {\n\t\t\tconst innerBlock = findBlockByClientId( blockId, innerBlocks );\n\n\t\t\tif ( innerBlock ) {\n\t\t\t\treturn innerBlock;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Check if two selection states are equal.\n *\n * @param selection1 - The first selection state.\n * @param selection2 - The second selection state.\n * @return True if the selection states are equal, false otherwise.\n */\nexport function areSelectionsStatesEqual(\n\tselection1: SelectionState,\n\tselection2: SelectionState\n): boolean {\n\tif ( selection1.type !== selection2.type ) {\n\t\treturn false;\n\t}\n\n\tswitch ( selection1.type ) {\n\t\tcase SelectionType.None:\n\t\t\treturn true;\n\n\t\tcase SelectionType.Cursor:\n\t\t\treturn (\n\t\t\t\tselection1.blockId ===\n\t\t\t\t\t( selection2 as SelectionCursor ).blockId &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorPosition,\n\t\t\t\t\t( selection2 as SelectionCursor ).cursorPosition\n\t\t\t\t)\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInOneBlock:\n\t\t\treturn (\n\t\t\t\tselection1.blockId ===\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).blockId &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorEndPosition\n\t\t\t\t)\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInMultipleBlocks:\n\t\t\treturn (\n\t\t\t\tselection1.blockStartId ===\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks ).blockStartId &&\n\t\t\t\tselection1.blockEndId ===\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks ).blockEndId &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorEndPosition\n\t\t\t\t)\n\t\t\t);\n\t\tcase SelectionType.WholeBlock:\n\t\t\treturn (\n\t\t\t\tselection1.blockId ===\n\t\t\t\t( selection2 as SelectionWholeBlock ).blockId\n\t\t\t);\n\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Check if two cursor positions are equal.\n *\n * @param cursorPosition1 - The first cursor position.\n * @param cursorPosition2 - The second cursor position.\n * @return True if the cursor positions are equal, false otherwise.\n */\nfunction areCursorPositionsEqual(\n\tcursorPosition1: CursorPosition,\n\tcursorPosition2: CursorPosition\n): boolean {\n\tconst isRelativePositionEqual =\n\t\tJSON.stringify( cursorPosition1.relativePosition ) ===\n\t\tJSON.stringify( cursorPosition2.relativePosition );\n\n\t// Ensure a change in calculated absolute offset results in a treating the cursor as modified.\n\t// This is necessary because Y.Text relative positions can remain the same after text changes.\n\tconst isAbsoluteOffsetEqual =\n\t\tcursorPosition1.absoluteOffset === cursorPosition2.absoluteOffset;\n\n\treturn isRelativePositionEqual && isAbsoluteOffsetEqual;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["SelectionType", "cursorStartPosition", "cursorEndPosition"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delay in milliseconds before throttling the cursor position updates.
|
|
3
|
+
*/
|
|
4
|
+
export declare const AWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS = 100;
|
|
5
|
+
/**
|
|
6
|
+
* Delay in milliseconds before updating the cursor position.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS = 5;
|
|
9
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/awareness/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,kCAAkC,IAAI,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AwarenessState, type Y } from '@wordpress/sync';
|
|
2
|
+
import { areUserInfosEqual } from './utils';
|
|
3
|
+
import type { EditorState, PostEditorState } from './types';
|
|
4
|
+
export declare class PostEditorAwareness extends AwarenessState<PostEditorState> {
|
|
5
|
+
private kind;
|
|
6
|
+
private name;
|
|
7
|
+
private postId;
|
|
8
|
+
protected equalityFieldChecks: {
|
|
9
|
+
editorState: (state1?: EditorState, state2?: EditorState) => boolean;
|
|
10
|
+
userInfo: typeof areUserInfosEqual;
|
|
11
|
+
};
|
|
12
|
+
constructor(doc: Y.Doc, kind: string, name: string, postId: number);
|
|
13
|
+
setUp(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Set the current user info in the local state.
|
|
16
|
+
*/
|
|
17
|
+
private setCurrentUserInfo;
|
|
18
|
+
/**
|
|
19
|
+
* Subscribe to user selection changes and update the selection state.
|
|
20
|
+
*/
|
|
21
|
+
private subscribeToUserSelectionChanges;
|
|
22
|
+
/**
|
|
23
|
+
* Update the entity record with the current user's selection.
|
|
24
|
+
*
|
|
25
|
+
* @param selectionStart - The start position of the selection.
|
|
26
|
+
* @param selectionEnd - The end position of the selection.
|
|
27
|
+
* @param initialPosition - The initial position of the selection.
|
|
28
|
+
*/
|
|
29
|
+
private updateSelectionInEntityRecord;
|
|
30
|
+
/**
|
|
31
|
+
* Check if two editor states are equal.
|
|
32
|
+
*
|
|
33
|
+
* @param state1 - The first editor state.
|
|
34
|
+
* @param state2 - The second editor state.
|
|
35
|
+
* @return True if the editor states are equal, false otherwise.
|
|
36
|
+
*/
|
|
37
|
+
private areEditorStatesEqual;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=post-editor-awareness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-editor-awareness.d.ts","sourceRoot":"","sources":["../../src/awareness/post-editor-awareness.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAYzD,OAAO,EAAoB,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAO9D,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5D,qBAAa,mBAAoB,SAAQ,cAAc,CAAE,eAAe,CAAE;IAQxE,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,MAAM;IATf,SAAS,CAAC,mBAAmB;+BAuJnB,WAAW,WACX,WAAW,KAClB,OAAO;;MAtJR;gBAGD,GAAG,EAAE,CAAC,CAAC,GAAG,EACF,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM;IAKhB,KAAK,IAAI,IAAI;IAOpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgB1B;;OAEG;IACH,OAAO,CAAC,+BAA+B;IA8DvC;;;;;;OAMG;YACW,6BAA6B;IA+B3C;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;CAU5B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { SelectionState } from '../utils/crdt-user-selections';
|
|
5
|
+
import type { User } from '../entity-types';
|
|
6
|
+
export type UserInfo = Pick<User<'view'>, 'id' | 'name' | 'slug' | 'avatar_urls'> & {
|
|
7
|
+
browserType: string;
|
|
8
|
+
color: string;
|
|
9
|
+
enteredAt: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* This base state represents the presence of the user. We expect it to be
|
|
13
|
+
* extended to include additional state describing the user's current activity.
|
|
14
|
+
* This state must be serializable and compact.
|
|
15
|
+
*/
|
|
16
|
+
export interface BaseState {
|
|
17
|
+
userInfo: UserInfo;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The editor state includes information about the user's current selection.
|
|
21
|
+
*/
|
|
22
|
+
export interface EditorState {
|
|
23
|
+
selection: SelectionState;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The post editor state extends the base state with information used to render
|
|
27
|
+
* presence indicators in the post editor.
|
|
28
|
+
*/
|
|
29
|
+
export interface PostEditorState extends BaseState {
|
|
30
|
+
editorState?: EditorState;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/awareness/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG,IAAI,CAC1B,IAAI,CAAE,MAAM,CAAE,EACd,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CACtC,GAAG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,EAAE,QAAQ,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,cAAc,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { User } from '../entity-types';
|
|
5
|
+
import type { UserInfo } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Check if two user infos are equal.
|
|
8
|
+
*
|
|
9
|
+
* @param userInfo1 - The first user info.
|
|
10
|
+
* @param userInfo2 - The second user info.
|
|
11
|
+
* @return True if the user infos are equal, false otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export declare function areUserInfosEqual(userInfo1?: UserInfo, userInfo2?: UserInfo): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Generate a user info object from a current user and a list of existing colors.
|
|
16
|
+
*
|
|
17
|
+
* @param currentUser - The current user.
|
|
18
|
+
* @param existingColors - The existing colors.
|
|
19
|
+
* @return The user info object.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateUserInfo(currentUser: User<'view'>, existingColors: string[]): UserInfo;
|
|
22
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/awareness/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgHxC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAChC,SAAS,CAAC,EAAE,QAAQ,EACpB,SAAS,CAAC,EAAE,QAAQ,GAClB,OAAO,CAaT;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,WAAW,EAAE,IAAI,CAAE,MAAM,CAAE,EAC3B,cAAc,EAAE,MAAM,EAAE,GACtB,QAAQ,CAOV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../src/entities.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../src/entities.js"],"names":[],"mappings":"AAsBA,iCAAkC,IAAI,CAAC;AAYvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2LE;;;;;;;;;;;;AAcF;;;;;;;;;;KASE;AAWK,2EAJI,MAAM,cACN,OAAO,OA0CjB;AAwLM,oCANI,MAAM,QACN,MAAM,WACN,MAAM,GAEL,MAAM,CAMjB;AA1LD;;;;GAIG;AACH,sDAqGC;AAyBD;;;;GAIG;AACH,gDA0BC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.js"],"names":[],"mappings":"AAmCO,kCAHI,MAAO,SAAS,IAKlB;;CAAY,mBAOnB;AAKK,mCAEE;;CAAY,mBAGnB;AAYK,sCAPI,MAAM,QACN,MAAM,OACN,MAAM,GAAC,MAAM,qBACb,MAAO,SAAS,IAMlB;;;;;CAA6C,
|
|
1
|
+
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.js"],"names":[],"mappings":"AAmCO,kCAHI,MAAO,SAAS,IAKlB;;CAAY,mBAOnB;AAKK,mCAEE;;CAAY,mBAGnB;AAYK,sCAPI,MAAM,QACN,MAAM,OACN,MAAM,GAAC,MAAM,qBACb,MAAO,SAAS,IAMlB;;;;;CAA6C,mBAoLpD;;IAIF,kEAcC;;AAED;;GAEG;AACH,0CAAuE;AAEvE;;GAEG;AACH,6CAA0E;AAUnE,uCALI,MAAM,QACN,MAAM,UACN,UAAO,IAKT;;;;CAAqC,mBA+N5C;;IAEF,kEAOC;;AAED;;GAEG;AACH,kDAAgF;AAEhF;;GAEG;AACH,kDAAgF;AAKzE,oCAEE;;;CAA2B,mBAQlC;AAEF;;GAEG;AACH,wCAAqE;AAO9D,qCAFI,MAAM,IAIR;;CAAY,mBAUnB;AAYK,yCANI,MAAM,YAEN,MAAM,MAAO,MAEZ,MAAM,OAAA,IAIT;;;;CAAqC,mBAiF5C;AAUK,8CAJI,MAAM,QACN,MAAM,YACN,MAAM,GAAC,MAAM,IAIf;;CAAY,mBAEnB;AAQK,uCAHI,MAAM,UACN,MAAM,IAIR;;;CAA2B,mBAiBlC;AAWK,sCAHI,MAAM,UACN,MAAM,IAIR;;CAAiB,mBAExB;AAEK,2DAEE;;;CAA2B,mBAqBlC;AAEK,kEAEE;;;CAA2B,mBAUlC;AAEK,wEAEE;;;CAA2B,mBAUlC;AAKK,yDAEE;;;CAA2B,mBA6BlC;;IAEF,gDAOC;;AAEM,qCAEE;;CAAY,mBAGnB;AAEK,8CAEE;;CAAY,mBAKnB;AAEK,6CAEE;;;CAA2B,mBAsBlC;AAEK,4CAEE;;;;CAA8B,mBAwCrC;AAEK,mDAEE;;;;CAAqC,mBA6B5C;;IAEF,gDAMC;;AAYM,mCAPI,MAAM,QACN,MAAM,aACN,MAAM,GAAC,MAAM,UACb,MAAO,SAAS,IAMlB;;;;CAAqC,mBAkG5C;;IAGF,sFAK8B;;AAavB,kCARI,MAAM,QACN,MAAM,aACN,MAAM,GAAC,MAAM,eACb,MAAM,GAAC,MAAM,SACb,MAAO,SAAS,IAMlB;;;CAA2B,mBAyClC;AAOK,gDAFI,MAAM,IAIR;;;CAA2B,mBAsBlC;AAOK,wCAFI,MAAM,IAIR;;CAAY,mBAmBnB;AAKK,sCAEE;;CAAY,mBAKnB;AAKK,oCAEE;;CAAY,mBAKnB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Y } from '@wordpress/sync';
|
|
5
|
+
import type { WPBlockSelection } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* The type of selection.
|
|
8
|
+
*/
|
|
9
|
+
export declare enum SelectionType {
|
|
10
|
+
None = "none",
|
|
11
|
+
Cursor = "cursor",
|
|
12
|
+
SelectionInOneBlock = "selection-in-one-block",
|
|
13
|
+
SelectionInMultipleBlocks = "selection-in-multiple-blocks",
|
|
14
|
+
WholeBlock = "whole-block"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The position of the cursor.
|
|
18
|
+
*/
|
|
19
|
+
export type CursorPosition = {
|
|
20
|
+
relativePosition: Y.RelativePosition;
|
|
21
|
+
absoluteOffset: number;
|
|
22
|
+
};
|
|
23
|
+
export type SelectionNone = {
|
|
24
|
+
type: SelectionType.None;
|
|
25
|
+
};
|
|
26
|
+
export type SelectionCursor = {
|
|
27
|
+
type: SelectionType.Cursor;
|
|
28
|
+
blockId: string;
|
|
29
|
+
cursorPosition: CursorPosition;
|
|
30
|
+
};
|
|
31
|
+
export type SelectionInOneBlock = {
|
|
32
|
+
type: SelectionType.SelectionInOneBlock;
|
|
33
|
+
blockId: string;
|
|
34
|
+
cursorStartPosition: CursorPosition;
|
|
35
|
+
cursorEndPosition: CursorPosition;
|
|
36
|
+
};
|
|
37
|
+
export type SelectionInMultipleBlocks = {
|
|
38
|
+
type: SelectionType.SelectionInMultipleBlocks;
|
|
39
|
+
blockStartId: string;
|
|
40
|
+
blockEndId: string;
|
|
41
|
+
cursorStartPosition: CursorPosition;
|
|
42
|
+
cursorEndPosition: CursorPosition;
|
|
43
|
+
};
|
|
44
|
+
export type SelectionWholeBlock = {
|
|
45
|
+
type: SelectionType.WholeBlock;
|
|
46
|
+
blockId: string;
|
|
47
|
+
};
|
|
48
|
+
export type SelectionState = SelectionNone | SelectionCursor | SelectionInOneBlock | SelectionInMultipleBlocks | SelectionWholeBlock;
|
|
49
|
+
/**
|
|
50
|
+
* Converts WordPress block editor selection to a SelectionState.
|
|
51
|
+
*
|
|
52
|
+
* @param selectionStart - The start position of the selection
|
|
53
|
+
* @param selectionEnd - The end position of the selection
|
|
54
|
+
* @param yDoc - The Yjs document
|
|
55
|
+
* @return The SelectionState
|
|
56
|
+
*/
|
|
57
|
+
export declare function getSelectionState(selectionStart: WPBlockSelection, selectionEnd: WPBlockSelection, yDoc: Y.Doc): SelectionState;
|
|
58
|
+
/**
|
|
59
|
+
* Check if two selection states are equal.
|
|
60
|
+
*
|
|
61
|
+
* @param selection1 - The first selection state.
|
|
62
|
+
* @param selection2 - The second selection state.
|
|
63
|
+
* @return True if the selection states are equal, false otherwise.
|
|
64
|
+
*/
|
|
65
|
+
export declare function areSelectionsStatesEqual(selection1: SelectionState, selection2: SelectionState): boolean;
|
|
66
|
+
//# sourceMappingURL=crdt-user-selections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crdt-user-selections.d.ts","sourceRoot":"","sources":["../../src/utils/crdt-user-selections.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,CAAC,EAAuB,MAAM,iBAAiB,CAAC;AAQzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD;;GAEG;AACH,oBAAY,aAAa;IACxB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,mBAAmB,2BAA2B;IAC9C,yBAAyB,iCAAiC;IAC1D,UAAU,gBAAgB;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,CAAC;IAYrC,cAAc,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAE3B,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAE7B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,cAAc,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAEjC,IAAI,EAAE,aAAa,CAAC,mBAAmB,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,cAAc,CAAC;IACpC,iBAAiB,EAAE,cAAc,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IAEvC,IAAI,EAAE,aAAa,CAAC,yBAAyB,CAAC;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,cAAc,CAAC;IACpC,iBAAiB,EAAE,cAAc,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAEjC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GACvB,aAAa,GACb,eAAe,GACf,mBAAmB,GACnB,yBAAyB,GACzB,mBAAmB,CAAC;AAEvB;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,cAAc,EAAE,gBAAgB,EAChC,YAAY,EAAE,gBAAgB,EAC9B,IAAI,EAAE,CAAC,CAAC,GAAG,GACT,cAAc,CAgFhB;AA8DD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,UAAU,EAAE,cAAc,EAC1B,UAAU,EAAE,cAAc,GACxB,OAAO,CA2DT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/core-data",
|
|
3
|
-
"version": "7.38.0",
|
|
3
|
+
"version": "7.38.1-next.v.0+b8934fcf9",
|
|
4
4
|
"description": "Access to and manipulation of core WordPress entities.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -49,22 +49,22 @@
|
|
|
49
49
|
"build-module/index.mjs"
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@wordpress/api-fetch": "^7.38.0",
|
|
53
|
-
"@wordpress/block-editor": "^15.11.0",
|
|
54
|
-
"@wordpress/blocks": "^15.11.0",
|
|
55
|
-
"@wordpress/compose": "^7.38.0",
|
|
56
|
-
"@wordpress/data": "^10.38.0",
|
|
57
|
-
"@wordpress/deprecated": "^4.38.0",
|
|
58
|
-
"@wordpress/element": "^6.38.0",
|
|
59
|
-
"@wordpress/html-entities": "^4.38.0",
|
|
60
|
-
"@wordpress/i18n": "^6.11.0",
|
|
61
|
-
"@wordpress/is-shallow-equal": "^5.38.0",
|
|
62
|
-
"@wordpress/private-apis": "^1.38.0",
|
|
63
|
-
"@wordpress/rich-text": "^7.38.0",
|
|
64
|
-
"@wordpress/sync": "^1.38.0",
|
|
65
|
-
"@wordpress/undo-manager": "^1.38.0",
|
|
66
|
-
"@wordpress/url": "^4.38.0",
|
|
67
|
-
"@wordpress/warning": "^3.38.0",
|
|
52
|
+
"@wordpress/api-fetch": "^7.38.1-next.v.0+b8934fcf9",
|
|
53
|
+
"@wordpress/block-editor": "^15.11.1-next.v.0+b8934fcf9",
|
|
54
|
+
"@wordpress/blocks": "^15.11.1-next.v.0+b8934fcf9",
|
|
55
|
+
"@wordpress/compose": "^7.38.1-next.v.0+b8934fcf9",
|
|
56
|
+
"@wordpress/data": "^10.38.1-next.v.0+b8934fcf9",
|
|
57
|
+
"@wordpress/deprecated": "^4.38.1-next.v.0+b8934fcf9",
|
|
58
|
+
"@wordpress/element": "^6.38.1-next.v.0+b8934fcf9",
|
|
59
|
+
"@wordpress/html-entities": "^4.38.1-next.v.0+b8934fcf9",
|
|
60
|
+
"@wordpress/i18n": "^6.11.1-next.v.0+b8934fcf9",
|
|
61
|
+
"@wordpress/is-shallow-equal": "^5.38.1-next.v.0+b8934fcf9",
|
|
62
|
+
"@wordpress/private-apis": "^1.38.1-next.v.0+b8934fcf9",
|
|
63
|
+
"@wordpress/rich-text": "^7.38.1-next.v.0+b8934fcf9",
|
|
64
|
+
"@wordpress/sync": "^1.38.1-next.v.0+b8934fcf9",
|
|
65
|
+
"@wordpress/undo-manager": "^1.38.1-next.v.0+b8934fcf9",
|
|
66
|
+
"@wordpress/url": "^4.38.1-next.v.0+b8934fcf9",
|
|
67
|
+
"@wordpress/warning": "^3.38.1-next.v.0+b8934fcf9",
|
|
68
68
|
"change-case": "^4.1.2",
|
|
69
69
|
"equivalent-key-map": "^0.2.2",
|
|
70
70
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"uuid": "^9.0.1"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
+
"@types/node": "^20.17.10",
|
|
75
76
|
"deep-freeze": "0.0.1"
|
|
76
77
|
},
|
|
77
78
|
"peerDependencies": {
|
|
@@ -81,5 +82,5 @@
|
|
|
81
82
|
"publishConfig": {
|
|
82
83
|
"access": "public"
|
|
83
84
|
},
|
|
84
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "17529010285784fd2e735348a28391c79de87c88"
|
|
85
86
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delay in milliseconds before throttling the cursor position updates.
|
|
3
|
+
*/
|
|
4
|
+
export const AWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS = 100;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Delay in milliseconds before updating the cursor position.
|
|
8
|
+
*/
|
|
9
|
+
export const LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS = 5;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { dispatch, select, subscribe } from '@wordpress/data';
|
|
5
|
+
import { AwarenessState, type Y } from '@wordpress/sync';
|
|
6
|
+
// @ts-ignore No exported types for block editor store selectors.
|
|
7
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Internal dependencies
|
|
11
|
+
*/
|
|
12
|
+
import {
|
|
13
|
+
AWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS,
|
|
14
|
+
LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS,
|
|
15
|
+
} from './config';
|
|
16
|
+
import { STORE_NAME as coreStore } from '../name';
|
|
17
|
+
import { generateUserInfo, areUserInfosEqual } from './utils';
|
|
18
|
+
import {
|
|
19
|
+
areSelectionsStatesEqual,
|
|
20
|
+
getSelectionState,
|
|
21
|
+
} from '../utils/crdt-user-selections';
|
|
22
|
+
|
|
23
|
+
import type { WPBlockSelection } from '../types';
|
|
24
|
+
import type { EditorState, PostEditorState } from './types';
|
|
25
|
+
|
|
26
|
+
export class PostEditorAwareness extends AwarenessState< PostEditorState > {
|
|
27
|
+
protected equalityFieldChecks = {
|
|
28
|
+
editorState: this.areEditorStatesEqual,
|
|
29
|
+
userInfo: areUserInfosEqual,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
public constructor(
|
|
33
|
+
doc: Y.Doc,
|
|
34
|
+
private kind: string,
|
|
35
|
+
private name: string,
|
|
36
|
+
private postId: number
|
|
37
|
+
) {
|
|
38
|
+
super( doc );
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public setUp(): void {
|
|
42
|
+
super.setUp();
|
|
43
|
+
|
|
44
|
+
this.setCurrentUserInfo();
|
|
45
|
+
this.subscribeToUserSelectionChanges();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set the current user info in the local state.
|
|
50
|
+
*/
|
|
51
|
+
private setCurrentUserInfo(): void {
|
|
52
|
+
const states = this.getStates();
|
|
53
|
+
const otherUserColors = Array.from( states.entries() )
|
|
54
|
+
.filter(
|
|
55
|
+
( [ clientId, state ] ) =>
|
|
56
|
+
state.userInfo && clientId !== this.clientID
|
|
57
|
+
)
|
|
58
|
+
.map( ( [ , state ] ) => state.userInfo.color )
|
|
59
|
+
.filter( Boolean );
|
|
60
|
+
|
|
61
|
+
// Get current user info and set it in local state.
|
|
62
|
+
const currentUser = select( coreStore ).getCurrentUser();
|
|
63
|
+
const userInfo = generateUserInfo( currentUser, otherUserColors );
|
|
64
|
+
this.setLocalStateField( 'userInfo', userInfo );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe to user selection changes and update the selection state.
|
|
69
|
+
*/
|
|
70
|
+
private subscribeToUserSelectionChanges(): void {
|
|
71
|
+
const {
|
|
72
|
+
getSelectionStart,
|
|
73
|
+
getSelectionEnd,
|
|
74
|
+
getSelectedBlocksInitialCaretPosition,
|
|
75
|
+
} = select( blockEditorStore );
|
|
76
|
+
|
|
77
|
+
// Keep track of the current selection in the outer scope so we can compare
|
|
78
|
+
// in the subscription.
|
|
79
|
+
let selectionStart = getSelectionStart();
|
|
80
|
+
let selectionEnd = getSelectionEnd();
|
|
81
|
+
let localCursorTimeout: NodeJS.Timeout | null = null;
|
|
82
|
+
|
|
83
|
+
subscribe( () => {
|
|
84
|
+
const newSelectionStart = getSelectionStart();
|
|
85
|
+
const newSelectionEnd = getSelectionEnd();
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
newSelectionStart === selectionStart &&
|
|
89
|
+
newSelectionEnd === selectionEnd
|
|
90
|
+
) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
selectionStart = newSelectionStart;
|
|
95
|
+
selectionEnd = newSelectionEnd;
|
|
96
|
+
|
|
97
|
+
// Typically selection position is only persisted after typing in a block, which
|
|
98
|
+
// can cause selection position to be reset by other users making block updates.
|
|
99
|
+
// Ensure we update the controlled selection right away, persisting our cursor position locally.
|
|
100
|
+
const initialPosition = getSelectedBlocksInitialCaretPosition();
|
|
101
|
+
void this.updateSelectionInEntityRecord(
|
|
102
|
+
selectionStart,
|
|
103
|
+
selectionEnd,
|
|
104
|
+
initialPosition
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// We receive two selection changes in quick succession
|
|
108
|
+
// from local selection events:
|
|
109
|
+
// { clientId: "123...", attributeKey: "content", offset: undefined }
|
|
110
|
+
// { clientId: "123...", attributeKey: "content", offset: 554 }
|
|
111
|
+
// Add a short debounce to avoid sending the first selection change.
|
|
112
|
+
if ( localCursorTimeout ) {
|
|
113
|
+
clearTimeout( localCursorTimeout );
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
localCursorTimeout = setTimeout( () => {
|
|
117
|
+
const selectionState = getSelectionState(
|
|
118
|
+
selectionStart,
|
|
119
|
+
selectionEnd,
|
|
120
|
+
this.doc
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
this.setThrottledLocalStateField(
|
|
124
|
+
'editorState',
|
|
125
|
+
{ selection: selectionState },
|
|
126
|
+
AWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS
|
|
127
|
+
);
|
|
128
|
+
}, LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS );
|
|
129
|
+
} );
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Update the entity record with the current user's selection.
|
|
134
|
+
*
|
|
135
|
+
* @param selectionStart - The start position of the selection.
|
|
136
|
+
* @param selectionEnd - The end position of the selection.
|
|
137
|
+
* @param initialPosition - The initial position of the selection.
|
|
138
|
+
*/
|
|
139
|
+
private async updateSelectionInEntityRecord(
|
|
140
|
+
selectionStart: WPBlockSelection,
|
|
141
|
+
selectionEnd: WPBlockSelection,
|
|
142
|
+
initialPosition: number | null
|
|
143
|
+
): Promise< void > {
|
|
144
|
+
// Send an entityRecord `selection` update if we have a selection.
|
|
145
|
+
//
|
|
146
|
+
// Normally WordPress updates the `selection` property of the post when changes are made to blocks.
|
|
147
|
+
// In a multi-user setup, block changes can occur from other users. When an entity is updated from another
|
|
148
|
+
// user's changes, useBlockSync() in Gutenberg will reset the user's selection to the last saved selection.
|
|
149
|
+
//
|
|
150
|
+
// Manually adding an edit for each movement ensures that other user's changes to the document will
|
|
151
|
+
// not cause the local user's selection to reset to the last local change location.
|
|
152
|
+
const edits = {
|
|
153
|
+
selection: { selectionStart, selectionEnd, initialPosition },
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const options = {
|
|
157
|
+
undoIgnore: true,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// @ts-ignore Types are not provided when using store name instead of store instance.
|
|
161
|
+
dispatch( coreStore ).editEntityRecord(
|
|
162
|
+
this.kind,
|
|
163
|
+
this.name,
|
|
164
|
+
this.postId,
|
|
165
|
+
edits,
|
|
166
|
+
options
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if two editor states are equal.
|
|
172
|
+
*
|
|
173
|
+
* @param state1 - The first editor state.
|
|
174
|
+
* @param state2 - The second editor state.
|
|
175
|
+
* @return True if the editor states are equal, false otherwise.
|
|
176
|
+
*/
|
|
177
|
+
private areEditorStatesEqual(
|
|
178
|
+
state1?: EditorState,
|
|
179
|
+
state2?: EditorState
|
|
180
|
+
): boolean {
|
|
181
|
+
if ( ! state1 || ! state2 ) {
|
|
182
|
+
return state1 === state2;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return areSelectionsStatesEqual( state1.selection, state2.selection );
|
|
186
|
+
}
|
|
187
|
+
}
|