@wordpress/core-data 6.11.0 → 6.12.1
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 +2 -0
- package/README.md +5 -1
- package/build/actions.js +252 -276
- package/build/actions.js.map +1 -1
- package/build/batch/create-batch.js +8 -16
- package/build/batch/create-batch.js.map +1 -1
- package/build/batch/default-processor.js +1 -1
- package/build/batch/default-processor.js.map +1 -1
- package/build/entities.js +16 -25
- package/build/entities.js.map +1 -1
- package/build/entity-provider.js +74 -23
- package/build/entity-provider.js.map +1 -1
- package/build/fetch/__experimental-fetch-link-suggestions.js +2 -6
- package/build/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
- package/build/fetch/__experimental-fetch-url-data.js +1 -2
- package/build/fetch/__experimental-fetch-url-data.js.map +1 -1
- package/build/hooks/use-entity-record.js +11 -13
- package/build/hooks/use-entity-record.js.map +1 -1
- package/build/hooks/use-entity-records.js +4 -6
- package/build/hooks/use-entity-records.js.map +1 -1
- package/build/hooks/use-query-select.js +1 -6
- package/build/hooks/use-query-select.js.map +1 -1
- package/build/index.js +1 -7
- package/build/index.js.map +1 -1
- package/build/locks/actions.js +3 -4
- package/build/locks/actions.js.map +1 -1
- package/build/locks/reducer.js +1 -4
- package/build/locks/reducer.js.map +1 -1
- package/build/locks/selectors.js +3 -4
- package/build/locks/selectors.js.map +1 -1
- package/build/locks/utils.js +3 -5
- package/build/locks/utils.js.map +1 -1
- package/build/private-apis.js +19 -0
- package/build/private-apis.js.map +1 -0
- package/build/private-selectors.js +37 -0
- package/build/private-selectors.js.map +1 -0
- package/build/queried-data/actions.js +2 -5
- package/build/queried-data/actions.js.map +1 -1
- package/build/queried-data/reducer.js +17 -47
- package/build/queried-data/reducer.js.map +1 -1
- package/build/queried-data/selectors.js +4 -11
- package/build/queried-data/selectors.js.map +1 -1
- package/build/reducer.js +167 -194
- package/build/reducer.js.map +1 -1
- package/build/resolvers.js +175 -220
- package/build/resolvers.js.map +1 -1
- package/build/selectors.js +53 -61
- package/build/selectors.js.map +1 -1
- package/build/utils/forward-resolver.js +4 -11
- package/build/utils/forward-resolver.js.map +1 -1
- package/build/utils/on-sub-key.js +1 -3
- package/build/utils/on-sub-key.js.map +1 -1
- package/build-module/actions.js +251 -276
- package/build-module/actions.js.map +1 -1
- package/build-module/batch/create-batch.js +8 -16
- package/build-module/batch/create-batch.js.map +1 -1
- package/build-module/batch/default-processor.js +1 -1
- package/build-module/batch/default-processor.js.map +1 -1
- package/build-module/entities.js +16 -25
- package/build-module/entities.js.map +1 -1
- package/build-module/entity-provider.js +71 -22
- package/build-module/entity-provider.js.map +1 -1
- package/build-module/fetch/__experimental-fetch-link-suggestions.js +2 -6
- package/build-module/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
- package/build-module/fetch/__experimental-fetch-url-data.js +1 -2
- package/build-module/fetch/__experimental-fetch-url-data.js.map +1 -1
- package/build-module/hooks/use-entity-record.js +11 -13
- package/build-module/hooks/use-entity-record.js.map +1 -1
- package/build-module/hooks/use-entity-records.js +4 -6
- package/build-module/hooks/use-entity-records.js.map +1 -1
- package/build-module/hooks/use-query-select.js +1 -6
- package/build-module/hooks/use-query-select.js.map +1 -1
- package/build-module/index.js +1 -7
- package/build-module/index.js.map +1 -1
- package/build-module/locks/actions.js +3 -4
- package/build-module/locks/actions.js.map +1 -1
- package/build-module/locks/reducer.js +1 -4
- package/build-module/locks/reducer.js.map +1 -1
- package/build-module/locks/selectors.js +3 -4
- package/build-module/locks/selectors.js.map +1 -1
- package/build-module/locks/utils.js +3 -5
- package/build-module/locks/utils.js.map +1 -1
- package/build-module/private-apis.js +9 -0
- package/build-module/private-apis.js.map +1 -0
- package/build-module/private-selectors.js +28 -0
- package/build-module/private-selectors.js.map +1 -0
- package/build-module/queried-data/actions.js +2 -5
- package/build-module/queried-data/actions.js.map +1 -1
- package/build-module/queried-data/reducer.js +17 -47
- package/build-module/queried-data/reducer.js.map +1 -1
- package/build-module/queried-data/selectors.js +4 -11
- package/build-module/queried-data/selectors.js.map +1 -1
- package/build-module/reducer.js +168 -194
- package/build-module/reducer.js.map +1 -1
- package/build-module/resolvers.js +175 -220
- package/build-module/resolvers.js.map +1 -1
- package/build-module/selectors.js +55 -61
- package/build-module/selectors.js.map +1 -1
- package/build-module/utils/forward-resolver.js +4 -11
- package/build-module/utils/forward-resolver.js.map +1 -1
- package/build-module/utils/on-sub-key.js +1 -3
- package/build-module/utils/on-sub-key.js.map +1 -1
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts.map +1 -1
- package/build-types/entity-types/wp-template.d.ts +4 -0
- package/build-types/entity-types/wp-template.d.ts.map +1 -1
- package/build-types/hooks/use-entity-record.d.ts.map +1 -1
- package/build-types/hooks/use-entity-records.d.ts +1 -1
- package/build-types/private-apis.d.ts +3 -0
- package/build-types/private-apis.d.ts.map +1 -0
- package/build-types/private-selectors.d.ts +25 -0
- package/build-types/private-selectors.d.ts.map +1 -0
- package/build-types/reducer.d.ts +6 -2
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/selectors.d.ts +16 -4
- package/build-types/selectors.d.ts.map +1 -1
- package/package.json +14 -12
- package/src/actions.js +9 -8
- package/src/entity-provider.js +72 -5
- package/src/entity-types/wp-template.ts +4 -0
- package/src/hooks/use-entity-record.ts +4 -2
- package/src/hooks/use-entity-records.ts +1 -1
- package/src/index.js +0 -1
- package/src/private-apis.js +10 -0
- package/src/private-selectors.ts +30 -0
- package/src/reducer.js +130 -104
- package/src/selectors.ts +33 -13
- package/src/test/reducer.js +89 -54
- package/src/test/selectors.js +8 -8
- package/tsconfig.json +2 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/actions.js
CHANGED
|
@@ -18,6 +18,7 @@ import { receiveItems, removeItems, receiveQueriedItems } from './queried-data';
|
|
|
18
18
|
import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities';
|
|
19
19
|
import { createBatch } from './batch';
|
|
20
20
|
import { STORE_NAME } from './name';
|
|
21
|
+
import { getUndoEdits, getRedoEdits } from './private-selectors';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Returns an action object used in signalling that authors have been received.
|
|
@@ -406,14 +407,14 @@ export const editEntityRecord =
|
|
|
406
407
|
export const undo =
|
|
407
408
|
() =>
|
|
408
409
|
( { select, dispatch } ) => {
|
|
409
|
-
|
|
410
|
+
// Todo: we shouldn't have to pass "root" here.
|
|
411
|
+
const undoEdit = select( ( state ) => getUndoEdits( state.root ) );
|
|
410
412
|
if ( ! undoEdit ) {
|
|
411
413
|
return;
|
|
412
414
|
}
|
|
413
415
|
dispatch( {
|
|
414
|
-
type: '
|
|
415
|
-
|
|
416
|
-
meta: { isUndo: true },
|
|
416
|
+
type: 'UNDO',
|
|
417
|
+
stackedEdits: undoEdit,
|
|
417
418
|
} );
|
|
418
419
|
};
|
|
419
420
|
|
|
@@ -424,14 +425,14 @@ export const undo =
|
|
|
424
425
|
export const redo =
|
|
425
426
|
() =>
|
|
426
427
|
( { select, dispatch } ) => {
|
|
427
|
-
|
|
428
|
+
// Todo: we shouldn't have to pass "root" here.
|
|
429
|
+
const redoEdit = select( ( state ) => getRedoEdits( state.root ) );
|
|
428
430
|
if ( ! redoEdit ) {
|
|
429
431
|
return;
|
|
430
432
|
}
|
|
431
433
|
dispatch( {
|
|
432
|
-
type: '
|
|
433
|
-
|
|
434
|
-
meta: { isRedo: true },
|
|
434
|
+
type: 'REDO',
|
|
435
|
+
stackedEdits: redoEdit,
|
|
435
436
|
} );
|
|
436
437
|
};
|
|
437
438
|
|
package/src/entity-provider.js
CHANGED
|
@@ -7,18 +7,22 @@ import {
|
|
|
7
7
|
useCallback,
|
|
8
8
|
useEffect,
|
|
9
9
|
} from '@wordpress/element';
|
|
10
|
-
import { useSelect, useDispatch } from '@wordpress/data';
|
|
10
|
+
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
|
|
11
11
|
import { parse, __unstableSerializeAndClean } from '@wordpress/blocks';
|
|
12
|
+
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Internal dependencies
|
|
15
16
|
*/
|
|
16
17
|
import { STORE_NAME } from './name';
|
|
18
|
+
import { unlock } from './private-apis';
|
|
17
19
|
|
|
18
20
|
/** @typedef {import('@wordpress/blocks').WPBlock} WPBlock */
|
|
19
21
|
|
|
20
22
|
const EMPTY_ARRAY = [];
|
|
21
23
|
|
|
24
|
+
let oldFootnotes = {};
|
|
25
|
+
|
|
22
26
|
/**
|
|
23
27
|
* Internal dependencies
|
|
24
28
|
*/
|
|
@@ -150,6 +154,8 @@ export function useEntityProp( kind, name, prop, _id ) {
|
|
|
150
154
|
* @return {[WPBlock[], Function, Function]} The block array and setters.
|
|
151
155
|
*/
|
|
152
156
|
export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
|
|
157
|
+
const [ meta, updateMeta ] = useEntityProp( kind, name, 'meta', _id );
|
|
158
|
+
const registry = useRegistry();
|
|
153
159
|
const providerId = useEntityId( kind, name );
|
|
154
160
|
const id = _id ?? providerId;
|
|
155
161
|
const { content, blocks } = useSelect(
|
|
@@ -184,6 +190,61 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
|
|
|
184
190
|
}
|
|
185
191
|
}, [ content ] );
|
|
186
192
|
|
|
193
|
+
const updateFootnotes = useCallback(
|
|
194
|
+
( _blocks ) => {
|
|
195
|
+
if ( ! meta ) return;
|
|
196
|
+
// If meta.footnotes is empty, it means the meta is not registered.
|
|
197
|
+
if ( meta.footnotes === undefined ) return;
|
|
198
|
+
|
|
199
|
+
const { getRichTextValues } = unlock( blockEditorPrivateApis );
|
|
200
|
+
const _content = getRichTextValues( _blocks ).join( '' ) || '';
|
|
201
|
+
const newOrder = [];
|
|
202
|
+
|
|
203
|
+
// This can be avoided when
|
|
204
|
+
// https://github.com/WordPress/gutenberg/pull/43204 lands. We can then
|
|
205
|
+
// get the order directly from the rich text values.
|
|
206
|
+
if ( _content.indexOf( 'data-fn' ) !== -1 ) {
|
|
207
|
+
const regex = /data-fn="([^"]+)"/g;
|
|
208
|
+
let match;
|
|
209
|
+
while ( ( match = regex.exec( _content ) ) !== null ) {
|
|
210
|
+
newOrder.push( match[ 1 ] );
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const footnotes = meta.footnotes
|
|
215
|
+
? JSON.parse( meta.footnotes )
|
|
216
|
+
: [];
|
|
217
|
+
const currentOrder = footnotes.map( ( fn ) => fn.id );
|
|
218
|
+
|
|
219
|
+
if ( currentOrder.join( '' ) === newOrder.join( '' ) ) return;
|
|
220
|
+
|
|
221
|
+
const newFootnotes = newOrder.map(
|
|
222
|
+
( fnId ) =>
|
|
223
|
+
footnotes.find( ( fn ) => fn.id === fnId ) ||
|
|
224
|
+
oldFootnotes[ fnId ] || {
|
|
225
|
+
id: fnId,
|
|
226
|
+
content: '',
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
oldFootnotes = {
|
|
231
|
+
...oldFootnotes,
|
|
232
|
+
...footnotes.reduce( ( acc, fn ) => {
|
|
233
|
+
if ( ! newOrder.includes( fn.id ) ) {
|
|
234
|
+
acc[ fn.id ] = fn;
|
|
235
|
+
}
|
|
236
|
+
return acc;
|
|
237
|
+
}, {} ),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
updateMeta( {
|
|
241
|
+
...meta,
|
|
242
|
+
footnotes: JSON.stringify( newFootnotes ),
|
|
243
|
+
} );
|
|
244
|
+
},
|
|
245
|
+
[ meta, updateMeta ]
|
|
246
|
+
);
|
|
247
|
+
|
|
187
248
|
const onChange = useCallback(
|
|
188
249
|
( newBlocks, options ) => {
|
|
189
250
|
const { selection } = options;
|
|
@@ -200,18 +261,24 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
|
|
|
200
261
|
edits.content = ( { blocks: blocksForSerialization = [] } ) =>
|
|
201
262
|
__unstableSerializeAndClean( blocksForSerialization );
|
|
202
263
|
|
|
203
|
-
|
|
264
|
+
registry.batch( () => {
|
|
265
|
+
updateFootnotes( edits.blocks );
|
|
266
|
+
editEntityRecord( kind, name, id, edits );
|
|
267
|
+
} );
|
|
204
268
|
},
|
|
205
|
-
[ kind, name, id, blocks ]
|
|
269
|
+
[ kind, name, id, blocks, updateFootnotes ]
|
|
206
270
|
);
|
|
207
271
|
|
|
208
272
|
const onInput = useCallback(
|
|
209
273
|
( newBlocks, options ) => {
|
|
210
274
|
const { selection } = options;
|
|
211
275
|
const edits = { blocks: newBlocks, selection };
|
|
212
|
-
|
|
276
|
+
registry.batch( () => {
|
|
277
|
+
updateFootnotes( edits.blocks );
|
|
278
|
+
editEntityRecord( kind, name, id, edits );
|
|
279
|
+
} );
|
|
213
280
|
},
|
|
214
|
-
[ kind, name, id ]
|
|
281
|
+
[ kind, name, id, updateFootnotes ]
|
|
215
282
|
);
|
|
216
283
|
|
|
217
284
|
return [ blocks ?? EMPTY_ARRAY, onInput, onChange ];
|
|
@@ -85,6 +85,10 @@ declare module './base-entity-records' {
|
|
|
85
85
|
* Whether a template is a custom template.
|
|
86
86
|
*/
|
|
87
87
|
is_custom: Record< string, string >;
|
|
88
|
+
/**
|
|
89
|
+
* The date the template was last modified, in the site's timezone.
|
|
90
|
+
*/
|
|
91
|
+
modified: ContextualField< string, 'view' | 'edit', C >;
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -160,7 +160,7 @@ export default function useEntityRecord< RecordType >(
|
|
|
160
160
|
...saveOptions,
|
|
161
161
|
} ),
|
|
162
162
|
} ),
|
|
163
|
-
[ recordId ]
|
|
163
|
+
[ editEntityRecord, kind, name, recordId, saveEditedEntityRecord ]
|
|
164
164
|
);
|
|
165
165
|
|
|
166
166
|
const { editedRecord, hasEdits } = useSelect(
|
|
@@ -182,7 +182,9 @@ export default function useEntityRecord< RecordType >(
|
|
|
182
182
|
const { data: record, ...querySelectRest } = useQuerySelect(
|
|
183
183
|
( query ) => {
|
|
184
184
|
if ( ! options.enabled ) {
|
|
185
|
-
return
|
|
185
|
+
return {
|
|
186
|
+
data: null,
|
|
187
|
+
};
|
|
186
188
|
}
|
|
187
189
|
return query( coreStore ).getEntityRecord( kind, name, recordId );
|
|
188
190
|
},
|
|
@@ -43,7 +43,7 @@ const EMPTY_ARRAY = [];
|
|
|
43
43
|
* @param options Optional hook options.
|
|
44
44
|
* @example
|
|
45
45
|
* ```js
|
|
46
|
-
* import {
|
|
46
|
+
* import { useEntityRecords } from '@wordpress/core-data';
|
|
47
47
|
*
|
|
48
48
|
* function PageTitlesList() {
|
|
49
49
|
* const { records, isResolving } = useEntityRecords( 'postType', 'page' );
|
package/src/index.js
CHANGED
|
@@ -62,7 +62,6 @@ const storeConfig = () => ( {
|
|
|
62
62
|
* @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore
|
|
63
63
|
*/
|
|
64
64
|
export const store = createReduxStore( STORE_NAME, storeConfig() );
|
|
65
|
-
|
|
66
65
|
register( store );
|
|
67
66
|
|
|
68
67
|
export { default as EntityProvider } from './entity-provider';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
|
|
5
|
+
|
|
6
|
+
export const { lock, unlock } =
|
|
7
|
+
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
|
|
8
|
+
'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.',
|
|
9
|
+
'@wordpress/core-data'
|
|
10
|
+
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { State, UndoEdit } from './selectors';
|
|
5
|
+
|
|
6
|
+
type Optional< T > = T | undefined;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the previous edit from the current undo offset
|
|
10
|
+
* for the entity records edits history, if any.
|
|
11
|
+
*
|
|
12
|
+
* @param state State tree.
|
|
13
|
+
*
|
|
14
|
+
* @return The edit.
|
|
15
|
+
*/
|
|
16
|
+
export function getUndoEdits( state: State ): Optional< UndoEdit[] > {
|
|
17
|
+
return state.undo.list[ state.undo.list.length - 1 + state.undo.offset ];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the next edit from the current undo offset
|
|
22
|
+
* for the entity records edits history, if any.
|
|
23
|
+
*
|
|
24
|
+
* @param state State tree.
|
|
25
|
+
*
|
|
26
|
+
* @return The edit.
|
|
27
|
+
*/
|
|
28
|
+
export function getRedoEdits( state: State ): Optional< UndoEdit[] > {
|
|
29
|
+
return state.undo.list[ state.undo.list.length + state.undo.offset ];
|
|
30
|
+
}
|
package/src/reducer.js
CHANGED
|
@@ -183,6 +183,30 @@ export function themeGlobalStyleVariations( state = {}, action ) {
|
|
|
183
183
|
return state;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
const withMultiEntityRecordEdits = ( reducer ) => ( state, action ) => {
|
|
187
|
+
if ( action.type === 'UNDO' || action.type === 'REDO' ) {
|
|
188
|
+
const { stackedEdits } = action;
|
|
189
|
+
|
|
190
|
+
let newState = state;
|
|
191
|
+
stackedEdits.forEach(
|
|
192
|
+
( { kind, name, recordId, property, from, to } ) => {
|
|
193
|
+
newState = reducer( newState, {
|
|
194
|
+
type: 'EDIT_ENTITY_RECORD',
|
|
195
|
+
kind,
|
|
196
|
+
name,
|
|
197
|
+
recordId,
|
|
198
|
+
edits: {
|
|
199
|
+
[ property ]: action.type === 'UNDO' ? from : to,
|
|
200
|
+
},
|
|
201
|
+
} );
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
return newState;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return reducer( state, action );
|
|
208
|
+
};
|
|
209
|
+
|
|
186
210
|
/**
|
|
187
211
|
* Higher Order Reducer for a given entity config. It supports:
|
|
188
212
|
*
|
|
@@ -196,6 +220,8 @@ export function themeGlobalStyleVariations( state = {}, action ) {
|
|
|
196
220
|
*/
|
|
197
221
|
function entity( entityConfig ) {
|
|
198
222
|
return compose( [
|
|
223
|
+
withMultiEntityRecordEdits,
|
|
224
|
+
|
|
199
225
|
// Limit to matching action type so we don't attempt to replace action on
|
|
200
226
|
// an unhandled action.
|
|
201
227
|
ifMatchingAction(
|
|
@@ -411,8 +437,9 @@ export const entities = ( state = {}, action ) => {
|
|
|
411
437
|
/**
|
|
412
438
|
* @typedef {Object} UndoStateMeta
|
|
413
439
|
*
|
|
414
|
-
* @property {number}
|
|
415
|
-
* @property {
|
|
440
|
+
* @property {number} list The undo stack.
|
|
441
|
+
* @property {number} offset Where in the undo stack we are.
|
|
442
|
+
* @property {Object} cache Cache of unpersisted transient edits.
|
|
416
443
|
*/
|
|
417
444
|
|
|
418
445
|
/** @typedef {Array<Object> & UndoStateMeta} UndoState */
|
|
@@ -422,10 +449,7 @@ export const entities = ( state = {}, action ) => {
|
|
|
422
449
|
*
|
|
423
450
|
* @todo Given how we use this we might want to make a custom class for it.
|
|
424
451
|
*/
|
|
425
|
-
const UNDO_INITIAL_STATE =
|
|
426
|
-
|
|
427
|
-
/** @type {Object} */
|
|
428
|
-
let lastEditAction;
|
|
452
|
+
const UNDO_INITIAL_STATE = { list: [], offset: 0 };
|
|
429
453
|
|
|
430
454
|
/**
|
|
431
455
|
* Reducer keeping track of entity edit undo history.
|
|
@@ -436,107 +460,114 @@ let lastEditAction;
|
|
|
436
460
|
* @return {UndoState} Updated state.
|
|
437
461
|
*/
|
|
438
462
|
export function undo( state = UNDO_INITIAL_STATE, action ) {
|
|
463
|
+
const omitPendingRedos = ( currentState ) => {
|
|
464
|
+
return {
|
|
465
|
+
...currentState,
|
|
466
|
+
list: currentState.list.slice(
|
|
467
|
+
0,
|
|
468
|
+
currentState.offset || undefined
|
|
469
|
+
),
|
|
470
|
+
offset: 0,
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const appendCachedEditsToLastUndo = ( currentState ) => {
|
|
475
|
+
if ( ! currentState.cache ) {
|
|
476
|
+
return currentState;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
let nextState = {
|
|
480
|
+
...currentState,
|
|
481
|
+
list: [ ...currentState.list ],
|
|
482
|
+
};
|
|
483
|
+
nextState = omitPendingRedos( nextState );
|
|
484
|
+
const previousUndoState = nextState.list.pop();
|
|
485
|
+
const updatedUndoState = currentState.cache.reduce(
|
|
486
|
+
appendEditToStack,
|
|
487
|
+
previousUndoState
|
|
488
|
+
);
|
|
489
|
+
nextState.list.push( updatedUndoState );
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
...nextState,
|
|
493
|
+
cache: undefined,
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const appendEditToStack = (
|
|
498
|
+
stack = [],
|
|
499
|
+
{ kind, name, recordId, property, from, to }
|
|
500
|
+
) => {
|
|
501
|
+
const existingEditIndex = stack?.findIndex(
|
|
502
|
+
( { kind: k, name: n, recordId: r, property: p } ) => {
|
|
503
|
+
return (
|
|
504
|
+
k === kind && n === name && r === recordId && p === property
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
);
|
|
508
|
+
const nextStack = [ ...stack ];
|
|
509
|
+
if ( existingEditIndex !== -1 ) {
|
|
510
|
+
// If the edit is already in the stack leave the initial "from" value.
|
|
511
|
+
nextStack[ existingEditIndex ] = {
|
|
512
|
+
...nextStack[ existingEditIndex ],
|
|
513
|
+
to,
|
|
514
|
+
};
|
|
515
|
+
} else {
|
|
516
|
+
nextStack.push( {
|
|
517
|
+
kind,
|
|
518
|
+
name,
|
|
519
|
+
recordId,
|
|
520
|
+
property,
|
|
521
|
+
from,
|
|
522
|
+
to,
|
|
523
|
+
} );
|
|
524
|
+
}
|
|
525
|
+
return nextStack;
|
|
526
|
+
};
|
|
527
|
+
|
|
439
528
|
switch ( action.type ) {
|
|
440
|
-
case 'EDIT_ENTITY_RECORD':
|
|
441
529
|
case 'CREATE_UNDO_LEVEL':
|
|
442
|
-
|
|
443
|
-
const isUndoOrRedo =
|
|
444
|
-
! isCreateUndoLevel &&
|
|
445
|
-
( action.meta.isUndo || action.meta.isRedo );
|
|
446
|
-
if ( isCreateUndoLevel ) {
|
|
447
|
-
action = lastEditAction;
|
|
448
|
-
} else if ( ! isUndoOrRedo ) {
|
|
449
|
-
// Don't lose the last edit cache if the new one only has transient edits.
|
|
450
|
-
// Transient edits don't create new levels so updating the cache would make
|
|
451
|
-
// us skip an edit later when creating levels explicitly.
|
|
452
|
-
if (
|
|
453
|
-
Object.keys( action.edits ).some(
|
|
454
|
-
( key ) => ! action.transientEdits[ key ]
|
|
455
|
-
)
|
|
456
|
-
) {
|
|
457
|
-
lastEditAction = action;
|
|
458
|
-
} else {
|
|
459
|
-
lastEditAction = {
|
|
460
|
-
...action,
|
|
461
|
-
edits: {
|
|
462
|
-
...( lastEditAction && lastEditAction.edits ),
|
|
463
|
-
...action.edits,
|
|
464
|
-
},
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
}
|
|
530
|
+
return appendCachedEditsToLastUndo( state );
|
|
468
531
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if ( state.flattenedUndo ) {
|
|
479
|
-
// The first undo in a sequence of undos might happen while we have
|
|
480
|
-
// flattened undos in state. If this is the case, we want execution
|
|
481
|
-
// to continue as if we were creating an explicit undo level. This
|
|
482
|
-
// will result in an extra undo level being appended with the flattened
|
|
483
|
-
// undo values.
|
|
484
|
-
// We also have to take into account if the `lastEditAction` had opted out
|
|
485
|
-
// of being tracked in undo history, like the action that persists the latest
|
|
486
|
-
// content right before saving. In that case we have to update the `lastEditAction`
|
|
487
|
-
// to avoid returning early before applying the existing flattened undos.
|
|
488
|
-
isCreateUndoLevel = true;
|
|
489
|
-
if ( ! lastEditAction.meta.undo ) {
|
|
490
|
-
lastEditAction.meta.undo = {
|
|
491
|
-
edits: {},
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
action = lastEditAction;
|
|
495
|
-
} else {
|
|
496
|
-
return nextState;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
532
|
+
case 'UNDO':
|
|
533
|
+
case 'REDO': {
|
|
534
|
+
const nextState = appendCachedEditsToLastUndo( state );
|
|
535
|
+
return {
|
|
536
|
+
...nextState,
|
|
537
|
+
offset: state.offset + ( action.type === 'UNDO' ? -1 : 1 ),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
499
540
|
|
|
541
|
+
case 'EDIT_ENTITY_RECORD': {
|
|
500
542
|
if ( ! action.meta.undo ) {
|
|
501
543
|
return state;
|
|
502
544
|
}
|
|
503
545
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
...state.flattenedUndo,
|
|
517
|
-
...action.edits,
|
|
546
|
+
const isCachedChange = Object.keys( action.edits ).every(
|
|
547
|
+
( key ) => action.transientEdits[ key ]
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const edits = Object.keys( action.edits ).map( ( key ) => {
|
|
551
|
+
return {
|
|
552
|
+
kind: action.kind,
|
|
553
|
+
name: action.name,
|
|
554
|
+
recordId: action.recordId,
|
|
555
|
+
property: key,
|
|
556
|
+
from: action.meta.undo.edits[ key ],
|
|
557
|
+
to: action.edits[ key ],
|
|
518
558
|
};
|
|
519
|
-
|
|
520
|
-
return nextState;
|
|
521
|
-
}
|
|
559
|
+
} );
|
|
522
560
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
nextState.pop();
|
|
529
|
-
if ( ! isCreateUndoLevel ) {
|
|
530
|
-
nextState.push( {
|
|
531
|
-
kind: action.meta.undo.kind,
|
|
532
|
-
name: action.meta.undo.name,
|
|
533
|
-
recordId: action.meta.undo.recordId,
|
|
534
|
-
edits: {
|
|
535
|
-
...state.flattenedUndo,
|
|
536
|
-
...action.meta.undo.edits,
|
|
537
|
-
},
|
|
538
|
-
} );
|
|
561
|
+
if ( isCachedChange ) {
|
|
562
|
+
return {
|
|
563
|
+
...state,
|
|
564
|
+
cache: edits.reduce( appendEditToStack, state.cache ),
|
|
565
|
+
};
|
|
539
566
|
}
|
|
567
|
+
|
|
568
|
+
let nextState = omitPendingRedos( state );
|
|
569
|
+
nextState = appendCachedEditsToLastUndo( nextState );
|
|
570
|
+
nextState = { ...nextState, list: [ ...nextState.list ] };
|
|
540
571
|
// When an edit is a function it's an optimization to avoid running some expensive operation.
|
|
541
572
|
// We can't rely on the function references being the same so we opt out of comparing them here.
|
|
542
573
|
const comparisonUndoEdits = Object.values(
|
|
@@ -546,16 +577,11 @@ export function undo( state = UNDO_INITIAL_STATE, action ) {
|
|
|
546
577
|
( edit ) => typeof edit !== 'function'
|
|
547
578
|
);
|
|
548
579
|
if ( ! isShallowEqual( comparisonUndoEdits, comparisonEdits ) ) {
|
|
549
|
-
nextState.push(
|
|
550
|
-
kind: action.kind,
|
|
551
|
-
name: action.name,
|
|
552
|
-
recordId: action.recordId,
|
|
553
|
-
edits: isCreateUndoLevel
|
|
554
|
-
? { ...state.flattenedUndo, ...action.edits }
|
|
555
|
-
: action.edits,
|
|
556
|
-
} );
|
|
580
|
+
nextState.list.push( edits );
|
|
557
581
|
}
|
|
582
|
+
|
|
558
583
|
return nextState;
|
|
584
|
+
}
|
|
559
585
|
}
|
|
560
586
|
|
|
561
587
|
return state;
|
package/src/selectors.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
setNestedValue,
|
|
23
23
|
} from './utils';
|
|
24
24
|
import type * as ET from './entity-types';
|
|
25
|
+
import { getUndoEdits, getRedoEdits } from './private-selectors';
|
|
25
26
|
|
|
26
27
|
// This is an incomplete, high-level approximation of the State type.
|
|
27
28
|
// It makes the selectors slightly more safe, but is intended to evolve
|
|
@@ -73,9 +74,18 @@ interface EntityConfig {
|
|
|
73
74
|
kind: string;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
interface
|
|
77
|
-
|
|
77
|
+
export interface UndoEdit {
|
|
78
|
+
name: string;
|
|
79
|
+
kind: string;
|
|
80
|
+
recordId: string;
|
|
81
|
+
from: any;
|
|
82
|
+
to: any;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface UndoState {
|
|
86
|
+
list: Array< UndoEdit[] >;
|
|
78
87
|
offset: number;
|
|
88
|
+
cache: UndoEdit[];
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
interface UserState {
|
|
@@ -884,24 +894,38 @@ function getCurrentUndoOffset( state: State ): number {
|
|
|
884
894
|
* Returns the previous edit from the current undo offset
|
|
885
895
|
* for the entity records edits history, if any.
|
|
886
896
|
*
|
|
887
|
-
* @
|
|
897
|
+
* @deprecated since 6.3
|
|
898
|
+
*
|
|
899
|
+
* @param state State tree.
|
|
888
900
|
*
|
|
889
901
|
* @return The edit.
|
|
890
902
|
*/
|
|
891
903
|
export function getUndoEdit( state: State ): Optional< any > {
|
|
892
|
-
|
|
904
|
+
deprecated( "select( 'core' ).getUndoEdit()", {
|
|
905
|
+
since: '6.3',
|
|
906
|
+
} );
|
|
907
|
+
return state.undo.list[
|
|
908
|
+
state.undo.list.length - 2 + getCurrentUndoOffset( state )
|
|
909
|
+
]?.[ 0 ];
|
|
893
910
|
}
|
|
894
911
|
|
|
895
912
|
/**
|
|
896
913
|
* Returns the next edit from the current undo offset
|
|
897
914
|
* for the entity records edits history, if any.
|
|
898
915
|
*
|
|
899
|
-
* @
|
|
916
|
+
* @deprecated since 6.3
|
|
917
|
+
*
|
|
918
|
+
* @param state State tree.
|
|
900
919
|
*
|
|
901
920
|
* @return The edit.
|
|
902
921
|
*/
|
|
903
922
|
export function getRedoEdit( state: State ): Optional< any > {
|
|
904
|
-
|
|
923
|
+
deprecated( "select( 'core' ).getRedoEdit()", {
|
|
924
|
+
since: '6.3',
|
|
925
|
+
} );
|
|
926
|
+
return state.undo.list[
|
|
927
|
+
state.undo.list.length + getCurrentUndoOffset( state )
|
|
928
|
+
]?.[ 0 ];
|
|
905
929
|
}
|
|
906
930
|
|
|
907
931
|
/**
|
|
@@ -913,7 +937,7 @@ export function getRedoEdit( state: State ): Optional< any > {
|
|
|
913
937
|
* @return Whether there is a previous edit or not.
|
|
914
938
|
*/
|
|
915
939
|
export function hasUndo( state: State ): boolean {
|
|
916
|
-
return Boolean(
|
|
940
|
+
return Boolean( getUndoEdits( state ) );
|
|
917
941
|
}
|
|
918
942
|
|
|
919
943
|
/**
|
|
@@ -925,7 +949,7 @@ export function hasUndo( state: State ): boolean {
|
|
|
925
949
|
* @return Whether there is a next edit or not.
|
|
926
950
|
*/
|
|
927
951
|
export function hasRedo( state: State ): boolean {
|
|
928
|
-
return Boolean(
|
|
952
|
+
return Boolean( getRedoEdits( state ) );
|
|
929
953
|
}
|
|
930
954
|
|
|
931
955
|
/**
|
|
@@ -1142,11 +1166,7 @@ export const hasFetchedAutosaves = createRegistrySelector(
|
|
|
1142
1166
|
export const getReferenceByDistinctEdits = createSelector(
|
|
1143
1167
|
// This unused state argument is listed here for the documentation generating tool (docgen).
|
|
1144
1168
|
( state: State ) => [],
|
|
1145
|
-
( state: State ) => [
|
|
1146
|
-
state.undo.length,
|
|
1147
|
-
state.undo.offset,
|
|
1148
|
-
state.undo.flattenedUndo,
|
|
1149
|
-
]
|
|
1169
|
+
( state: State ) => [ state.undo.list.length, state.undo.offset ]
|
|
1150
1170
|
);
|
|
1151
1171
|
|
|
1152
1172
|
/**
|