@wordpress/core-data 7.39.0 → 7.39.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/base-awareness.cjs +4 -5
- package/build/awareness/base-awareness.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +75 -2
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/hooks/use-post-editor-awareness-state.cjs +97 -0
- package/build/hooks/use-post-editor-awareness-state.cjs.map +7 -0
- package/build/types.cjs.map +1 -1
- package/build/utils/crdt-user-selections.cjs.map +2 -2
- package/build-module/awareness/base-awareness.mjs +5 -6
- package/build-module/awareness/base-awareness.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +75 -2
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +69 -0
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +7 -0
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-types/awareness/base-awareness.d.ts +1 -1
- package/build-types/awareness/base-awareness.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +23 -3
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +23 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +35 -0
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -0
- package/build-types/types.d.ts +41 -0
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts +1 -34
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/awareness/base-awareness.ts +5 -7
- package/src/awareness/post-editor-awareness.ts +108 -5
- package/src/awareness/types.ts +46 -1
- package/src/hooks/use-post-editor-awareness-state.ts +148 -0
- package/src/types.ts +71 -0
- package/src/utils/crdt-user-selections.ts +10 -62
|
@@ -30,19 +30,18 @@ var import_sync = require("@wordpress/sync");
|
|
|
30
30
|
var import_name = require("../name.cjs");
|
|
31
31
|
var import_utils = require("./utils.cjs");
|
|
32
32
|
var BaseAwarenessState = class extends import_sync.AwarenessState {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.setCurrentUserInfo();
|
|
33
|
+
onSetUp() {
|
|
34
|
+
void this.setCurrentUserInfo();
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
38
37
|
* Set the current user info in the local state.
|
|
39
38
|
*/
|
|
40
|
-
setCurrentUserInfo() {
|
|
39
|
+
async setCurrentUserInfo() {
|
|
41
40
|
const states = this.getStates();
|
|
42
41
|
const otherUserColors = Array.from(states.entries()).filter(
|
|
43
42
|
([clientId, state]) => state.userInfo && clientId !== this.clientID
|
|
44
43
|
).map(([, state]) => state.userInfo.color).filter(Boolean);
|
|
45
|
-
const currentUser = (0, import_data.
|
|
44
|
+
const currentUser = await (0, import_data.resolveSelect)(import_name.STORE_NAME).getCurrentUser();
|
|
46
45
|
const userInfo = (0, import_utils.generateUserInfo)(currentUser, otherUserColors);
|
|
47
46
|
this.setLocalStateField("userInfo", userInfo);
|
|
48
47
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/awareness/base-awareness.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { resolveSelect } from '@wordpress/data';\nimport { AwarenessState } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport { STORE_NAME as coreStore } from '../name';\nimport { generateUserInfo, areUserInfosEqual } from './utils';\n\nimport type { BaseState } from './types';\n\nexport abstract class BaseAwarenessState<\n\tState extends BaseState,\n> extends AwarenessState< State > {\n\tprotected onSetUp(): void {\n\t\tvoid this.setCurrentUserInfo();\n\t}\n\n\t/**\n\t * Set the current user info in the local state.\n\t */\n\tprivate async setCurrentUserInfo(): Promise< void > {\n\t\tconst states = this.getStates();\n\t\tconst otherUserColors = Array.from( states.entries() )\n\t\t\t.filter(\n\t\t\t\t( [ clientId, state ] ) =>\n\t\t\t\t\tstate.userInfo && clientId !== this.clientID\n\t\t\t)\n\t\t\t.map( ( [ , state ] ) => state.userInfo.color )\n\t\t\t.filter( Boolean );\n\n\t\t// Get current user info and set it in local state.\n\t\tconst currentUser = await resolveSelect( coreStore ).getCurrentUser();\n\t\tconst userInfo = generateUserInfo( currentUser, otherUserColors );\n\t\tthis.setLocalStateField( 'userInfo', userInfo );\n\t}\n}\n\nexport const baseEqualityFieldChecks = {\n\tuserInfo: areUserInfosEqual,\n};\n\nexport class BaseAwareness extends BaseAwarenessState< BaseState > {\n\tprotected equalityFieldChecks = baseEqualityFieldChecks;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAA8B;AAC9B,kBAA+B;AAK/B,kBAAwC;AACxC,mBAAoD;AAI7C,IAAe,qBAAf,cAEG,2BAAwB;AAAA,EACvB,UAAgB;AACzB,SAAK,KAAK,mBAAmB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBAAsC;AACnD,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,kBAAkB,MAAM,KAAM,OAAO,QAAQ,CAAE,EACnD;AAAA,MACA,CAAE,CAAE,UAAU,KAAM,MACnB,MAAM,YAAY,aAAa,KAAK;AAAA,IACtC,EACC,IAAK,CAAE,CAAE,EAAE,KAAM,MAAO,MAAM,SAAS,KAAM,EAC7C,OAAQ,OAAQ;AAGlB,UAAM,cAAc,UAAM,2BAAe,YAAAA,UAAU,EAAE,eAAe;AACpE,UAAM,eAAW,+BAAkB,aAAa,eAAgB;AAChE,SAAK,mBAAoB,YAAY,QAAS;AAAA,EAC/C;AACD;AAEO,IAAM,0BAA0B;AAAA,EACtC,UAAU;AACX;AAEO,IAAM,gBAAN,cAA4B,mBAAgC;AAAA,EACxD,sBAAsB;AACjC;",
|
|
6
6
|
"names": ["coreStore"]
|
|
7
7
|
}
|
|
@@ -24,6 +24,7 @@ __export(post_editor_awareness_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(post_editor_awareness_exports);
|
|
26
26
|
var import_data = require("@wordpress/data");
|
|
27
|
+
var import_sync = require("@wordpress/sync");
|
|
27
28
|
var import_block_editor = require("@wordpress/block-editor");
|
|
28
29
|
var import_base_awareness = require("./base-awareness.cjs");
|
|
29
30
|
var import_config = require("./config.cjs");
|
|
@@ -40,8 +41,8 @@ var PostEditorAwareness = class extends import_base_awareness.BaseAwarenessState
|
|
|
40
41
|
...import_base_awareness.baseEqualityFieldChecks,
|
|
41
42
|
editorState: this.areEditorStatesEqual
|
|
42
43
|
};
|
|
43
|
-
|
|
44
|
-
super.
|
|
44
|
+
onSetUp() {
|
|
45
|
+
super.onSetUp();
|
|
45
46
|
this.subscribeToUserSelectionChanges();
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
@@ -122,6 +123,78 @@ var PostEditorAwareness = class extends import_base_awareness.BaseAwarenessState
|
|
|
122
123
|
}
|
|
123
124
|
return (0, import_crdt_user_selections.areSelectionsStatesEqual)(state1.selection, state2.selection);
|
|
124
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the absolute position index from a selection cursor.
|
|
128
|
+
*
|
|
129
|
+
* @param selection - The selection cursor.
|
|
130
|
+
* @return The absolute position index, or null if not found.
|
|
131
|
+
*/
|
|
132
|
+
getAbsolutePositionIndex(selection) {
|
|
133
|
+
return import_sync.Y.createAbsolutePositionFromRelativePosition(
|
|
134
|
+
selection.cursorPosition.relativePosition,
|
|
135
|
+
this.doc
|
|
136
|
+
)?.index ?? null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Type guard to check if a struct is a Y.Item (not Y.GC)
|
|
140
|
+
* @param struct - The struct to check.
|
|
141
|
+
* @return True if the struct is a Y.Item, false otherwise.
|
|
142
|
+
*/
|
|
143
|
+
isYItem(struct) {
|
|
144
|
+
return "content" in struct;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get data for debugging, using the awareness state.
|
|
148
|
+
*
|
|
149
|
+
* @return {YDocDebugData} The debug data.
|
|
150
|
+
*/
|
|
151
|
+
getDebugData() {
|
|
152
|
+
const ydoc = this.doc;
|
|
153
|
+
const docData = Object.fromEntries(
|
|
154
|
+
Array.from(ydoc.share, ([key, value]) => [
|
|
155
|
+
key,
|
|
156
|
+
value.toJSON()
|
|
157
|
+
])
|
|
158
|
+
);
|
|
159
|
+
const userMapData = new Map(
|
|
160
|
+
Array.from(this.getSeenStates().entries()).map(
|
|
161
|
+
([clientId, userState]) => [
|
|
162
|
+
String(clientId),
|
|
163
|
+
{
|
|
164
|
+
name: userState.userInfo.name,
|
|
165
|
+
wpUserId: userState.userInfo.id
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
const serializableClientItems = {};
|
|
171
|
+
ydoc.store.clients.forEach((structs, clientId) => {
|
|
172
|
+
const items = structs.filter(this.isYItem);
|
|
173
|
+
serializableClientItems[clientId] = items.map((item) => {
|
|
174
|
+
const { left, right, ...rest } = item;
|
|
175
|
+
return {
|
|
176
|
+
...rest,
|
|
177
|
+
left: left ? {
|
|
178
|
+
id: left.id,
|
|
179
|
+
length: left.length,
|
|
180
|
+
origin: left.origin,
|
|
181
|
+
content: left.content
|
|
182
|
+
} : null,
|
|
183
|
+
right: right ? {
|
|
184
|
+
id: right.id,
|
|
185
|
+
length: right.length,
|
|
186
|
+
origin: right.origin,
|
|
187
|
+
content: right.content
|
|
188
|
+
} : null
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
doc: docData,
|
|
194
|
+
clients: serializableClientItems,
|
|
195
|
+
userMap: Object.fromEntries(userMapData)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
125
198
|
};
|
|
126
199
|
// Annotate the CommonJS export names for ESM import in node:
|
|
127
200
|
0 && (module.exports = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/awareness/post-editor-awareness.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select, subscribe } from '@wordpress/data';\nimport
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAA4C;
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select, subscribe } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport { BaseAwarenessState, baseEqualityFieldChecks } from './base-awareness';\nimport {\n\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS,\n\tLOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS,\n} from './config';\nimport { STORE_NAME as coreStore } from '../name';\nimport {\n\tareSelectionsStatesEqual,\n\tgetSelectionState,\n} from '../utils/crdt-user-selections';\n\nimport type { SelectionCursor, WPBlockSelection } from '../types';\nimport type {\n\tDebugUserData,\n\tEditorState,\n\tPostEditorState,\n\tSerializableYItem,\n\tYDocDebugData,\n} from './types';\n\nexport class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {\n\tprotected equalityFieldChecks = {\n\t\t...baseEqualityFieldChecks,\n\t\teditorState: this.areEditorStatesEqual,\n\t};\n\n\tpublic constructor(\n\t\tdoc: Y.Doc,\n\t\tprivate kind: string,\n\t\tprivate name: string,\n\t\tprivate postId: number\n\t) {\n\t\tsuper( doc );\n\t}\n\n\tprotected onSetUp(): void {\n\t\tsuper.onSetUp();\n\n\t\tthis.subscribeToUserSelectionChanges();\n\t}\n\n\t/**\n\t * Subscribe to user selection changes and update the selection state.\n\t */\n\tprivate subscribeToUserSelectionChanges(): void {\n\t\tconst {\n\t\t\tgetSelectionStart,\n\t\t\tgetSelectionEnd,\n\t\t\tgetSelectedBlocksInitialCaretPosition,\n\t\t} = select( blockEditorStore );\n\n\t\t// Keep track of the current selection in the outer scope so we can compare\n\t\t// in the subscription.\n\t\tlet selectionStart = getSelectionStart();\n\t\tlet selectionEnd = getSelectionEnd();\n\t\tlet localCursorTimeout: NodeJS.Timeout | null = null;\n\n\t\tsubscribe( () => {\n\t\t\tconst newSelectionStart = getSelectionStart();\n\t\t\tconst newSelectionEnd = getSelectionEnd();\n\n\t\t\tif (\n\t\t\t\tnewSelectionStart === selectionStart &&\n\t\t\t\tnewSelectionEnd === selectionEnd\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tselectionStart = newSelectionStart;\n\t\t\tselectionEnd = newSelectionEnd;\n\n\t\t\t// Typically selection position is only persisted after typing in a block, which\n\t\t\t// can cause selection position to be reset by other users making block updates.\n\t\t\t// Ensure we update the controlled selection right away, persisting our cursor position locally.\n\t\t\tconst initialPosition = getSelectedBlocksInitialCaretPosition();\n\t\t\tvoid this.updateSelectionInEntityRecord(\n\t\t\t\tselectionStart,\n\t\t\t\tselectionEnd,\n\t\t\t\tinitialPosition\n\t\t\t);\n\n\t\t\t// We receive two selection changes in quick succession\n\t\t\t// from local selection events:\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: undefined }\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: 554 }\n\t\t\t// Add a short debounce to avoid sending the first selection change.\n\t\t\tif ( localCursorTimeout ) {\n\t\t\t\tclearTimeout( localCursorTimeout );\n\t\t\t}\n\n\t\t\tlocalCursorTimeout = setTimeout( () => {\n\t\t\t\tconst selectionState = getSelectionState(\n\t\t\t\t\tselectionStart,\n\t\t\t\t\tselectionEnd,\n\t\t\t\t\tthis.doc\n\t\t\t\t);\n\n\t\t\t\tthis.setThrottledLocalStateField(\n\t\t\t\t\t'editorState',\n\t\t\t\t\t{ selection: selectionState },\n\t\t\t\t\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS\n\t\t\t\t);\n\t\t\t}, LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS );\n\t\t} );\n\t}\n\n\t/**\n\t * Update the entity record with the current user's selection.\n\t *\n\t * @param selectionStart - The start position of the selection.\n\t * @param selectionEnd - The end position of the selection.\n\t * @param initialPosition - The initial position of the selection.\n\t */\n\tprivate async updateSelectionInEntityRecord(\n\t\tselectionStart: WPBlockSelection,\n\t\tselectionEnd: WPBlockSelection,\n\t\tinitialPosition: number | null\n\t): Promise< void > {\n\t\t// Send an entityRecord `selection` update if we have a selection.\n\t\t//\n\t\t// Normally WordPress updates the `selection` property of the post when changes are made to blocks.\n\t\t// In a multi-user setup, block changes can occur from other users. When an entity is updated from another\n\t\t// user's changes, useBlockSync() in Gutenberg will reset the user's selection to the last saved selection.\n\t\t//\n\t\t// Manually adding an edit for each movement ensures that other user's changes to the document will\n\t\t// not cause the local user's selection to reset to the last local change location.\n\t\tconst edits = {\n\t\t\tselection: { selectionStart, selectionEnd, initialPosition },\n\t\t};\n\n\t\tconst options = {\n\t\t\tundoIgnore: true,\n\t\t};\n\n\t\t// @ts-ignore Types are not provided when using store name instead of store instance.\n\t\tdispatch( coreStore ).editEntityRecord(\n\t\t\tthis.kind,\n\t\t\tthis.name,\n\t\t\tthis.postId,\n\t\t\tedits,\n\t\t\toptions\n\t\t);\n\t}\n\n\t/**\n\t * Check if two editor states are equal.\n\t *\n\t * @param state1 - The first editor state.\n\t * @param state2 - The second editor state.\n\t * @return True if the editor states are equal, false otherwise.\n\t */\n\tprivate areEditorStatesEqual(\n\t\tstate1?: EditorState,\n\t\tstate2?: EditorState\n\t): boolean {\n\t\tif ( ! state1 || ! state2 ) {\n\t\t\treturn state1 === state2;\n\t\t}\n\n\t\treturn areSelectionsStatesEqual( state1.selection, state2.selection );\n\t}\n\n\t/**\n\t * Get the absolute position index from a selection cursor.\n\t *\n\t * @param selection - The selection cursor.\n\t * @return The absolute position index, or null if not found.\n\t */\n\tpublic getAbsolutePositionIndex(\n\t\tselection: SelectionCursor\n\t): number | null {\n\t\treturn (\n\t\t\tY.createAbsolutePositionFromRelativePosition(\n\t\t\t\tselection.cursorPosition.relativePosition,\n\t\t\t\tthis.doc\n\t\t\t)?.index ?? null\n\t\t);\n\t}\n\n\t/**\n\t * Type guard to check if a struct is a Y.Item (not Y.GC)\n\t * @param struct - The struct to check.\n\t * @return True if the struct is a Y.Item, false otherwise.\n\t */\n\tprivate isYItem( struct: Y.Item | Y.GC ): struct is Y.Item {\n\t\treturn 'content' in struct;\n\t}\n\n\t/**\n\t * Get data for debugging, using the awareness state.\n\t *\n\t * @return {YDocDebugData} The debug data.\n\t */\n\tpublic getDebugData(): YDocDebugData {\n\t\tconst ydoc = this.doc;\n\n\t\t// Manually extract doc data to avoid deprecated toJSON method\n\t\tconst docData: Record< string, unknown > = Object.fromEntries(\n\t\t\tArray.from( ydoc.share, ( [ key, value ] ) => [\n\t\t\t\tkey,\n\t\t\t\tvalue.toJSON(),\n\t\t\t] )\n\t\t);\n\n\t\t// Build userMap from awareness store (all users seen this session)\n\t\tconst userMapData = new Map< string, DebugUserData >(\n\t\t\tArray.from( this.getSeenStates().entries() ).map(\n\t\t\t\t( [ clientId, userState ] ) => [\n\t\t\t\t\tString( clientId ),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: userState.userInfo.name,\n\t\t\t\t\t\twpUserId: userState.userInfo.id,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t)\n\t\t);\n\n\t\t// Serialize Yjs client items to avoid deep nesting\n\t\tconst serializableClientItems: Record<\n\t\t\tnumber,\n\t\t\tArray< SerializableYItem >\n\t\t> = {};\n\n\t\tydoc.store.clients.forEach( ( structs, clientId ) => {\n\t\t\t// Filter for Y.Item only (skip Y.GC garbage collection structs)\n\t\t\tconst items = structs.filter( this.isYItem );\n\n\t\t\tserializableClientItems[ clientId ] = items.map( ( item ) => {\n\t\t\t\tconst { left, right, ...rest } = item;\n\n\t\t\t\treturn {\n\t\t\t\t\t...rest,\n\t\t\t\t\tleft: left\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: left.id,\n\t\t\t\t\t\t\t\tlength: left.length,\n\t\t\t\t\t\t\t\torigin: left.origin,\n\t\t\t\t\t\t\t\tcontent: left.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t\tright: right\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: right.id,\n\t\t\t\t\t\t\t\tlength: right.length,\n\t\t\t\t\t\t\t\torigin: right.origin,\n\t\t\t\t\t\t\t\tcontent: right.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t};\n\t\t\t} );\n\t\t} );\n\n\t\treturn {\n\t\t\tdoc: docData,\n\t\t\tclients: serializableClientItems,\n\t\t\tuserMap: Object.fromEntries( userMapData ),\n\t\t};\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["blockEditorStore", "coreStore"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/awareness/types.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Internal dependencies\n */\nimport type { SelectionState } from '../
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { EnhancedState, Y } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport type { SelectionState } from '../types';\nimport type { User } from '../entity-types';\n\nexport type UserInfo = Pick<\n\tUser< 'view' >,\n\t'id' | 'name' | 'slug' | 'avatar_urls'\n> & {\n\tbrowserType: string;\n\tcolor: string;\n\tenteredAt: number;\n};\n\n/**\n * This base state represents the presence of the user. We expect it to be\n * extended to include additional state describing the user's current activity.\n * This state must be serializable and compact.\n */\nexport interface BaseState {\n\tuserInfo: UserInfo;\n}\n\n/**\n * The editor state includes information about the user's current selection.\n */\nexport interface EditorState {\n\tselection: SelectionState;\n}\n\n/**\n * The post editor state extends the base state with information used to render\n * presence indicators in the post editor.\n */\nexport interface PostEditorState extends BaseState {\n\teditorState?: EditorState;\n}\n\n/**\n * An enhanced post editor awareness state includes additional metadata about\n * the user and their connection.\n */\nexport type PostEditorAwarenessState = EnhancedState< PostEditorState >;\n\n// WordPress user info for debug export (subset of UserInfo)\nexport type DebugUserData = Pick< UserInfo, 'name' > & {\n\twpUserId: UserInfo[ 'id' ];\n};\n\nexport interface YDocDebugData {\n\tdoc: Record< string, unknown >;\n\tclients: Record< number, Array< SerializableYItem > >;\n\tuserMap: Record< string, DebugUserData >;\n}\n\n// Type for serializable left/right item references to avoid deep nesting\nexport type SerializableYItemRef = Pick<\n\tY.Item,\n\t'id' | 'length' | 'origin' | 'content'\n>;\n\n// Serializable Y.Item - only includes data properties with shallow left/right references\nexport type SerializableYItem = Pick<\n\tY.Item,\n\t| 'id'\n\t| 'length'\n\t| 'origin'\n\t| 'rightOrigin'\n\t| 'parent'\n\t| 'parentSub'\n\t| 'redone'\n\t| 'content'\n\t| 'info'\n> & {\n\tleft: SerializableYItemRef | null;\n\tright: SerializableYItemRef | null;\n};\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/core-data/src/hooks/use-post-editor-awareness-state.ts
|
|
21
|
+
var use_post_editor_awareness_state_exports = {};
|
|
22
|
+
__export(use_post_editor_awareness_state_exports, {
|
|
23
|
+
useActiveUsers: () => useActiveUsers,
|
|
24
|
+
useGetAbsolutePositionIndex: () => useGetAbsolutePositionIndex,
|
|
25
|
+
useGetDebugData: () => useGetDebugData,
|
|
26
|
+
useIsDisconnected: () => useIsDisconnected
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(use_post_editor_awareness_state_exports);
|
|
29
|
+
var import_element = require("@wordpress/element");
|
|
30
|
+
var import_sync = require("../sync.cjs");
|
|
31
|
+
var defaultState = {
|
|
32
|
+
activeUsers: [],
|
|
33
|
+
getAbsolutePositionIndex: () => null,
|
|
34
|
+
getDebugData: () => ({
|
|
35
|
+
doc: {},
|
|
36
|
+
clients: {},
|
|
37
|
+
userMap: {}
|
|
38
|
+
}),
|
|
39
|
+
isCurrentUserDisconnected: false
|
|
40
|
+
};
|
|
41
|
+
function getAwarenessState(awareness, newState) {
|
|
42
|
+
const activeUsers = newState ?? awareness.getCurrentState();
|
|
43
|
+
return {
|
|
44
|
+
activeUsers,
|
|
45
|
+
getAbsolutePositionIndex: (selection) => awareness.getAbsolutePositionIndex(selection),
|
|
46
|
+
getDebugData: () => awareness.getDebugData(),
|
|
47
|
+
isCurrentUserDisconnected: activeUsers.find((user) => user.isMe)?.isConnected === false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function usePostEditorAwarenessState(postId, postType) {
|
|
51
|
+
const [state, setState] = (0, import_element.useState)(defaultState);
|
|
52
|
+
(0, import_element.useEffect)(() => {
|
|
53
|
+
if (null === postId || null === postType) {
|
|
54
|
+
setState(defaultState);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const objectType = `postType/${postType}`;
|
|
58
|
+
const objectId = postId.toString();
|
|
59
|
+
const awareness = (0, import_sync.getSyncManager)()?.getAwareness(
|
|
60
|
+
objectType,
|
|
61
|
+
objectId
|
|
62
|
+
);
|
|
63
|
+
if (!awareness) {
|
|
64
|
+
setState(defaultState);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
awareness.setUp();
|
|
68
|
+
setState(getAwarenessState(awareness));
|
|
69
|
+
const unsubscribe = awareness?.onStateChange(
|
|
70
|
+
(newState) => {
|
|
71
|
+
setState(getAwarenessState(awareness, newState));
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
return unsubscribe;
|
|
75
|
+
}, [postId, postType]);
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
78
|
+
function useActiveUsers(postId, postType) {
|
|
79
|
+
return usePostEditorAwarenessState(postId, postType).activeUsers;
|
|
80
|
+
}
|
|
81
|
+
function useGetAbsolutePositionIndex(postId, postType) {
|
|
82
|
+
return usePostEditorAwarenessState(postId, postType).getAbsolutePositionIndex;
|
|
83
|
+
}
|
|
84
|
+
function useGetDebugData(postId, postType) {
|
|
85
|
+
return usePostEditorAwarenessState(postId, postType).getDebugData();
|
|
86
|
+
}
|
|
87
|
+
function useIsDisconnected(postId, postType) {
|
|
88
|
+
return usePostEditorAwarenessState(postId, postType).isCurrentUserDisconnected;
|
|
89
|
+
}
|
|
90
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
91
|
+
0 && (module.exports = {
|
|
92
|
+
useActiveUsers,
|
|
93
|
+
useGetAbsolutePositionIndex,
|
|
94
|
+
useGetDebugData,
|
|
95
|
+
useIsDisconnected
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=use-post-editor-awareness-state.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/use-post-editor-awareness-state.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport { useEffect, useState } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { getSyncManager } from '../sync';\nimport type {\n\tPostEditorAwarenessState as ActiveUser,\n\tYDocDebugData,\n} from '../awareness/types';\nimport type { SelectionCursor } from '../types';\nimport type { PostEditorAwareness } from '../awareness/post-editor-awareness';\n\ninterface AwarenessState {\n\tactiveUsers: ActiveUser[];\n\tgetAbsolutePositionIndex: ( selection: SelectionCursor ) => number | null;\n\tgetDebugData: () => YDocDebugData;\n\tisCurrentUserDisconnected: boolean;\n}\n\nconst defaultState: AwarenessState = {\n\tactiveUsers: [],\n\tgetAbsolutePositionIndex: () => null,\n\tgetDebugData: () => ( {\n\t\tdoc: {},\n\t\tclients: {},\n\t\tuserMap: {},\n\t} ),\n\tisCurrentUserDisconnected: false,\n};\n\nfunction getAwarenessState(\n\tawareness: PostEditorAwareness,\n\tnewState?: ActiveUser[]\n): AwarenessState {\n\tconst activeUsers = newState ?? awareness.getCurrentState();\n\n\treturn {\n\t\tactiveUsers,\n\t\tgetAbsolutePositionIndex: ( selection: SelectionCursor ) =>\n\t\t\tawareness.getAbsolutePositionIndex( selection ),\n\t\tgetDebugData: () => awareness.getDebugData(),\n\t\tisCurrentUserDisconnected:\n\t\t\tactiveUsers.find( ( user ) => user.isMe )?.isConnected === false,\n\t};\n}\n\nfunction usePostEditorAwarenessState(\n\tpostId: number | null,\n\tpostType: string | null\n): AwarenessState {\n\tconst [ state, setState ] = useState< AwarenessState >( defaultState );\n\n\tuseEffect( () => {\n\t\tif ( null === postId || null === postType ) {\n\t\t\tsetState( defaultState );\n\t\t\treturn;\n\t\t}\n\n\t\tconst objectType = `postType/${ postType }`;\n\t\tconst objectId = postId.toString();\n\t\tconst awareness = getSyncManager()?.getAwareness< PostEditorAwareness >(\n\t\t\tobjectType,\n\t\t\tobjectId\n\t\t);\n\n\t\tif ( ! awareness ) {\n\t\t\tsetState( defaultState );\n\t\t\treturn;\n\t\t}\n\n\t\tawareness.setUp();\n\n\t\t// Initialize with current awareness state.\n\t\tsetState( getAwarenessState( awareness ) );\n\n\t\tconst unsubscribe = awareness?.onStateChange(\n\t\t\t( newState: ActiveUser[] ) => {\n\t\t\t\tsetState( getAwarenessState( awareness, newState ) );\n\t\t\t}\n\t\t);\n\n\t\treturn unsubscribe;\n\t}, [ postId, postType ] );\n\n\treturn state;\n}\n\n/**\n * Hook to get the active users for a post editor.\n *\n * @param postId - The ID of the post.\n * @param postType - The type of the post.\n * @return {ActiveUser[]} The active users.\n */\nexport function useActiveUsers(\n\tpostId: number | null,\n\tpostType: string | null\n): ActiveUser[] {\n\treturn usePostEditorAwarenessState( postId, postType ).activeUsers;\n}\n\n/**\n * Hook to get the absolute position index for a post editor.\n *\n * @param postId - The ID of the post.\n * @param postType - The type of the post.\n * @return {SelectionCursor} The absolute position index.\n */\nexport function useGetAbsolutePositionIndex(\n\tpostId: number | null,\n\tpostType: string | null\n): ( selection: SelectionCursor ) => number | null {\n\treturn usePostEditorAwarenessState( postId, postType )\n\t\t.getAbsolutePositionIndex;\n}\n\n/**\n * Hook to get data for debugging, using the awareness state.\n *\n * @param postId - The ID of the post.\n * @param postType - The type of the post.\n * @return {YDocDebugData} The debug data.\n */\nexport function useGetDebugData(\n\tpostId: number | null,\n\tpostType: string | null\n): YDocDebugData {\n\treturn usePostEditorAwarenessState( postId, postType ).getDebugData();\n}\n\n/**\n * Hook to check if the current user is disconnected.\n *\n * @param postId - The ID of the post.\n * @param postType - The type of the post.\n * @return {boolean} Whether the current user is disconnected.\n */\nexport function useIsDisconnected(\n\tpostId: number | null,\n\tpostType: string | null\n): boolean {\n\treturn usePostEditorAwarenessState( postId, postType )\n\t\t.isCurrentUserDisconnected;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAAoC;AAKpC,kBAA+B;AAe/B,IAAM,eAA+B;AAAA,EACpC,aAAa,CAAC;AAAA,EACd,0BAA0B,MAAM;AAAA,EAChC,cAAc,OAAQ;AAAA,IACrB,KAAK,CAAC;AAAA,IACN,SAAS,CAAC;AAAA,IACV,SAAS,CAAC;AAAA,EACX;AAAA,EACA,2BAA2B;AAC5B;AAEA,SAAS,kBACR,WACA,UACiB;AACjB,QAAM,cAAc,YAAY,UAAU,gBAAgB;AAE1D,SAAO;AAAA,IACN;AAAA,IACA,0BAA0B,CAAE,cAC3B,UAAU,yBAA0B,SAAU;AAAA,IAC/C,cAAc,MAAM,UAAU,aAAa;AAAA,IAC3C,2BACC,YAAY,KAAM,CAAE,SAAU,KAAK,IAAK,GAAG,gBAAgB;AAAA,EAC7D;AACD;AAEA,SAAS,4BACR,QACA,UACiB;AACjB,QAAM,CAAE,OAAO,QAAS,QAAI,yBAA4B,YAAa;AAErE,gCAAW,MAAM;AAChB,QAAK,SAAS,UAAU,SAAS,UAAW;AAC3C,eAAU,YAAa;AACvB;AAAA,IACD;AAEA,UAAM,aAAa,YAAa,QAAS;AACzC,UAAM,WAAW,OAAO,SAAS;AACjC,UAAM,gBAAY,4BAAe,GAAG;AAAA,MACnC;AAAA,MACA;AAAA,IACD;AAEA,QAAK,CAAE,WAAY;AAClB,eAAU,YAAa;AACvB;AAAA,IACD;AAEA,cAAU,MAAM;AAGhB,aAAU,kBAAmB,SAAU,CAAE;AAEzC,UAAM,cAAc,WAAW;AAAA,MAC9B,CAAE,aAA4B;AAC7B,iBAAU,kBAAmB,WAAW,QAAS,CAAE;AAAA,MACpD;AAAA,IACD;AAEA,WAAO;AAAA,EACR,GAAG,CAAE,QAAQ,QAAS,CAAE;AAExB,SAAO;AACR;AASO,SAAS,eACf,QACA,UACe;AACf,SAAO,4BAA6B,QAAQ,QAAS,EAAE;AACxD;AASO,SAAS,4BACf,QACA,UACkD;AAClD,SAAO,4BAA6B,QAAQ,QAAS,EACnD;AACH;AASO,SAAS,gBACf,QACA,UACgB;AAChB,SAAO,4BAA6B,QAAQ,QAAS,EAAE,aAAa;AACrE;AASO,SAAS,kBACf,QACA,UACU;AACV,SAAO,4BAA6B,QAAQ,QAAS,EACnD;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/build/types.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/types.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { Y } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport type { SelectionType } from './utils/crdt-user-selections';\n\nexport interface AnyFunction {\n\t( ...args: any[] ): any;\n}\n\n/**\n * Avoid a circular dependency with @wordpress/editor\n *\n * Additionaly, this type marks `attributeKey` and `offset` as possibly\n * `undefined`, which can happen in two known scenarios:\n *\n * 1. If a user has an entire block highlighted (e.g., a `core/image` block).\n * 2. If there's an intermediate selection state while inserting a block, those\n * properties will be temporarily`undefined`.\n */\nexport interface WPBlockSelection {\n\tclientId: string;\n\tattributeKey?: string;\n\toffset?: number;\n}\n\nexport interface WPSelection {\n\tselectionEnd: WPBlockSelection;\n\tselectionStart: WPBlockSelection;\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"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 (\n\t\t! block ||\n\t\t! selection.attributeKey ||\n\t\tundefined === selection.offset\n\t) {\n\t\treturn null;\n\t}\n\n\tconst attributes = block.get( 'attributes' );\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": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAuC;AAOvC,wBAA2B;
|
|
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 {\n\tWPBlockSelection,\n\tSelectionState,\n\tSelectionNone,\n\tSelectionCursor,\n\tSelectionInOneBlock,\n\tSelectionInMultipleBlocks,\n\tSelectionWholeBlock,\n\tCursorPosition,\n} 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 * 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 (\n\t\t! block ||\n\t\t! selection.attributeKey ||\n\t\tundefined === selection.offset\n\t) {\n\t\treturn null;\n\t}\n\n\tconst attributes = block.get( 'attributes' );\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": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAuC;AAOvC,wBAA2B;AAepB,IAAK,gBAAL,kBAAKA,mBAAL;AACN,EAAAA,eAAA,UAAO;AACP,EAAAA,eAAA,YAAS;AACT,EAAAA,eAAA,yBAAsB;AACtB,EAAAA,eAAA,+BAA4B;AAC5B,EAAAA,eAAA,gBAAa;AALF,SAAAA;AAAA,GAAA;AAgBL,SAAS,kBACf,gBACA,cACA,MACiB;AACjB,QAAM,WAAO,8BAA2B,MAAM,+BAAoB;AAClE,QAAM,UAAU,KAAK,IAAK,QAAS,KAAK,IAAI,cAAE,MAAgB;AAE9D,QAAM,mBAAmB,OAAO,KAAM,cAAe,EAAE,WAAW;AAClE,QAAM,cAA6B;AAAA,IAClC,MAAM;AAAA,EACP;AAEA,MAAK,kBAAmB;AAEvB,WAAO;AAAA,EACR;AAGA,QAAM,wBACL,eAAe,aAAa,aAAa;AAC1C,QAAM,eACL,yBAAyB,eAAe,WAAW,aAAa;AACjE,QAAM,yBACL,yBACA,eAAe,WAAW,UAC1B,aAAa,WAAW;AAEzB,MAAK,wBAAyB;AAE7B,WAAO;AAAA,MACN,MAAM;AAAA,MACN,SAAS,eAAe;AAAA,IACzB;AAAA,EACD,WAAY,cAAe;AAE1B,UAAM,iBAAiB,kBAAmB,gBAAgB,OAAQ;AAElE,QAAK,CAAE,gBAAiB;AAEvB,aAAO;AAAA,IACR;AAEA,WAAO;AAAA,MACN,MAAM;AAAA,MACN,SAAS,eAAe;AAAA,MACxB;AAAA,IACD;AAAA,EACD,WAAY,uBAAwB;AAEnC,UAAMC,uBAAsB;AAAA,MAC3B;AAAA,MACA;AAAA,IACD;AACA,UAAMC,qBAAoB,kBAAmB,cAAc,OAAQ;AAEnE,QAAK,CAAED,wBAAuB,CAAEC,oBAAoB;AAEnD,aAAO;AAAA,IACR;AAEA,WAAO;AAAA,MACN,MAAM;AAAA,MACN,SAAS,eAAe;AAAA,MACxB,qBAAAD;AAAA,MACA,mBAAAC;AAAA,IACD;AAAA,EACD;AAGA,QAAM,sBAAsB,kBAAmB,gBAAgB,OAAQ;AACvE,QAAM,oBAAoB,kBAAmB,cAAc,OAAQ;AACnE,MAAK,CAAE,uBAAuB,CAAE,mBAAoB;AAEnD,WAAO;AAAA,EACR;AAEA,SAAO;AAAA,IACN,MAAM;AAAA,IACN,cAAc,eAAe;AAAA,IAC7B,YAAY,aAAa;AAAA,IACzB;AAAA,IACA;AAAA,EACD;AACD;AASA,SAAS,kBACR,WACA,QACwB;AACxB,QAAM,QAAQ,oBAAqB,UAAU,UAAU,MAAO;AAC9D,MACC,CAAE,SACF,CAAE,UAAU,gBACZ,WAAc,UAAU,QACvB;AACD,WAAO;AAAA,EACR;AAEA,QAAM,aAAa,MAAM,IAAK,YAAa;AAC3C,QAAM,eAAe,YAAY,IAAK,UAAU,YAAa;AAE7D,QAAM,mBAAmB,cAAE;AAAA,IAC1B;AAAA,IACA,UAAU;AAAA,EACX;AAEA,SAAO;AAAA,IACN;AAAA,IACA,gBAAgB,UAAU;AAAA,EAC3B;AACD;AASA,SAAS,oBACR,SACA,QACgB;AAChB,aAAY,SAAS,QAAS;AAC7B,QAAK,MAAM,IAAK,UAAW,MAAM,SAAU;AAC1C,aAAO;AAAA,IACR;AAEA,UAAM,cAAc,MAAM,IAAK,aAAc;AAE7C,QAAK,eAAe,YAAY,SAAS,GAAI;AAC5C,YAAM,aAAa,oBAAqB,SAAS,WAAY;AAE7D,UAAK,YAAa;AACjB,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AASO,SAAS,yBACf,YACA,YACU;AACV,MAAK,WAAW,SAAS,WAAW,MAAO;AAC1C,WAAO;AAAA,EACR;AAEA,UAAS,WAAW,MAAO;AAAA,IAC1B,KAAK;AACJ,aAAO;AAAA,IAER,KAAK;AACJ,aACC,WAAW,YACR,WAAgC,WACnC;AAAA,QACC,WAAW;AAAA,QACT,WAAgC;AAAA,MACnC;AAAA,IAGF,KAAK;AACJ,aACC,WAAW,YACR,WAAoC,WACvC;AAAA,QACC,WAAW;AAAA,QACT,WAAoC;AAAA,MACvC,KACA;AAAA,QACC,WAAW;AAAA,QACT,WAAoC;AAAA,MACvC;AAAA,IAGF,KAAK;AACJ,aACC,WAAW,iBACR,WAA0C,gBAC7C,WAAW,eACR,WAA0C,cAC7C;AAAA,QACC,WAAW;AAAA,QACT,WACA;AAAA,MACH,KACA;AAAA,QACC,WAAW;AAAA,QACT,WACA;AAAA,MACH;AAAA,IAEF,KAAK;AACJ,aACC,WAAW,YACT,WAAoC;AAAA,IAGxC;AACC,aAAO;AAAA,EACT;AACD;AASA,SAAS,wBACR,iBACA,iBACU;AACV,QAAM,0BACL,KAAK,UAAW,gBAAgB,gBAAiB,MACjD,KAAK,UAAW,gBAAgB,gBAAiB;AAIlD,QAAM,wBACL,gBAAgB,mBAAmB,gBAAgB;AAEpD,SAAO,2BAA2B;AACnC;",
|
|
6
6
|
"names": ["SelectionType", "cursorStartPosition", "cursorEndPosition"]
|
|
7
7
|
}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
// packages/core-data/src/awareness/base-awareness.ts
|
|
2
|
-
import {
|
|
2
|
+
import { resolveSelect } from "@wordpress/data";
|
|
3
3
|
import { AwarenessState } from "@wordpress/sync";
|
|
4
4
|
import { STORE_NAME as coreStore } from "../name.mjs";
|
|
5
5
|
import { generateUserInfo, areUserInfosEqual } from "./utils.mjs";
|
|
6
6
|
var BaseAwarenessState = class extends AwarenessState {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.setCurrentUserInfo();
|
|
7
|
+
onSetUp() {
|
|
8
|
+
void this.setCurrentUserInfo();
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
11
|
* Set the current user info in the local state.
|
|
13
12
|
*/
|
|
14
|
-
setCurrentUserInfo() {
|
|
13
|
+
async setCurrentUserInfo() {
|
|
15
14
|
const states = this.getStates();
|
|
16
15
|
const otherUserColors = Array.from(states.entries()).filter(
|
|
17
16
|
([clientId, state]) => state.userInfo && clientId !== this.clientID
|
|
18
17
|
).map(([, state]) => state.userInfo.color).filter(Boolean);
|
|
19
|
-
const currentUser =
|
|
18
|
+
const currentUser = await resolveSelect(coreStore).getCurrentUser();
|
|
20
19
|
const userInfo = generateUserInfo(currentUser, otherUserColors);
|
|
21
20
|
this.setLocalStateField("userInfo", userInfo);
|
|
22
21
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/awareness/base-awareness.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {
|
|
5
|
-
"mappings": ";AAGA,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { resolveSelect } from '@wordpress/data';\nimport { AwarenessState } from '@wordpress/sync';\n\n/**\n * Internal dependencies\n */\nimport { STORE_NAME as coreStore } from '../name';\nimport { generateUserInfo, areUserInfosEqual } from './utils';\n\nimport type { BaseState } from './types';\n\nexport abstract class BaseAwarenessState<\n\tState extends BaseState,\n> extends AwarenessState< State > {\n\tprotected onSetUp(): void {\n\t\tvoid this.setCurrentUserInfo();\n\t}\n\n\t/**\n\t * Set the current user info in the local state.\n\t */\n\tprivate async setCurrentUserInfo(): Promise< void > {\n\t\tconst states = this.getStates();\n\t\tconst otherUserColors = Array.from( states.entries() )\n\t\t\t.filter(\n\t\t\t\t( [ clientId, state ] ) =>\n\t\t\t\t\tstate.userInfo && clientId !== this.clientID\n\t\t\t)\n\t\t\t.map( ( [ , state ] ) => state.userInfo.color )\n\t\t\t.filter( Boolean );\n\n\t\t// Get current user info and set it in local state.\n\t\tconst currentUser = await resolveSelect( coreStore ).getCurrentUser();\n\t\tconst userInfo = generateUserInfo( currentUser, otherUserColors );\n\t\tthis.setLocalStateField( 'userInfo', userInfo );\n\t}\n}\n\nexport const baseEqualityFieldChecks = {\n\tuserInfo: areUserInfosEqual,\n};\n\nexport class BaseAwareness extends BaseAwarenessState< BaseState > {\n\tprotected equalityFieldChecks = baseEqualityFieldChecks;\n}\n"],
|
|
5
|
+
"mappings": ";AAGA,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAK/B,SAAS,cAAc,iBAAiB;AACxC,SAAS,kBAAkB,yBAAyB;AAI7C,IAAe,qBAAf,cAEG,eAAwB;AAAA,EACvB,UAAgB;AACzB,SAAK,KAAK,mBAAmB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBAAsC;AACnD,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,kBAAkB,MAAM,KAAM,OAAO,QAAQ,CAAE,EACnD;AAAA,MACA,CAAE,CAAE,UAAU,KAAM,MACnB,MAAM,YAAY,aAAa,KAAK;AAAA,IACtC,EACC,IAAK,CAAE,CAAE,EAAE,KAAM,MAAO,MAAM,SAAS,KAAM,EAC7C,OAAQ,OAAQ;AAGlB,UAAM,cAAc,MAAM,cAAe,SAAU,EAAE,eAAe;AACpE,UAAM,WAAW,iBAAkB,aAAa,eAAgB;AAChE,SAAK,mBAAoB,YAAY,QAAS;AAAA,EAC/C;AACD;AAEO,IAAM,0BAA0B;AAAA,EACtC,UAAU;AACX;AAEO,IAAM,gBAAN,cAA4B,mBAAgC;AAAA,EACxD,sBAAsB;AACjC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// packages/core-data/src/awareness/post-editor-awareness.ts
|
|
2
2
|
import { dispatch, select, subscribe } from "@wordpress/data";
|
|
3
|
+
import { Y } from "@wordpress/sync";
|
|
3
4
|
import { store as blockEditorStore } from "@wordpress/block-editor";
|
|
4
5
|
import { BaseAwarenessState, baseEqualityFieldChecks } from "./base-awareness.mjs";
|
|
5
6
|
import {
|
|
@@ -22,8 +23,8 @@ var PostEditorAwareness = class extends BaseAwarenessState {
|
|
|
22
23
|
...baseEqualityFieldChecks,
|
|
23
24
|
editorState: this.areEditorStatesEqual
|
|
24
25
|
};
|
|
25
|
-
|
|
26
|
-
super.
|
|
26
|
+
onSetUp() {
|
|
27
|
+
super.onSetUp();
|
|
27
28
|
this.subscribeToUserSelectionChanges();
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
@@ -104,6 +105,78 @@ var PostEditorAwareness = class extends BaseAwarenessState {
|
|
|
104
105
|
}
|
|
105
106
|
return areSelectionsStatesEqual(state1.selection, state2.selection);
|
|
106
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Get the absolute position index from a selection cursor.
|
|
110
|
+
*
|
|
111
|
+
* @param selection - The selection cursor.
|
|
112
|
+
* @return The absolute position index, or null if not found.
|
|
113
|
+
*/
|
|
114
|
+
getAbsolutePositionIndex(selection) {
|
|
115
|
+
return Y.createAbsolutePositionFromRelativePosition(
|
|
116
|
+
selection.cursorPosition.relativePosition,
|
|
117
|
+
this.doc
|
|
118
|
+
)?.index ?? null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Type guard to check if a struct is a Y.Item (not Y.GC)
|
|
122
|
+
* @param struct - The struct to check.
|
|
123
|
+
* @return True if the struct is a Y.Item, false otherwise.
|
|
124
|
+
*/
|
|
125
|
+
isYItem(struct) {
|
|
126
|
+
return "content" in struct;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get data for debugging, using the awareness state.
|
|
130
|
+
*
|
|
131
|
+
* @return {YDocDebugData} The debug data.
|
|
132
|
+
*/
|
|
133
|
+
getDebugData() {
|
|
134
|
+
const ydoc = this.doc;
|
|
135
|
+
const docData = Object.fromEntries(
|
|
136
|
+
Array.from(ydoc.share, ([key, value]) => [
|
|
137
|
+
key,
|
|
138
|
+
value.toJSON()
|
|
139
|
+
])
|
|
140
|
+
);
|
|
141
|
+
const userMapData = new Map(
|
|
142
|
+
Array.from(this.getSeenStates().entries()).map(
|
|
143
|
+
([clientId, userState]) => [
|
|
144
|
+
String(clientId),
|
|
145
|
+
{
|
|
146
|
+
name: userState.userInfo.name,
|
|
147
|
+
wpUserId: userState.userInfo.id
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
const serializableClientItems = {};
|
|
153
|
+
ydoc.store.clients.forEach((structs, clientId) => {
|
|
154
|
+
const items = structs.filter(this.isYItem);
|
|
155
|
+
serializableClientItems[clientId] = items.map((item) => {
|
|
156
|
+
const { left, right, ...rest } = item;
|
|
157
|
+
return {
|
|
158
|
+
...rest,
|
|
159
|
+
left: left ? {
|
|
160
|
+
id: left.id,
|
|
161
|
+
length: left.length,
|
|
162
|
+
origin: left.origin,
|
|
163
|
+
content: left.content
|
|
164
|
+
} : null,
|
|
165
|
+
right: right ? {
|
|
166
|
+
id: right.id,
|
|
167
|
+
length: right.length,
|
|
168
|
+
origin: right.origin,
|
|
169
|
+
content: right.content
|
|
170
|
+
} : null
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
doc: docData,
|
|
176
|
+
clients: serializableClientItems,
|
|
177
|
+
userMap: Object.fromEntries(userMapData)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
107
180
|
};
|
|
108
181
|
export {
|
|
109
182
|
PostEditorAwareness
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/awareness/post-editor-awareness.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select, subscribe } from '@wordpress/data';\nimport
|
|
5
|
-
"mappings": ";AAGA,SAAS,UAAU,QAAQ,iBAAiB;
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select, subscribe } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport { BaseAwarenessState, baseEqualityFieldChecks } from './base-awareness';\nimport {\n\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS,\n\tLOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS,\n} from './config';\nimport { STORE_NAME as coreStore } from '../name';\nimport {\n\tareSelectionsStatesEqual,\n\tgetSelectionState,\n} from '../utils/crdt-user-selections';\n\nimport type { SelectionCursor, WPBlockSelection } from '../types';\nimport type {\n\tDebugUserData,\n\tEditorState,\n\tPostEditorState,\n\tSerializableYItem,\n\tYDocDebugData,\n} from './types';\n\nexport class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {\n\tprotected equalityFieldChecks = {\n\t\t...baseEqualityFieldChecks,\n\t\teditorState: this.areEditorStatesEqual,\n\t};\n\n\tpublic constructor(\n\t\tdoc: Y.Doc,\n\t\tprivate kind: string,\n\t\tprivate name: string,\n\t\tprivate postId: number\n\t) {\n\t\tsuper( doc );\n\t}\n\n\tprotected onSetUp(): void {\n\t\tsuper.onSetUp();\n\n\t\tthis.subscribeToUserSelectionChanges();\n\t}\n\n\t/**\n\t * Subscribe to user selection changes and update the selection state.\n\t */\n\tprivate subscribeToUserSelectionChanges(): void {\n\t\tconst {\n\t\t\tgetSelectionStart,\n\t\t\tgetSelectionEnd,\n\t\t\tgetSelectedBlocksInitialCaretPosition,\n\t\t} = select( blockEditorStore );\n\n\t\t// Keep track of the current selection in the outer scope so we can compare\n\t\t// in the subscription.\n\t\tlet selectionStart = getSelectionStart();\n\t\tlet selectionEnd = getSelectionEnd();\n\t\tlet localCursorTimeout: NodeJS.Timeout | null = null;\n\n\t\tsubscribe( () => {\n\t\t\tconst newSelectionStart = getSelectionStart();\n\t\t\tconst newSelectionEnd = getSelectionEnd();\n\n\t\t\tif (\n\t\t\t\tnewSelectionStart === selectionStart &&\n\t\t\t\tnewSelectionEnd === selectionEnd\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tselectionStart = newSelectionStart;\n\t\t\tselectionEnd = newSelectionEnd;\n\n\t\t\t// Typically selection position is only persisted after typing in a block, which\n\t\t\t// can cause selection position to be reset by other users making block updates.\n\t\t\t// Ensure we update the controlled selection right away, persisting our cursor position locally.\n\t\t\tconst initialPosition = getSelectedBlocksInitialCaretPosition();\n\t\t\tvoid this.updateSelectionInEntityRecord(\n\t\t\t\tselectionStart,\n\t\t\t\tselectionEnd,\n\t\t\t\tinitialPosition\n\t\t\t);\n\n\t\t\t// We receive two selection changes in quick succession\n\t\t\t// from local selection events:\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: undefined }\n\t\t\t// { clientId: \"123...\", attributeKey: \"content\", offset: 554 }\n\t\t\t// Add a short debounce to avoid sending the first selection change.\n\t\t\tif ( localCursorTimeout ) {\n\t\t\t\tclearTimeout( localCursorTimeout );\n\t\t\t}\n\n\t\t\tlocalCursorTimeout = setTimeout( () => {\n\t\t\t\tconst selectionState = getSelectionState(\n\t\t\t\t\tselectionStart,\n\t\t\t\t\tselectionEnd,\n\t\t\t\t\tthis.doc\n\t\t\t\t);\n\n\t\t\t\tthis.setThrottledLocalStateField(\n\t\t\t\t\t'editorState',\n\t\t\t\t\t{ selection: selectionState },\n\t\t\t\t\tAWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS\n\t\t\t\t);\n\t\t\t}, LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS );\n\t\t} );\n\t}\n\n\t/**\n\t * Update the entity record with the current user's selection.\n\t *\n\t * @param selectionStart - The start position of the selection.\n\t * @param selectionEnd - The end position of the selection.\n\t * @param initialPosition - The initial position of the selection.\n\t */\n\tprivate async updateSelectionInEntityRecord(\n\t\tselectionStart: WPBlockSelection,\n\t\tselectionEnd: WPBlockSelection,\n\t\tinitialPosition: number | null\n\t): Promise< void > {\n\t\t// Send an entityRecord `selection` update if we have a selection.\n\t\t//\n\t\t// Normally WordPress updates the `selection` property of the post when changes are made to blocks.\n\t\t// In a multi-user setup, block changes can occur from other users. When an entity is updated from another\n\t\t// user's changes, useBlockSync() in Gutenberg will reset the user's selection to the last saved selection.\n\t\t//\n\t\t// Manually adding an edit for each movement ensures that other user's changes to the document will\n\t\t// not cause the local user's selection to reset to the last local change location.\n\t\tconst edits = {\n\t\t\tselection: { selectionStart, selectionEnd, initialPosition },\n\t\t};\n\n\t\tconst options = {\n\t\t\tundoIgnore: true,\n\t\t};\n\n\t\t// @ts-ignore Types are not provided when using store name instead of store instance.\n\t\tdispatch( coreStore ).editEntityRecord(\n\t\t\tthis.kind,\n\t\t\tthis.name,\n\t\t\tthis.postId,\n\t\t\tedits,\n\t\t\toptions\n\t\t);\n\t}\n\n\t/**\n\t * Check if two editor states are equal.\n\t *\n\t * @param state1 - The first editor state.\n\t * @param state2 - The second editor state.\n\t * @return True if the editor states are equal, false otherwise.\n\t */\n\tprivate areEditorStatesEqual(\n\t\tstate1?: EditorState,\n\t\tstate2?: EditorState\n\t): boolean {\n\t\tif ( ! state1 || ! state2 ) {\n\t\t\treturn state1 === state2;\n\t\t}\n\n\t\treturn areSelectionsStatesEqual( state1.selection, state2.selection );\n\t}\n\n\t/**\n\t * Get the absolute position index from a selection cursor.\n\t *\n\t * @param selection - The selection cursor.\n\t * @return The absolute position index, or null if not found.\n\t */\n\tpublic getAbsolutePositionIndex(\n\t\tselection: SelectionCursor\n\t): number | null {\n\t\treturn (\n\t\t\tY.createAbsolutePositionFromRelativePosition(\n\t\t\t\tselection.cursorPosition.relativePosition,\n\t\t\t\tthis.doc\n\t\t\t)?.index ?? null\n\t\t);\n\t}\n\n\t/**\n\t * Type guard to check if a struct is a Y.Item (not Y.GC)\n\t * @param struct - The struct to check.\n\t * @return True if the struct is a Y.Item, false otherwise.\n\t */\n\tprivate isYItem( struct: Y.Item | Y.GC ): struct is Y.Item {\n\t\treturn 'content' in struct;\n\t}\n\n\t/**\n\t * Get data for debugging, using the awareness state.\n\t *\n\t * @return {YDocDebugData} The debug data.\n\t */\n\tpublic getDebugData(): YDocDebugData {\n\t\tconst ydoc = this.doc;\n\n\t\t// Manually extract doc data to avoid deprecated toJSON method\n\t\tconst docData: Record< string, unknown > = Object.fromEntries(\n\t\t\tArray.from( ydoc.share, ( [ key, value ] ) => [\n\t\t\t\tkey,\n\t\t\t\tvalue.toJSON(),\n\t\t\t] )\n\t\t);\n\n\t\t// Build userMap from awareness store (all users seen this session)\n\t\tconst userMapData = new Map< string, DebugUserData >(\n\t\t\tArray.from( this.getSeenStates().entries() ).map(\n\t\t\t\t( [ clientId, userState ] ) => [\n\t\t\t\t\tString( clientId ),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: userState.userInfo.name,\n\t\t\t\t\t\twpUserId: userState.userInfo.id,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t)\n\t\t);\n\n\t\t// Serialize Yjs client items to avoid deep nesting\n\t\tconst serializableClientItems: Record<\n\t\t\tnumber,\n\t\t\tArray< SerializableYItem >\n\t\t> = {};\n\n\t\tydoc.store.clients.forEach( ( structs, clientId ) => {\n\t\t\t// Filter for Y.Item only (skip Y.GC garbage collection structs)\n\t\t\tconst items = structs.filter( this.isYItem );\n\n\t\t\tserializableClientItems[ clientId ] = items.map( ( item ) => {\n\t\t\t\tconst { left, right, ...rest } = item;\n\n\t\t\t\treturn {\n\t\t\t\t\t...rest,\n\t\t\t\t\tleft: left\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: left.id,\n\t\t\t\t\t\t\t\tlength: left.length,\n\t\t\t\t\t\t\t\torigin: left.origin,\n\t\t\t\t\t\t\t\tcontent: left.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t\tright: right\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: right.id,\n\t\t\t\t\t\t\t\tlength: right.length,\n\t\t\t\t\t\t\t\torigin: right.origin,\n\t\t\t\t\t\t\t\tcontent: right.content,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: null,\n\t\t\t\t};\n\t\t\t} );\n\t\t} );\n\n\t\treturn {\n\t\t\tdoc: docData,\n\t\t\tclients: serializableClientItems,\n\t\t\tuserMap: Object.fromEntries( userMapData ),\n\t\t};\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|