@wordpress/core-data 7.48.0 → 7.48.2-next.v.202606191442.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/build/actions.cjs +1 -7
- package/build/actions.cjs.map +3 -3
- package/build/awareness/block-lookup.cjs +14 -26
- package/build/awareness/block-lookup.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +4 -3
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/entities.cjs +6 -3
- package/build/entities.cjs.map +2 -2
- package/build/entity-types/helpers.cjs.map +1 -1
- package/build/hooks/use-entity-record.cjs +21 -19
- package/build/hooks/use-entity-record.cjs.map +3 -3
- package/build/hooks/use-entity-records.cjs +22 -20
- package/build/hooks/use-entity-records.cjs.map +3 -3
- package/build/hooks/use-post-editor-awareness-state.cjs +8 -2
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/hooks/use-query-select.cjs +2 -20
- package/build/hooks/use-query-select.cjs.map +2 -2
- package/build/hooks/utils.cjs +53 -0
- package/build/hooks/utils.cjs.map +7 -0
- package/build/private-actions.cjs +8 -0
- package/build/private-actions.cjs.map +2 -2
- package/build/private-selectors.cjs.map +2 -2
- package/build/reducer.cjs +23 -7
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +13 -8
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +7 -0
- package/build/selectors.cjs.map +2 -2
- package/build/types.cjs.map +1 -1
- package/build/utils/clear-unchanged-edits.cjs +51 -0
- package/build/utils/clear-unchanged-edits.cjs.map +7 -0
- package/build/utils/crdt-blocks.cjs +12 -2
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs.map +1 -1
- package/build/utils/crdt-utils.cjs.map +1 -1
- package/build/utils/crdt.cjs +2 -1
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +6 -0
- package/build/utils/index.cjs.map +2 -2
- package/build/utils/save-crdt-doc.cjs +75 -0
- package/build/utils/save-crdt-doc.cjs.map +7 -0
- package/build/utils/set-nested-value.cjs.map +1 -1
- package/build-module/actions.mjs +2 -8
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/block-lookup.mjs +13 -26
- package/build-module/awareness/block-lookup.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +4 -3
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +6 -3
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/use-entity-record.mjs +21 -19
- package/build-module/hooks/use-entity-record.mjs.map +2 -2
- package/build-module/hooks/use-entity-records.mjs +20 -18
- package/build-module/hooks/use-entity-records.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +9 -3
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/hooks/use-query-select.mjs +2 -20
- package/build-module/hooks/use-query-select.mjs.map +2 -2
- package/build-module/hooks/utils.mjs +28 -0
- package/build-module/hooks/utils.mjs.map +7 -0
- package/build-module/private-actions.mjs +7 -0
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +23 -8
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +15 -9
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +7 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/utils/clear-unchanged-edits.mjs +20 -0
- package/build-module/utils/clear-unchanged-edits.mjs.map +7 -0
- package/build-module/utils/crdt-blocks.mjs +12 -2
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs.map +1 -1
- package/build-module/utils/crdt-utils.mjs.map +1 -1
- package/build-module/utils/crdt.mjs +2 -1
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +24 -20
- package/build-module/utils/index.mjs.map +2 -2
- package/build-module/utils/save-crdt-doc.mjs +40 -0
- package/build-module/utils/save-crdt-doc.mjs.map +7 -0
- package/build-module/utils/set-nested-value.mjs.map +1 -1
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/block-lookup.d.ts +27 -7
- package/build-types/awareness/block-lookup.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +3 -1
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/use-entity-record.d.ts +4 -0
- package/build-types/hooks/use-entity-record.d.ts.map +1 -1
- package/build-types/hooks/use-entity-records.d.ts +5 -1
- package/build-types/hooks/use-entity-records.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/hooks/utils.d.ts +22 -0
- package/build-types/hooks/utils.d.ts.map +1 -0
- package/build-types/index.d.ts +8 -8
- package/build-types/private-actions.d.ts +15 -0
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +0 -12
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts +15 -0
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +12 -8
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/utils/clear-unchanged-edits.d.ts +12 -0
- package/build-types/utils/clear-unchanged-edits.d.ts.map +1 -0
- package/build-types/utils/crdt-blocks.d.ts +5 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +2 -0
- package/build-types/utils/index.d.ts.map +1 -1
- package/build-types/utils/save-crdt-doc.d.ts +8 -0
- package/build-types/utils/save-crdt-doc.d.ts.map +1 -0
- package/package.json +27 -20
- package/src/actions.js +2 -10
- package/src/awareness/block-lookup.ts +21 -62
- package/src/awareness/post-editor-awareness.ts +8 -3
- package/src/awareness/test/block-lookup.ts +98 -94
- package/src/awareness/test/post-editor-awareness.ts +177 -180
- package/src/entities.js +14 -3
- package/src/hooks/test/use-entity-record.js +5 -1
- package/src/hooks/test/use-post-editor-awareness-state.ts +10 -2
- package/src/hooks/use-entity-record.ts +26 -19
- package/src/hooks/use-entity-records.ts +26 -18
- package/src/hooks/use-post-editor-awareness-state.ts +20 -7
- package/src/hooks/use-query-select.ts +2 -23
- package/src/hooks/utils.ts +40 -0
- package/src/private-actions.js +18 -0
- package/src/private-selectors.ts +0 -12
- package/src/reducer.js +30 -9
- package/src/resolvers.js +20 -13
- package/src/selectors.ts +11 -0
- package/src/test/entities.js +51 -0
- package/src/test/private-selectors.js +66 -0
- package/src/test/reducer.js +44 -0
- package/src/test/resolvers.js +121 -113
- package/src/test/selectors.js +48 -0
- package/src/utils/clear-unchanged-edits.ts +34 -0
- package/src/utils/crdt-blocks.ts +27 -22
- package/src/utils/crdt.ts +2 -1
- package/src/utils/index.js +2 -0
- package/src/utils/save-crdt-doc.js +64 -0
- package/src/utils/test/clear-unchanged-edits.js +42 -0
- package/src/utils/test/crdt-blocks.ts +57 -2
- package/src/utils/test/rtc-rich-text-cursor-scope.test.js +2 -2
- package/src/utils/test/save-crdt-doc.js +185 -0
|
@@ -8,9 +8,9 @@ import { useMemo } from '@wordpress/element';
|
|
|
8
8
|
/**
|
|
9
9
|
* Internal dependencies
|
|
10
10
|
*/
|
|
11
|
-
import useQuerySelect from './use-query-select';
|
|
12
11
|
import { store as coreStore } from '../';
|
|
13
12
|
import type { Status } from './constants';
|
|
13
|
+
import { getResolutionStatus } from './utils';
|
|
14
14
|
|
|
15
15
|
export interface EntityRecordResolution< RecordType > {
|
|
16
16
|
/** The requested entity record */
|
|
@@ -38,6 +38,11 @@ export interface EntityRecordResolution< RecordType > {
|
|
|
38
38
|
*/
|
|
39
39
|
hasEdits: boolean;
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Has the resolution started?
|
|
43
|
+
*/
|
|
44
|
+
hasStarted: boolean;
|
|
45
|
+
|
|
41
46
|
/**
|
|
42
47
|
* Is the record resolved by now?
|
|
43
48
|
*/
|
|
@@ -169,57 +174,59 @@ export default function useEntityRecord< RecordType >(
|
|
|
169
174
|
[ editEntityRecord, kind, name, recordId, saveEditedEntityRecord ]
|
|
170
175
|
);
|
|
171
176
|
|
|
172
|
-
const { editedRecord, hasEdits, edits } = useSelect(
|
|
177
|
+
const { record, editedRecord, hasEdits, edits, ...resolution } = useSelect(
|
|
173
178
|
( select ) => {
|
|
174
179
|
if ( ! options.enabled ) {
|
|
175
180
|
return {
|
|
181
|
+
record: null,
|
|
176
182
|
editedRecord: EMPTY_OBJECT,
|
|
177
183
|
hasEdits: false,
|
|
178
184
|
edits: EMPTY_OBJECT,
|
|
185
|
+
...getResolutionStatus(),
|
|
179
186
|
};
|
|
180
187
|
}
|
|
181
188
|
|
|
189
|
+
const storeSelectors = select( coreStore );
|
|
190
|
+
const resolutionStatus = storeSelectors.getResolutionState(
|
|
191
|
+
'getEntityRecord',
|
|
192
|
+
[ kind, name, recordId ]
|
|
193
|
+
)?.status;
|
|
194
|
+
|
|
182
195
|
return {
|
|
183
|
-
|
|
196
|
+
record: ( storeSelectors.getEntityRecord(
|
|
197
|
+
kind,
|
|
198
|
+
name,
|
|
199
|
+
recordId
|
|
200
|
+
) ?? null ) as RecordType | null,
|
|
201
|
+
editedRecord: storeSelectors.getEditedEntityRecord(
|
|
184
202
|
kind,
|
|
185
203
|
name,
|
|
186
204
|
recordId
|
|
187
205
|
),
|
|
188
|
-
hasEdits:
|
|
206
|
+
hasEdits: storeSelectors.hasEditsForEntityRecord(
|
|
189
207
|
kind,
|
|
190
208
|
name,
|
|
191
209
|
recordId
|
|
192
210
|
),
|
|
193
|
-
edits:
|
|
211
|
+
edits: storeSelectors.getEntityRecordNonTransientEdits(
|
|
194
212
|
kind,
|
|
195
213
|
name,
|
|
196
214
|
recordId
|
|
197
215
|
),
|
|
216
|
+
...getResolutionStatus( resolutionStatus ),
|
|
198
217
|
};
|
|
199
218
|
},
|
|
200
219
|
[ kind, name, recordId, options.enabled ]
|
|
201
220
|
);
|
|
202
221
|
|
|
203
|
-
const { data: record, ...querySelectRest } = useQuerySelect(
|
|
204
|
-
( query ) => {
|
|
205
|
-
if ( ! options.enabled ) {
|
|
206
|
-
return {
|
|
207
|
-
data: null,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
return query( coreStore ).getEntityRecord( kind, name, recordId );
|
|
211
|
-
},
|
|
212
|
-
[ kind, name, recordId, options.enabled ]
|
|
213
|
-
);
|
|
214
|
-
|
|
215
222
|
return {
|
|
216
223
|
record,
|
|
217
224
|
editedRecord,
|
|
218
225
|
hasEdits,
|
|
219
226
|
edits,
|
|
220
|
-
...
|
|
227
|
+
...resolution,
|
|
221
228
|
...mutations,
|
|
222
|
-
}
|
|
229
|
+
} as EntityRecordResolution< RecordType >;
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
export function useDeprecatedEntityRecord(
|
|
@@ -9,14 +9,14 @@ import { useMemo } from '@wordpress/element';
|
|
|
9
9
|
/**
|
|
10
10
|
* Internal dependencies
|
|
11
11
|
*/
|
|
12
|
-
import useQuerySelect from './use-query-select';
|
|
13
12
|
import { store as coreStore } from '../';
|
|
14
13
|
import type { Options } from './use-entity-record';
|
|
15
14
|
import type { Status } from './constants';
|
|
15
|
+
import { getResolutionStatus } from './utils';
|
|
16
16
|
import { unlock } from '../lock-unlock';
|
|
17
17
|
import { getNormalizedCommaSeparable } from '../utils';
|
|
18
18
|
|
|
19
|
-
interface EntityRecordsResolution< RecordType > {
|
|
19
|
+
export interface EntityRecordsResolution< RecordType > {
|
|
20
20
|
/** The requested entity records */
|
|
21
21
|
records: RecordType[] | null;
|
|
22
22
|
|
|
@@ -25,6 +25,11 @@ interface EntityRecordsResolution< RecordType > {
|
|
|
25
25
|
*/
|
|
26
26
|
isResolving: boolean;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Has the resolution started?
|
|
30
|
+
*/
|
|
31
|
+
hasStarted: boolean;
|
|
32
|
+
|
|
28
33
|
/**
|
|
29
34
|
* Is the record resolved by now?
|
|
30
35
|
*/
|
|
@@ -108,38 +113,41 @@ export default function useEntityRecords< RecordType >(
|
|
|
108
113
|
// if the values remain the same.
|
|
109
114
|
const queryAsString = addQueryArgs( '', queryArgs );
|
|
110
115
|
|
|
111
|
-
const {
|
|
112
|
-
( query ) => {
|
|
113
|
-
if ( ! options.enabled ) {
|
|
114
|
-
return {
|
|
115
|
-
// Avoiding returning a new reference on every execution.
|
|
116
|
-
data: EMPTY_ARRAY,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
return query( coreStore ).getEntityRecords( kind, name, queryArgs );
|
|
120
|
-
},
|
|
121
|
-
[ kind, name, queryAsString, options.enabled ]
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const { totalItems, totalPages } = useSelect(
|
|
116
|
+
const { records, totalItems, totalPages, ...rest } = useSelect(
|
|
125
117
|
( select ) => {
|
|
126
118
|
if ( ! options.enabled ) {
|
|
127
119
|
return {
|
|
120
|
+
// Avoiding returning a new reference on every execution.
|
|
121
|
+
records: EMPTY_ARRAY,
|
|
128
122
|
totalItems: null,
|
|
129
123
|
totalPages: null,
|
|
124
|
+
...getResolutionStatus(),
|
|
130
125
|
};
|
|
131
126
|
}
|
|
127
|
+
|
|
128
|
+
const storeSelectors = select( coreStore );
|
|
129
|
+
const resolutionStatus = storeSelectors.getResolutionState(
|
|
130
|
+
'getEntityRecords',
|
|
131
|
+
[ kind, name, queryArgs ]
|
|
132
|
+
)?.status;
|
|
133
|
+
|
|
132
134
|
return {
|
|
133
|
-
|
|
135
|
+
records: storeSelectors.getEntityRecords(
|
|
136
|
+
kind,
|
|
137
|
+
name,
|
|
138
|
+
queryArgs
|
|
139
|
+
) as RecordType[] | null,
|
|
140
|
+
totalItems: storeSelectors.getEntityRecordsTotalItems(
|
|
134
141
|
kind,
|
|
135
142
|
name,
|
|
136
143
|
queryArgs
|
|
137
144
|
),
|
|
138
|
-
totalPages:
|
|
145
|
+
totalPages: storeSelectors.getEntityRecordsTotalPages(
|
|
139
146
|
kind,
|
|
140
147
|
name,
|
|
141
148
|
queryArgs
|
|
142
149
|
),
|
|
150
|
+
...getResolutionStatus( resolutionStatus ),
|
|
143
151
|
};
|
|
144
152
|
},
|
|
145
153
|
[ kind, name, queryAsString, options.enabled ]
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { usePrevious } from '@wordpress/compose';
|
|
5
|
-
import { useEffect, useState } from '@wordpress/element';
|
|
5
|
+
import { useEffect, useState, useCallback } from '@wordpress/element';
|
|
6
6
|
import type { Y } from '@wordpress/sync';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Internal dependencies
|
|
10
10
|
*/
|
|
11
11
|
import { getSyncManager } from '../sync';
|
|
12
|
+
import { usePostContentBlocks } from '../awareness/block-lookup';
|
|
13
|
+
import type { EditorStoreBlock } from '../awareness/block-lookup';
|
|
12
14
|
import type {
|
|
13
15
|
PostEditorAwarenessState as ActiveCollaborator,
|
|
14
16
|
PostSaveEvent,
|
|
@@ -19,7 +21,10 @@ import type { PostEditorAwareness } from '../awareness/post-editor-awareness';
|
|
|
19
21
|
|
|
20
22
|
interface AwarenessState {
|
|
21
23
|
activeCollaborators: ActiveCollaborator[];
|
|
22
|
-
resolveSelection: (
|
|
24
|
+
resolveSelection: (
|
|
25
|
+
selection: SelectionState,
|
|
26
|
+
blocks: EditorStoreBlock[]
|
|
27
|
+
) => ResolvedSelection;
|
|
23
28
|
getDebugData: () => YDocDebugData;
|
|
24
29
|
isCurrentCollaboratorDisconnected: boolean;
|
|
25
30
|
}
|
|
@@ -49,8 +54,10 @@ function getAwarenessState(
|
|
|
49
54
|
|
|
50
55
|
return {
|
|
51
56
|
activeCollaborators,
|
|
52
|
-
resolveSelection: (
|
|
53
|
-
|
|
57
|
+
resolveSelection: (
|
|
58
|
+
selection: SelectionState,
|
|
59
|
+
blocks: EditorStoreBlock[]
|
|
60
|
+
) => awareness.convertSelectionStateToAbsolute( selection, blocks ),
|
|
54
61
|
getDebugData: () => awareness.getDebugData(),
|
|
55
62
|
isCurrentCollaboratorDisconnected:
|
|
56
63
|
activeCollaborators.find( ( collaborator ) => collaborator.isMe )
|
|
@@ -124,7 +131,13 @@ export function useResolvedSelection(
|
|
|
124
131
|
postId: number | null,
|
|
125
132
|
postType: string | null
|
|
126
133
|
): ( selection: SelectionState ) => ResolvedSelection {
|
|
127
|
-
|
|
134
|
+
const blocks = usePostContentBlocks();
|
|
135
|
+
const awarenessState = usePostEditorAwarenessState( postId, postType );
|
|
136
|
+
return useCallback(
|
|
137
|
+
( selection: SelectionState ) =>
|
|
138
|
+
awarenessState.resolveSelection( selection, blocks ),
|
|
139
|
+
[ blocks, awarenessState ]
|
|
140
|
+
);
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
/**
|
|
@@ -158,8 +171,8 @@ export function useIsDisconnected(
|
|
|
158
171
|
|
|
159
172
|
/**
|
|
160
173
|
* Hook that subscribes to the CRDT state map and returns the most recent
|
|
161
|
-
* save event (timestamp + client ID). The state map is
|
|
162
|
-
* `markEntityAsSaved` in `@wordpress/sync
|
|
174
|
+
* user-facing post save event (timestamp + client ID). The state map is
|
|
175
|
+
* updated by `markEntityAsSaved` in `@wordpress/sync`.
|
|
163
176
|
*
|
|
164
177
|
* @param postId The ID of the post.
|
|
165
178
|
* @param postType The type of the post.
|
|
@@ -7,7 +7,7 @@ import { useSelect } from '@wordpress/data';
|
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
9
|
import memoize from 'memize';
|
|
10
|
-
import {
|
|
10
|
+
import { getResolutionStatus } from './utils';
|
|
11
11
|
|
|
12
12
|
export const META_SELECTORS = [
|
|
13
13
|
'getIsResolving',
|
|
@@ -114,30 +114,9 @@ const enrichSelectors = memoize( ( ( selectors ) => {
|
|
|
114
114
|
args
|
|
115
115
|
)?.status;
|
|
116
116
|
|
|
117
|
-
let status;
|
|
118
|
-
switch ( resolutionStatus ) {
|
|
119
|
-
case 'resolving':
|
|
120
|
-
status = Status.Resolving;
|
|
121
|
-
break;
|
|
122
|
-
case 'finished':
|
|
123
|
-
status = Status.Success;
|
|
124
|
-
break;
|
|
125
|
-
case 'error':
|
|
126
|
-
status = Status.Error;
|
|
127
|
-
break;
|
|
128
|
-
case undefined:
|
|
129
|
-
status = Status.Idle;
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
117
|
return {
|
|
134
118
|
data,
|
|
135
|
-
|
|
136
|
-
isResolving: status === Status.Resolving,
|
|
137
|
-
hasStarted: status !== Status.Idle,
|
|
138
|
-
hasResolved:
|
|
139
|
-
status === Status.Success ||
|
|
140
|
-
status === Status.Error,
|
|
119
|
+
...getResolutionStatus( resolutionStatus ),
|
|
141
120
|
};
|
|
142
121
|
},
|
|
143
122
|
} );
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { ResolutionStatus } from '@wordpress/data';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { Status } from './constants';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes a resolution status from the store into the resolution info
|
|
13
|
+
* shared by the entity record hooks and `useQuerySelect`.
|
|
14
|
+
*
|
|
15
|
+
* @param resolutionStatus Status returned by the `getResolutionState` selector.
|
|
16
|
+
* @return Resolution info object.
|
|
17
|
+
*/
|
|
18
|
+
export function getResolutionStatus( resolutionStatus?: ResolutionStatus ) {
|
|
19
|
+
let status: Status;
|
|
20
|
+
switch ( resolutionStatus ) {
|
|
21
|
+
case 'resolving':
|
|
22
|
+
status = Status.Resolving;
|
|
23
|
+
break;
|
|
24
|
+
case 'finished':
|
|
25
|
+
status = Status.Success;
|
|
26
|
+
break;
|
|
27
|
+
case 'error':
|
|
28
|
+
status = Status.Error;
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
status = Status.Idle;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status,
|
|
36
|
+
isResolving: status === Status.Resolving,
|
|
37
|
+
hasStarted: status !== Status.Idle,
|
|
38
|
+
hasResolved: status === Status.Success || status === Status.Error,
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/private-actions.js
CHANGED
|
@@ -197,6 +197,24 @@ export function receiveViewConfig( kind, name, config ) {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Returns an action object used to notify core-data that the sync undo manager
|
|
202
|
+
* state changed outside of the core-data reducer, e.g. The Yjs UndoManager
|
|
203
|
+
* captured an undo level.
|
|
204
|
+
*
|
|
205
|
+
* @param {Object} state The sync undo stack state.
|
|
206
|
+
* @param {boolean} state.hasRedo Whether there are changes to redo.
|
|
207
|
+
* @param {boolean} state.hasUndo Whether there are changes to undo.
|
|
208
|
+
*
|
|
209
|
+
* @return {Object} Action object.
|
|
210
|
+
*/
|
|
211
|
+
export function __unstableNotifySyncUndoManagerChange( state ) {
|
|
212
|
+
return {
|
|
213
|
+
type: 'SYNC_UNDO_MANAGER_CHANGE',
|
|
214
|
+
...state,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
200
218
|
/**
|
|
201
219
|
* Returns an action object used to set the sync connection status for an entity or collection.
|
|
202
220
|
*
|
package/src/private-selectors.ts
CHANGED
|
@@ -21,18 +21,6 @@ const EMPTY_OBJECT = {};
|
|
|
21
21
|
* Returns the previous edit from the current undo offset
|
|
22
22
|
* for the entity records edits history, if any.
|
|
23
23
|
*
|
|
24
|
-
* Known Issue: Every-time state.undoManager changes, the getUndoManager
|
|
25
|
-
* private selector is called (if used within useSelect and things like that)
|
|
26
|
-
* which ensures the UI is always properly reactive. But, it's not the case with
|
|
27
|
-
* the custom "sync" undo manager.
|
|
28
|
-
*
|
|
29
|
-
* Assumption: When an undo/redo is created, other parts of the core-data state
|
|
30
|
-
* are likely changing simultaneously, which will trigger the selectors again.
|
|
31
|
-
*
|
|
32
|
-
* This issue is acceptable based on the assumption above.
|
|
33
|
-
*
|
|
34
|
-
* @see https://github.com/WordPress/gutenberg/pull/72407/files#r2580214235 for more details.
|
|
35
|
-
*
|
|
36
24
|
* @param state State tree.
|
|
37
25
|
*
|
|
38
26
|
* @return The undo manager.
|
package/src/reducer.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createUndoManager } from '@wordpress/undo-manager';
|
|
|
13
13
|
/**
|
|
14
14
|
* Internal dependencies
|
|
15
15
|
*/
|
|
16
|
-
import { ifMatchingAction, replaceAction } from './utils';
|
|
16
|
+
import { clearUnchangedEdits, ifMatchingAction, replaceAction } from './utils';
|
|
17
17
|
import { reducer as queriedDataReducer } from './queried-data';
|
|
18
18
|
import { rootEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities';
|
|
19
19
|
import { ConnectionErrorCode } from './sync';
|
|
@@ -150,19 +150,23 @@ const withMultiEntityRecordEdits = ( reducer ) => ( state, action ) => {
|
|
|
150
150
|
|
|
151
151
|
let newState = state;
|
|
152
152
|
record.forEach( ( { id: { kind, name, recordId }, changes } ) => {
|
|
153
|
+
const persistedRecord =
|
|
154
|
+
state?.queriedData?.items?.default?.[ recordId ];
|
|
155
|
+
const edits = Object.fromEntries(
|
|
156
|
+
Object.entries( changes ).map( ( [ key, value ] ) => [
|
|
157
|
+
key,
|
|
158
|
+
action.type === 'UNDO' ? value.from : value.to,
|
|
159
|
+
] )
|
|
160
|
+
);
|
|
161
|
+
|
|
153
162
|
newState = reducer( newState, {
|
|
154
163
|
type: 'EDIT_ENTITY_RECORD',
|
|
155
164
|
kind,
|
|
156
165
|
name,
|
|
157
166
|
recordId,
|
|
158
|
-
edits
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
action.type === 'UNDO' ? value.from : value.to;
|
|
162
|
-
return acc;
|
|
163
|
-
},
|
|
164
|
-
{}
|
|
165
|
-
),
|
|
167
|
+
// Clear edits matching the persisted record so the entity is
|
|
168
|
+
// no longer dirty after undoing back to its saved state.
|
|
169
|
+
edits: clearUnchangedEdits( edits, persistedRecord ),
|
|
166
170
|
} );
|
|
167
171
|
} );
|
|
168
172
|
return newState;
|
|
@@ -460,6 +464,22 @@ export function undoManager( state = createUndoManager() ) {
|
|
|
460
464
|
return state;
|
|
461
465
|
}
|
|
462
466
|
|
|
467
|
+
// Stores a snapshot of the sync undo manager's undo/redo availability so
|
|
468
|
+
// core-data selectors can react to undo stack changes.
|
|
469
|
+
export function syncUndoManagerState(
|
|
470
|
+
state = { hasRedo: false, hasUndo: false },
|
|
471
|
+
action
|
|
472
|
+
) {
|
|
473
|
+
switch ( action.type ) {
|
|
474
|
+
case 'SYNC_UNDO_MANAGER_CHANGE':
|
|
475
|
+
return {
|
|
476
|
+
hasRedo: action.hasRedo,
|
|
477
|
+
hasUndo: action.hasUndo,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return state;
|
|
481
|
+
}
|
|
482
|
+
|
|
463
483
|
export function editsReference( state = {}, action ) {
|
|
464
484
|
switch ( action.type ) {
|
|
465
485
|
case 'EDIT_ENTITY_RECORD':
|
|
@@ -750,6 +770,7 @@ export default combineReducers( {
|
|
|
750
770
|
themeGlobalStyleRevisions,
|
|
751
771
|
entities,
|
|
752
772
|
editsReference,
|
|
773
|
+
syncUndoManagerState,
|
|
753
774
|
undoManager,
|
|
754
775
|
embedPreviews,
|
|
755
776
|
userPermissions,
|
package/src/resolvers.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
RECEIVE_INTERMEDIATE_RESULTS,
|
|
26
26
|
isNumericID,
|
|
27
27
|
normalizeQueryForResolution,
|
|
28
|
+
saveCRDTDoc,
|
|
28
29
|
} from './utils';
|
|
29
30
|
import { fetchBlockPatterns } from './fetch';
|
|
30
31
|
import { restoreSelection, getSelectionHistory } from './utils/crdt-selection';
|
|
@@ -250,9 +251,15 @@ export const getEntityRecord =
|
|
|
250
251
|
// persistence. As we add support for syncing additional entity,
|
|
251
252
|
// we'll need to revisit where persisted CRDT documents are stored.
|
|
252
253
|
persistCRDTDoc: () => {
|
|
253
|
-
|
|
254
|
+
if (
|
|
255
|
+
! entityConfig.syncConfig?.supportsPersistence
|
|
256
|
+
) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return resolveSelect
|
|
254
261
|
.getEditedEntityRecord( kind, name, key )
|
|
255
|
-
.then( ( editedRecord ) => {
|
|
262
|
+
.then( async ( editedRecord ) => {
|
|
256
263
|
// Don't persist the CRDT document if the record is still an
|
|
257
264
|
// auto-draft or if the entity does not support meta.
|
|
258
265
|
const { meta, status } = editedRecord;
|
|
@@ -260,19 +267,14 @@ export const getEntityRecord =
|
|
|
260
267
|
return;
|
|
261
268
|
}
|
|
262
269
|
|
|
263
|
-
// Trigger a minimal save to persist the CRDT document. The
|
|
264
|
-
// entity's pre-persist hooks will create the persisted CRDT
|
|
265
|
-
// document and apply it to the record's meta.
|
|
266
270
|
const entityIdKey =
|
|
267
271
|
entityConfig.key || DEFAULT_ENTITY_KEY;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
},
|
|
275
|
-
{ __unstableSkipSyncUpdate: true }
|
|
272
|
+
const entityId =
|
|
273
|
+
editedRecord[ entityIdKey ];
|
|
274
|
+
|
|
275
|
+
await saveCRDTDoc(
|
|
276
|
+
`${ kind }/${ name }`,
|
|
277
|
+
entityId
|
|
276
278
|
);
|
|
277
279
|
} );
|
|
278
280
|
},
|
|
@@ -287,6 +289,11 @@ export const getEntityRecord =
|
|
|
287
289
|
);
|
|
288
290
|
}
|
|
289
291
|
},
|
|
292
|
+
onUndoStackChange: ( undoState ) => {
|
|
293
|
+
dispatch.__unstableNotifySyncUndoManagerChange(
|
|
294
|
+
undoState
|
|
295
|
+
);
|
|
296
|
+
},
|
|
290
297
|
restoreUndoMeta: ( ydoc, meta ) => {
|
|
291
298
|
const selectionHistory =
|
|
292
299
|
meta.get( 'selectionHistory' );
|
package/src/selectors.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
isNumericID,
|
|
25
25
|
getUserPermissionCacheKey,
|
|
26
26
|
} from './utils';
|
|
27
|
+
import { getSyncManager } from './sync';
|
|
27
28
|
import type * as ET from './entity-types';
|
|
28
29
|
import logEntityDeprecation from './utils/log-entity-deprecation';
|
|
29
30
|
|
|
@@ -44,6 +45,10 @@ export interface State {
|
|
|
44
45
|
themeGlobalStyleVariations: Record< string, string >;
|
|
45
46
|
themeGlobalStyleRevisions: Record< number, Object >;
|
|
46
47
|
undoManager: UndoManager;
|
|
48
|
+
syncUndoManagerState: {
|
|
49
|
+
hasRedo: boolean;
|
|
50
|
+
hasUndo: boolean;
|
|
51
|
+
};
|
|
47
52
|
userPermissions: Record< string, boolean >;
|
|
48
53
|
users: UserState;
|
|
49
54
|
navigationFallbackId: EntityRecordKey;
|
|
@@ -1148,6 +1153,9 @@ export function getRedoEdit( state: State ): Optional< any > {
|
|
|
1148
1153
|
* @return Whether there is a previous edit or not.
|
|
1149
1154
|
*/
|
|
1150
1155
|
export function hasUndo( state: State ): boolean {
|
|
1156
|
+
if ( getSyncManager()?.undoManager ) {
|
|
1157
|
+
return state.syncUndoManagerState.hasUndo;
|
|
1158
|
+
}
|
|
1151
1159
|
return getUndoManager( state ).hasUndo();
|
|
1152
1160
|
}
|
|
1153
1161
|
|
|
@@ -1160,6 +1168,9 @@ export function hasUndo( state: State ): boolean {
|
|
|
1160
1168
|
* @return Whether there is a next edit or not.
|
|
1161
1169
|
*/
|
|
1162
1170
|
export function hasRedo( state: State ): boolean {
|
|
1171
|
+
if ( getSyncManager()?.undoManager ) {
|
|
1172
|
+
return state.syncUndoManagerState.hasRedo;
|
|
1173
|
+
}
|
|
1163
1174
|
return getUndoManager( state ).hasRedo();
|
|
1164
1175
|
}
|
|
1165
1176
|
|
package/src/test/entities.js
CHANGED
|
@@ -136,15 +136,20 @@ describe( 'prePersistPostType', () => {
|
|
|
136
136
|
|
|
137
137
|
describe( 'loadPostTypeEntities', () => {
|
|
138
138
|
let originalCollaborationEnabled;
|
|
139
|
+
let originalCollaborationDisabledPostTypes;
|
|
139
140
|
|
|
140
141
|
beforeEach( () => {
|
|
141
142
|
apiFetch.mockReset();
|
|
142
143
|
applyPostChangesToCRDTDoc.mockReset();
|
|
143
144
|
originalCollaborationEnabled = window._wpCollaborationEnabled;
|
|
145
|
+
originalCollaborationDisabledPostTypes =
|
|
146
|
+
window._wpCollaborationDisabledPostTypes;
|
|
144
147
|
} );
|
|
145
148
|
|
|
146
149
|
afterEach( () => {
|
|
147
150
|
window._wpCollaborationEnabled = originalCollaborationEnabled;
|
|
151
|
+
window._wpCollaborationDisabledPostTypes =
|
|
152
|
+
originalCollaborationDisabledPostTypes;
|
|
148
153
|
} );
|
|
149
154
|
|
|
150
155
|
it( 'should include custom taxonomy rest_bases in synced properties when collaboration is enabled', async () => {
|
|
@@ -224,6 +229,52 @@ describe( 'loadPostTypeEntities', () => {
|
|
|
224
229
|
expect( syncedProperties ).not.toContain( 'tags' );
|
|
225
230
|
} );
|
|
226
231
|
|
|
232
|
+
it( 'should sync post type entities by default', async () => {
|
|
233
|
+
window._wpCollaborationEnabled = false;
|
|
234
|
+
window._wpCollaborationDisabledPostTypes = undefined;
|
|
235
|
+
|
|
236
|
+
const mockPostTypes = {
|
|
237
|
+
post: {
|
|
238
|
+
name: 'Posts',
|
|
239
|
+
rest_base: 'posts',
|
|
240
|
+
rest_namespace: 'wp/v2',
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
apiFetch.mockResolvedValueOnce( mockPostTypes );
|
|
245
|
+
|
|
246
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
247
|
+
( loader ) => loader.kind === 'postType'
|
|
248
|
+
);
|
|
249
|
+
const entities = await postTypeLoader.loadEntities();
|
|
250
|
+
const postEntity = entities.find( ( e ) => e.name === 'post' );
|
|
251
|
+
|
|
252
|
+
expect( postEntity.syncConfig.shouldSync() ).toBe( true );
|
|
253
|
+
} );
|
|
254
|
+
|
|
255
|
+
it( 'should not sync post type entities disabled for collaboration', async () => {
|
|
256
|
+
window._wpCollaborationEnabled = false;
|
|
257
|
+
window._wpCollaborationDisabledPostTypes = [ 'book' ];
|
|
258
|
+
|
|
259
|
+
const mockPostTypes = {
|
|
260
|
+
book: {
|
|
261
|
+
name: 'Books',
|
|
262
|
+
rest_base: 'books',
|
|
263
|
+
rest_namespace: 'wp/v2',
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
apiFetch.mockResolvedValueOnce( mockPostTypes );
|
|
268
|
+
|
|
269
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
270
|
+
( loader ) => loader.kind === 'postType'
|
|
271
|
+
);
|
|
272
|
+
const entities = await postTypeLoader.loadEntities();
|
|
273
|
+
const bookEntity = entities.find( ( e ) => e.name === 'book' );
|
|
274
|
+
|
|
275
|
+
expect( bookEntity.syncConfig.shouldSync() ).toBe( false );
|
|
276
|
+
} );
|
|
277
|
+
|
|
227
278
|
it( 'should skip taxonomy rest_base when taxonomy is not found in fetched taxonomies', async () => {
|
|
228
279
|
window._wpCollaborationEnabled = true;
|
|
229
280
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { getUndoManager } from '../private-selectors';
|
|
5
|
+
import { getSyncManager } from '../sync';
|
|
6
|
+
|
|
7
|
+
jest.mock( '../sync', () => ( {
|
|
8
|
+
getSyncManager: jest.fn(),
|
|
9
|
+
} ) );
|
|
10
|
+
|
|
11
|
+
describe( 'getUndoManager', () => {
|
|
12
|
+
afterEach( () => {
|
|
13
|
+
getSyncManager.mockReset();
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
it( 'returns the sync undo manager when one is available', () => {
|
|
17
|
+
const syncUndoManager = {
|
|
18
|
+
addRecord: jest.fn(),
|
|
19
|
+
hasRedo: jest.fn(),
|
|
20
|
+
hasUndo: jest.fn(),
|
|
21
|
+
redo: jest.fn(),
|
|
22
|
+
undo: jest.fn(),
|
|
23
|
+
};
|
|
24
|
+
const fallbackUndoManager = {
|
|
25
|
+
addRecord: jest.fn(),
|
|
26
|
+
hasRedo: jest.fn(),
|
|
27
|
+
hasUndo: jest.fn(),
|
|
28
|
+
redo: jest.fn(),
|
|
29
|
+
undo: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
getSyncManager.mockReturnValue( {
|
|
32
|
+
undoManager: syncUndoManager,
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
const state = {
|
|
36
|
+
undoManager: fallbackUndoManager,
|
|
37
|
+
syncUndoManagerState: {
|
|
38
|
+
hasRedo: false,
|
|
39
|
+
hasUndo: false,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect( getUndoManager( state ) ).toBe( syncUndoManager );
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
it( 'returns the default undo manager when there is no sync undo manager', () => {
|
|
47
|
+
const fallbackUndoManager = {
|
|
48
|
+
addRecord: jest.fn(),
|
|
49
|
+
hasRedo: jest.fn(),
|
|
50
|
+
hasUndo: jest.fn(),
|
|
51
|
+
redo: jest.fn(),
|
|
52
|
+
undo: jest.fn(),
|
|
53
|
+
};
|
|
54
|
+
getSyncManager.mockReturnValue( undefined );
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
getUndoManager( {
|
|
58
|
+
undoManager: fallbackUndoManager,
|
|
59
|
+
syncUndoManagerState: {
|
|
60
|
+
hasRedo: false,
|
|
61
|
+
hasUndo: false,
|
|
62
|
+
},
|
|
63
|
+
} )
|
|
64
|
+
).toBe( fallbackUndoManager );
|
|
65
|
+
} );
|
|
66
|
+
} );
|