@wordpress/core-data 7.41.0 → 7.41.2-next.v.202603161435.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 +6 -0
- package/README.md +19 -0
- package/build/actions.cjs +25 -31
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +34 -1
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/entities.cjs +3 -2
- package/build/entities.cjs.map +2 -2
- package/build/entity-provider.cjs +15 -6
- package/build/entity-provider.cjs.map +2 -2
- package/build/hooks/use-entity-prop.cjs +33 -2
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-post-editor-awareness-state.cjs +83 -2
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/index.cjs +3 -0
- package/build/index.cjs.map +2 -2
- package/build/private-actions.cjs +1 -1
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +3 -1
- package/build/private-apis.cjs.map +2 -2
- package/build/queried-data/actions.cjs +1 -1
- package/build/queried-data/actions.cjs.map +2 -2
- package/build/queried-data/reducer.cjs +19 -13
- package/build/queried-data/reducer.cjs.map +2 -2
- package/build/queried-data/selectors.cjs +7 -4
- package/build/queried-data/selectors.cjs.map +2 -2
- package/build/reducer.cjs +2 -1
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +114 -76
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +29 -0
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +3 -0
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs +16 -0
- package/build/types.cjs.map +3 -3
- package/build/utils/crdt-blocks.cjs +22 -26
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +8 -5
- package/build/utils/crdt-user-selections.cjs.map +2 -2
- package/build/utils/crdt.cjs +1 -3
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +3 -0
- package/build/utils/index.cjs.map +2 -2
- package/build/utils/normalize-query-for-resolution.cjs +35 -0
- package/build/utils/normalize-query-for-resolution.cjs.map +7 -0
- package/build/utils/user-permissions.cjs +1 -4
- package/build/utils/user-permissions.cjs.map +2 -2
- package/build-module/actions.mjs +30 -32
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +34 -1
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +3 -2
- package/build-module/entities.mjs.map +2 -2
- package/build-module/entity-provider.mjs +15 -6
- package/build-module/entity-provider.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +34 -3
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +80 -1
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/index.mjs +2 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-actions.mjs +1 -1
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +6 -2
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/queried-data/actions.mjs +1 -1
- package/build-module/queried-data/actions.mjs.map +2 -2
- package/build-module/queried-data/reducer.mjs +19 -13
- package/build-module/queried-data/reducer.mjs.map +2 -2
- package/build-module/queried-data/selectors.mjs +7 -4
- package/build-module/queried-data/selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +2 -1
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +116 -77
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +28 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +2 -0
- package/build-module/sync.mjs.map +2 -2
- package/build-module/types.mjs +9 -0
- package/build-module/types.mjs.map +4 -4
- package/build-module/utils/crdt-blocks.mjs +22 -26
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +8 -5
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +1 -3
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +2 -0
- package/build-module/utils/index.mjs.map +2 -2
- package/build-module/utils/normalize-query-for-resolution.mjs +14 -0
- package/build-module/utils/normalize-query-for-resolution.mjs.map +7 -0
- package/build-module/utils/user-permissions.mjs +1 -4
- package/build-module/utils/user-permissions.mjs.map +2 -2
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +1 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/entities.d.ts +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts +11 -6
- package/build-types/entity-provider.d.ts.map +1 -1
- package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +34 -10
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts +2 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/queried-data/reducer.d.ts.map +1 -1
- package/build-types/queried-data/selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +2 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +17 -0
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/sync.d.ts +2 -2
- package/build-types/sync.d.ts.map +1 -1
- package/build-types/types.d.ts +15 -0
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts +10 -5
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +1 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -0
- package/build-types/utils/user-permissions.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/actions.js +49 -50
- package/src/awareness/post-editor-awareness.ts +93 -1
- package/src/awareness/test/post-editor-awareness.ts +35 -0
- package/src/awareness/types.ts +1 -1
- package/src/entities.js +2 -1
- package/src/entity-provider.js +24 -11
- package/src/hooks/test/use-post-editor-awareness-state.ts +443 -0
- package/src/hooks/use-entity-prop.js +43 -3
- package/src/hooks/use-post-editor-awareness-state.ts +159 -7
- package/src/index.js +1 -0
- package/src/private-actions.js +1 -1
- package/src/private-apis.js +6 -2
- package/src/queried-data/actions.js +1 -1
- package/src/queried-data/reducer.js +26 -14
- package/src/queried-data/selectors.js +12 -5
- package/src/queried-data/test/selectors.js +25 -0
- package/src/reducer.js +4 -1
- package/src/resolvers.js +141 -91
- package/src/selectors.ts +56 -0
- package/src/sync.ts +2 -0
- package/src/test/private-actions.js +1 -1
- package/src/test/resolvers.js +88 -14
- package/src/test/selectors.js +150 -0
- package/src/test/store.js +182 -0
- package/src/types.ts +19 -0
- package/src/utils/crdt-blocks.ts +47 -54
- package/src/utils/crdt-user-selections.ts +28 -16
- package/src/utils/crdt.ts +2 -7
- package/src/utils/index.js +1 -0
- package/src/utils/normalize-query-for-resolution.js +23 -0
- package/src/utils/test/crdt-blocks.ts +42 -24
- package/src/utils/user-permissions.js +4 -5
package/src/resolvers.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ALLOWED_RESOURCE_ACTIONS,
|
|
25
25
|
RECEIVE_INTERMEDIATE_RESULTS,
|
|
26
26
|
isNumericID,
|
|
27
|
+
normalizeQueryForResolution,
|
|
27
28
|
} from './utils';
|
|
28
29
|
import { fetchBlockPatterns } from './fetch';
|
|
29
30
|
import { restoreSelection, getSelectionHistory } from './utils/crdt-selection';
|
|
@@ -229,18 +230,26 @@ export const getEntityRecord =
|
|
|
229
230
|
query
|
|
230
231
|
);
|
|
231
232
|
},
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
233
|
+
// Persist the CRDT document.
|
|
234
|
+
//
|
|
235
|
+
// TODO: Currently, persisted CRDT documents are stored in post meta.
|
|
236
|
+
// This effectively means that only post entities support CRDT
|
|
237
|
+
// persistence. As we add support for syncing additional entity,
|
|
238
|
+
// we'll need to revisit where persisted CRDT documents are stored.
|
|
239
|
+
persistCRDTDoc: () => {
|
|
235
240
|
resolveSelect
|
|
236
241
|
.getEditedEntityRecord( kind, name, key )
|
|
237
242
|
.then( ( editedRecord ) => {
|
|
238
|
-
// Don't
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
// Don't persist the CRDT document if the record is still an
|
|
244
|
+
// auto-draft or if the entity does not support meta.
|
|
245
|
+
const { meta, status } = editedRecord;
|
|
246
|
+
if ( 'auto-draft' === status || ! meta ) {
|
|
241
247
|
return;
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
// Trigger a save to persist the CRDT document. The entity's
|
|
251
|
+
// pre-persist hooks will create the persisted CRDT document
|
|
252
|
+
// and apply it to the record's meta.
|
|
244
253
|
dispatch.saveEntityRecord(
|
|
245
254
|
kind,
|
|
246
255
|
name,
|
|
@@ -350,18 +359,14 @@ export const getEntityRecords =
|
|
|
350
359
|
const key = entityConfig.key || DEFAULT_ENTITY_KEY;
|
|
351
360
|
|
|
352
361
|
function getResolutionsArgs( records, recordsQuery ) {
|
|
353
|
-
const
|
|
354
|
-
Object.entries( recordsQuery ).filter( ( [ k, v ] ) => {
|
|
355
|
-
return [ 'context', '_fields' ].includes( k ) && !! v;
|
|
356
|
-
} )
|
|
357
|
-
);
|
|
362
|
+
const normalizedQuery = normalizeQueryForResolution( recordsQuery );
|
|
358
363
|
return records
|
|
359
364
|
.filter( ( record ) => record?.[ key ] )
|
|
360
365
|
.map( ( record ) => [
|
|
361
366
|
kind,
|
|
362
367
|
name,
|
|
363
368
|
record[ key ],
|
|
364
|
-
|
|
369
|
+
normalizedQuery,
|
|
365
370
|
] );
|
|
366
371
|
}
|
|
367
372
|
|
|
@@ -991,9 +996,11 @@ export const getDefaultTemplateId =
|
|
|
991
996
|
template.id = id;
|
|
992
997
|
registry.batch( () => {
|
|
993
998
|
dispatch.receiveDefaultTemplateId( query, id );
|
|
994
|
-
dispatch.receiveEntityRecords(
|
|
995
|
-
|
|
996
|
-
|
|
999
|
+
dispatch.receiveEntityRecords(
|
|
1000
|
+
'postType',
|
|
1001
|
+
template.type,
|
|
1002
|
+
template
|
|
1003
|
+
);
|
|
997
1004
|
// Avoid further network requests.
|
|
998
1005
|
dispatch.finishResolution( 'getEntityRecord', [
|
|
999
1006
|
'postType',
|
|
@@ -1034,78 +1041,87 @@ export const getRevisions =
|
|
|
1034
1041
|
return;
|
|
1035
1042
|
}
|
|
1036
1043
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
...query,
|
|
1043
|
-
_fields: [
|
|
1044
|
-
...new Set( [
|
|
1045
|
-
...( getNormalizedCommaSeparable( query._fields ) ||
|
|
1046
|
-
[] ),
|
|
1047
|
-
entityConfig.revisionKey || DEFAULT_ENTITY_KEY,
|
|
1048
|
-
] ),
|
|
1049
|
-
].join(),
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
const path = addQueryArgs(
|
|
1054
|
-
entityConfig.getRevisionsUrl( recordKey ),
|
|
1055
|
-
query
|
|
1044
|
+
const rawQuery = { ...query };
|
|
1045
|
+
const lock = await dispatch.__unstableAcquireStoreLock(
|
|
1046
|
+
STORE_NAME,
|
|
1047
|
+
[ 'entities', 'records', kind, name, recordKey, 'revisions' ],
|
|
1048
|
+
{ exclusive: false }
|
|
1056
1049
|
);
|
|
1057
1050
|
|
|
1058
|
-
let records, response;
|
|
1059
|
-
const meta = {};
|
|
1060
|
-
const isPaginated =
|
|
1061
|
-
entityConfig.supportsPagination && query.per_page !== -1;
|
|
1062
1051
|
try {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1052
|
+
if ( query._fields ) {
|
|
1053
|
+
// If requesting specific fields, items and query association to said
|
|
1054
|
+
// records are stored by ID reference. Thus, fields must always include
|
|
1055
|
+
// the ID.
|
|
1056
|
+
query = {
|
|
1057
|
+
...query,
|
|
1058
|
+
_fields: [
|
|
1059
|
+
...new Set( [
|
|
1060
|
+
...( getNormalizedCommaSeparable( query._fields ) ||
|
|
1061
|
+
[] ),
|
|
1062
|
+
entityConfig.revisionKey || DEFAULT_ENTITY_KEY,
|
|
1063
|
+
] ),
|
|
1064
|
+
].join(),
|
|
1065
|
+
};
|
|
1077
1066
|
}
|
|
1078
1067
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
records = records.map( ( record ) => {
|
|
1084
|
-
query._fields.split( ',' ).forEach( ( field ) => {
|
|
1085
|
-
if ( ! record.hasOwnProperty( field ) ) {
|
|
1086
|
-
record[ field ] = undefined;
|
|
1087
|
-
}
|
|
1088
|
-
} );
|
|
1068
|
+
const path = addQueryArgs(
|
|
1069
|
+
entityConfig.getRevisionsUrl( recordKey ),
|
|
1070
|
+
query
|
|
1071
|
+
);
|
|
1089
1072
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1073
|
+
let records, response;
|
|
1074
|
+
const meta = {};
|
|
1075
|
+
const isPaginated =
|
|
1076
|
+
entityConfig.supportsPagination && query.per_page !== -1;
|
|
1077
|
+
try {
|
|
1078
|
+
response = await apiFetch( { path, parse: ! isPaginated } );
|
|
1079
|
+
} catch ( error ) {
|
|
1080
|
+
// Do nothing if our request comes back with an API error.
|
|
1081
|
+
return;
|
|
1092
1082
|
}
|
|
1093
1083
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
);
|
|
1084
|
+
if ( response ) {
|
|
1085
|
+
if ( isPaginated ) {
|
|
1086
|
+
records = Object.values( await response.json() );
|
|
1087
|
+
meta.totalItems = parseInt(
|
|
1088
|
+
response.headers.get( 'X-WP-Total' )
|
|
1089
|
+
);
|
|
1090
|
+
} else {
|
|
1091
|
+
records = Object.values( response );
|
|
1092
|
+
}
|
|
1104
1093
|
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
|
|
1094
|
+
// If we request fields but the result doesn't contain the fields,
|
|
1095
|
+
// explicitly set these fields as "undefined"
|
|
1096
|
+
// that way we consider the query "fulfilled".
|
|
1097
|
+
if ( query._fields ) {
|
|
1098
|
+
records = records.map( ( record ) => {
|
|
1099
|
+
query._fields.split( ',' ).forEach( ( field ) => {
|
|
1100
|
+
if ( ! record.hasOwnProperty( field ) ) {
|
|
1101
|
+
record[ field ] = undefined;
|
|
1102
|
+
}
|
|
1103
|
+
} );
|
|
1104
|
+
|
|
1105
|
+
return record;
|
|
1106
|
+
} );
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
registry.batch( () => {
|
|
1110
|
+
dispatch.receiveRevisions(
|
|
1111
|
+
kind,
|
|
1112
|
+
name,
|
|
1113
|
+
recordKey,
|
|
1114
|
+
records,
|
|
1115
|
+
query,
|
|
1116
|
+
false,
|
|
1117
|
+
meta
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
// Mark individual getRevision resolutions as done so that
|
|
1121
|
+
// subsequent getRevision calls skip redundant API fetches.
|
|
1108
1122
|
const key = entityConfig.revisionKey || DEFAULT_ENTITY_KEY;
|
|
1123
|
+
const normalizedQuery =
|
|
1124
|
+
normalizeQueryForResolution( rawQuery );
|
|
1109
1125
|
const resolutionsArgs = records
|
|
1110
1126
|
.filter( ( record ) => record[ key ] )
|
|
1111
1127
|
.map( ( record ) => [
|
|
@@ -1113,14 +1129,17 @@ export const getRevisions =
|
|
|
1113
1129
|
name,
|
|
1114
1130
|
recordKey,
|
|
1115
1131
|
record[ key ],
|
|
1132
|
+
normalizedQuery,
|
|
1116
1133
|
] );
|
|
1117
1134
|
|
|
1118
1135
|
dispatch.finishResolutions(
|
|
1119
1136
|
'getRevision',
|
|
1120
1137
|
resolutionsArgs
|
|
1121
1138
|
);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1139
|
+
} );
|
|
1140
|
+
}
|
|
1141
|
+
} finally {
|
|
1142
|
+
dispatch.__unstableReleaseStoreLock( lock );
|
|
1124
1143
|
}
|
|
1125
1144
|
};
|
|
1126
1145
|
|
|
@@ -1145,7 +1164,7 @@ getRevisions.shouldInvalidate = ( action, kind, name, recordKey ) =>
|
|
|
1145
1164
|
*/
|
|
1146
1165
|
export const getRevision =
|
|
1147
1166
|
( kind, name, recordKey, revisionKey, query ) =>
|
|
1148
|
-
async ( { dispatch, resolveSelect } ) => {
|
|
1167
|
+
async ( { select, dispatch, resolveSelect } ) => {
|
|
1149
1168
|
const configs = await resolveSelect.getEntitiesConfig( kind );
|
|
1150
1169
|
const entityConfig = configs.find(
|
|
1151
1170
|
( config ) => config.name === name && config.kind === kind
|
|
@@ -1170,21 +1189,52 @@ export const getRevision =
|
|
|
1170
1189
|
].join(),
|
|
1171
1190
|
};
|
|
1172
1191
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1192
|
+
|
|
1193
|
+
const lock = await dispatch.__unstableAcquireStoreLock(
|
|
1194
|
+
STORE_NAME,
|
|
1195
|
+
[
|
|
1196
|
+
'entities',
|
|
1197
|
+
'records',
|
|
1198
|
+
kind,
|
|
1199
|
+
name,
|
|
1200
|
+
recordKey,
|
|
1201
|
+
'revisions',
|
|
1202
|
+
revisionKey,
|
|
1203
|
+
],
|
|
1204
|
+
{ exclusive: false }
|
|
1176
1205
|
);
|
|
1177
1206
|
|
|
1178
|
-
let record;
|
|
1179
1207
|
try {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1208
|
+
if (
|
|
1209
|
+
select.hasRevision( kind, name, recordKey, revisionKey, query )
|
|
1210
|
+
) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1185
1213
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1214
|
+
const path = addQueryArgs(
|
|
1215
|
+
entityConfig.getRevisionsUrl( recordKey, revisionKey ),
|
|
1216
|
+
query
|
|
1217
|
+
);
|
|
1218
|
+
|
|
1219
|
+
let record;
|
|
1220
|
+
try {
|
|
1221
|
+
record = await apiFetch( { path } );
|
|
1222
|
+
} catch ( error ) {
|
|
1223
|
+
// Do nothing if our request comes back with an API error.
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if ( record ) {
|
|
1228
|
+
dispatch.receiveRevisions(
|
|
1229
|
+
kind,
|
|
1230
|
+
name,
|
|
1231
|
+
recordKey,
|
|
1232
|
+
record,
|
|
1233
|
+
query
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
} finally {
|
|
1237
|
+
dispatch.__unstableReleaseStoreLock( lock );
|
|
1188
1238
|
}
|
|
1189
1239
|
};
|
|
1190
1240
|
|
package/src/selectors.ts
CHANGED
|
@@ -1524,6 +1524,62 @@ export const getRevisions = (
|
|
|
1524
1524
|
return getQueriedItems( queriedStateRevisions, query );
|
|
1525
1525
|
};
|
|
1526
1526
|
|
|
1527
|
+
/**
|
|
1528
|
+
* Returns true if a revision has been received for the given set of parameters,
|
|
1529
|
+
* or false otherwise.
|
|
1530
|
+
*
|
|
1531
|
+
* Note: This does not trigger a request for the revision from the API
|
|
1532
|
+
* if it's not available in the local state.
|
|
1533
|
+
*
|
|
1534
|
+
* @param state State tree
|
|
1535
|
+
* @param kind Entity kind.
|
|
1536
|
+
* @param name Entity name.
|
|
1537
|
+
* @param recordKey The key of the entity record whose revision you want to check.
|
|
1538
|
+
* @param revisionKey The revision's key.
|
|
1539
|
+
* @param query Optional query.
|
|
1540
|
+
*
|
|
1541
|
+
* @return Whether a revision has been received.
|
|
1542
|
+
*/
|
|
1543
|
+
export function hasRevision(
|
|
1544
|
+
state: State,
|
|
1545
|
+
kind: string,
|
|
1546
|
+
name: string,
|
|
1547
|
+
recordKey: EntityRecordKey,
|
|
1548
|
+
revisionKey: EntityRecordKey,
|
|
1549
|
+
query?: GetRecordsHttpQuery
|
|
1550
|
+
): boolean {
|
|
1551
|
+
const queriedState =
|
|
1552
|
+
state.entities.records?.[ kind ]?.[ name ]?.revisions?.[ recordKey ];
|
|
1553
|
+
if ( ! queriedState ) {
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
const context = query?.context ?? 'default';
|
|
1557
|
+
|
|
1558
|
+
if ( ! query || ! query._fields ) {
|
|
1559
|
+
return !! queriedState.itemIsComplete[ context ]?.[ revisionKey ];
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const item = queriedState.items[ context ]?.[ revisionKey ];
|
|
1563
|
+
if ( ! item ) {
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const fields = getNormalizedCommaSeparable( query._fields ) ?? [];
|
|
1568
|
+
for ( let i = 0; i < fields.length; i++ ) {
|
|
1569
|
+
const path = fields[ i ].split( '.' );
|
|
1570
|
+
let value = item;
|
|
1571
|
+
for ( let p = 0; p < path.length; p++ ) {
|
|
1572
|
+
const part = path[ p ];
|
|
1573
|
+
if ( ! value || ! Object.hasOwn( value, part ) ) {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
value = value[ part ];
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
return true;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1527
1583
|
/**
|
|
1528
1584
|
* Returns a single, specific revision of a parent entity.
|
|
1529
1585
|
*
|
package/src/sync.ts
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
CRDT_DOC_META_PERSISTENCE_KEY,
|
|
18
18
|
CRDT_RECORD_MAP_KEY,
|
|
19
19
|
LOCAL_EDITOR_ORIGIN,
|
|
20
|
+
LOCAL_UNDO_IGNORED_ORIGIN,
|
|
20
21
|
retrySyncConnection,
|
|
21
22
|
} = unlock( syncPrivateApis );
|
|
22
23
|
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
CRDT_DOC_META_PERSISTENCE_KEY,
|
|
26
27
|
CRDT_RECORD_MAP_KEY,
|
|
27
28
|
LOCAL_EDITOR_ORIGIN,
|
|
29
|
+
LOCAL_UNDO_IGNORED_ORIGIN,
|
|
28
30
|
retrySyncConnection,
|
|
29
31
|
};
|
|
30
32
|
|
package/src/test/resolvers.js
CHANGED
|
@@ -172,16 +172,69 @@ describe( 'getEntityRecord', () => {
|
|
|
172
172
|
editRecord: expect.any( Function ),
|
|
173
173
|
getEditedRecord: expect.any( Function ),
|
|
174
174
|
onStatusChange: expect.any( Function ),
|
|
175
|
+
persistCRDTDoc: expect.any( Function ),
|
|
175
176
|
refetchRecord: expect.any( Function ),
|
|
176
177
|
restoreUndoMeta: expect.any( Function ),
|
|
177
|
-
saveRecord: expect.any( Function ),
|
|
178
178
|
}
|
|
179
179
|
);
|
|
180
180
|
} );
|
|
181
181
|
|
|
182
|
-
it( '
|
|
183
|
-
const
|
|
184
|
-
const EDITED_RECORD = { id: 1, title: 'Edited
|
|
182
|
+
it( 'persistCRDTDoc fetches edited record and does not save full entity record when the entity does not support meta', async () => {
|
|
183
|
+
const ENTITY_RECORD = { id: 1, title: 'Test Record' };
|
|
184
|
+
const EDITED_RECORD = { id: 1, title: 'Edited Record' };
|
|
185
|
+
const ENTITY_RESPONSE = {
|
|
186
|
+
json: () => Promise.resolve( ENTITY_RECORD ),
|
|
187
|
+
};
|
|
188
|
+
const ENTITIES_WITH_SYNC = [
|
|
189
|
+
{
|
|
190
|
+
name: 'bar',
|
|
191
|
+
kind: 'foo',
|
|
192
|
+
baseURL: '/wp/v2/foo',
|
|
193
|
+
baseURLParams: { context: 'edit' },
|
|
194
|
+
syncConfig: {},
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
dispatch.saveEntityRecord = jest.fn();
|
|
199
|
+
|
|
200
|
+
const resolveSelectWithSync = {
|
|
201
|
+
getEntitiesConfig: jest.fn( () => ENTITIES_WITH_SYNC ),
|
|
202
|
+
getEditedEntityRecord: jest.fn( () =>
|
|
203
|
+
Promise.resolve( EDITED_RECORD )
|
|
204
|
+
),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
triggerFetch.mockImplementation( () => ENTITY_RESPONSE );
|
|
208
|
+
|
|
209
|
+
await getEntityRecord(
|
|
210
|
+
'foo',
|
|
211
|
+
'bar',
|
|
212
|
+
1
|
|
213
|
+
)( {
|
|
214
|
+
dispatch,
|
|
215
|
+
registry,
|
|
216
|
+
resolveSelect: resolveSelectWithSync,
|
|
217
|
+
} );
|
|
218
|
+
|
|
219
|
+
// Extract the handlers passed to syncManager.load.
|
|
220
|
+
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
221
|
+
|
|
222
|
+
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
223
|
+
handlers.persistCRDTDoc();
|
|
224
|
+
await resolveSelectWithSync.getEditedEntityRecord();
|
|
225
|
+
|
|
226
|
+
// Should have fetched the full edited entity record.
|
|
227
|
+
expect(
|
|
228
|
+
resolveSelectWithSync.getEditedEntityRecord
|
|
229
|
+
).toHaveBeenCalledWith( 'foo', 'bar', 1 );
|
|
230
|
+
|
|
231
|
+
// Should not have called saveEntityRecord.
|
|
232
|
+
expect( dispatch.saveEntityRecord ).not.toHaveBeenCalled();
|
|
233
|
+
} );
|
|
234
|
+
|
|
235
|
+
it( 'persistCRDTDoc fetches edited record and saves full entity record', async () => {
|
|
236
|
+
const POST_RECORD = { id: 1, title: 'Test Post', meta: {} };
|
|
237
|
+
const EDITED_RECORD = { id: 1, title: 'Edited Post', meta: {} };
|
|
185
238
|
const POST_RESPONSE = {
|
|
186
239
|
json: () => Promise.resolve( POST_RECORD ),
|
|
187
240
|
};
|
|
@@ -219,8 +272,8 @@ describe( 'getEntityRecord', () => {
|
|
|
219
272
|
// Extract the handlers passed to syncManager.load.
|
|
220
273
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
221
274
|
|
|
222
|
-
// Call
|
|
223
|
-
handlers.
|
|
275
|
+
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
276
|
+
handlers.persistCRDTDoc();
|
|
224
277
|
await resolveSelectWithSync.getEditedEntityRecord();
|
|
225
278
|
|
|
226
279
|
// Should have fetched the full edited entity record.
|
|
@@ -236,8 +289,8 @@ describe( 'getEntityRecord', () => {
|
|
|
236
289
|
);
|
|
237
290
|
} );
|
|
238
291
|
|
|
239
|
-
it( '
|
|
240
|
-
const POST_RECORD = { id: 1, title: 'Test Post' };
|
|
292
|
+
it( 'persistCRDTDoc saves even when there are no unsaved edits', async () => {
|
|
293
|
+
const POST_RECORD = { id: 1, title: 'Test Post', meta: {} };
|
|
241
294
|
const POST_RESPONSE = {
|
|
242
295
|
json: () => Promise.resolve( POST_RECORD ),
|
|
243
296
|
};
|
|
@@ -275,8 +328,8 @@ describe( 'getEntityRecord', () => {
|
|
|
275
328
|
|
|
276
329
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
277
330
|
|
|
278
|
-
// Call
|
|
279
|
-
handlers.
|
|
331
|
+
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
332
|
+
handlers.persistCRDTDoc();
|
|
280
333
|
await resolveSelectWithSync.getEditedEntityRecord();
|
|
281
334
|
|
|
282
335
|
// Should save the record even with no edits (the whole point of the fix).
|
|
@@ -336,9 +389,9 @@ describe( 'getEntityRecord', () => {
|
|
|
336
389
|
editRecord: expect.any( Function ),
|
|
337
390
|
getEditedRecord: expect.any( Function ),
|
|
338
391
|
onStatusChange: expect.any( Function ),
|
|
392
|
+
persistCRDTDoc: expect.any( Function ),
|
|
339
393
|
refetchRecord: expect.any( Function ),
|
|
340
394
|
restoreUndoMeta: expect.any( Function ),
|
|
341
|
-
saveRecord: expect.any( Function ),
|
|
342
395
|
}
|
|
343
396
|
);
|
|
344
397
|
} );
|
|
@@ -862,6 +915,24 @@ describe( 'canUser', () => {
|
|
|
862
915
|
expect( dispatch.receiveUserPermissions ).not.toHaveBeenCalled();
|
|
863
916
|
} );
|
|
864
917
|
|
|
918
|
+
it( 'receives false when the allow header is missing', async () => {
|
|
919
|
+
triggerFetch.mockImplementation( () => ( {
|
|
920
|
+
headers: new Map(),
|
|
921
|
+
} ) );
|
|
922
|
+
|
|
923
|
+
await canUser(
|
|
924
|
+
'create',
|
|
925
|
+
'media'
|
|
926
|
+
)( { dispatch, registry, resolveSelect } );
|
|
927
|
+
|
|
928
|
+
expect( dispatch.receiveUserPermissions ).toHaveBeenCalledWith( {
|
|
929
|
+
'create/media': false,
|
|
930
|
+
'read/media': false,
|
|
931
|
+
'update/media': false,
|
|
932
|
+
'delete/media': false,
|
|
933
|
+
} );
|
|
934
|
+
} );
|
|
935
|
+
|
|
865
936
|
it( 'throws an error when an entity resource object is malformed', async () => {
|
|
866
937
|
await expect(
|
|
867
938
|
canUser( 'create', { name: 'wp_block' } )( {
|
|
@@ -888,9 +959,12 @@ describe( 'canUser', () => {
|
|
|
888
959
|
parse: false,
|
|
889
960
|
} );
|
|
890
961
|
|
|
891
|
-
expect( dispatch.receiveUserPermissions ).toHaveBeenCalledWith(
|
|
892
|
-
|
|
893
|
-
|
|
962
|
+
expect( dispatch.receiveUserPermissions ).toHaveBeenCalledWith( {
|
|
963
|
+
'create/media': false,
|
|
964
|
+
'read/media': true,
|
|
965
|
+
'update/media': false,
|
|
966
|
+
'delete/media': false,
|
|
967
|
+
} );
|
|
894
968
|
} );
|
|
895
969
|
|
|
896
970
|
it( 'receives false when the user is not allowed to perform an action on entities', async () => {
|