diff --git a/package-lock.json b/package-lock.json index 6997ac51ebbd93..e22853f36d83ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4885,6 +4885,7 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index bc485ead098ed2..3da791a43f81c7 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -26,6 +26,7 @@ "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 48c3a6da889e84..68c3ff7e51f135 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -7,6 +7,7 @@ import { keyBy, map, groupBy, flowRight, isEqual, get } from 'lodash'; * WordPress dependencies */ import { combineReducers } from '@wordpress/data'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -322,32 +323,26 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { return nextState; } - let nextState; - if ( state.length === 0 ) { - // Create an initial entry so that we can undo to it. - nextState = [ - { - kind: action.meta.undo.kind, - name: action.meta.undo.name, - recordId: action.meta.undo.recordId, - edits: { ...state.flattenedUndo, ...action.meta.undo.edits }, - }, - ]; - nextState.offset = 0; - return nextState; - } - // Clear potential redos, because this only supports linear history. - nextState = state.slice( 0, state.offset || undefined ); + const nextState = state.slice( 0, state.offset || undefined ); nextState.offset = 0; - + nextState.pop(); nextState.push( { - kind: action.kind, - name: action.name, - recordId: action.recordId, - edits: { ...action.edits, ...state.flattenedUndo }, + kind: action.meta.undo.kind, + name: action.meta.undo.name, + recordId: action.meta.undo.recordId, + edits: { ...state.flattenedUndo, ...action.meta.undo.edits }, } ); - + const comparisonUndoEdits = Object.values( action.meta.undo.edits ).filter( ( edit ) => typeof edit !== 'function' ); + const comparisonEdits = Object.values( action.edits ).filter( ( edit ) => typeof edit !== 'function' ); + if ( ! isShallowEqual( comparisonUndoEdits, comparisonEdits ) ) { + nextState.push( { + kind: action.kind, + name: action.name, + recordId: action.recordId, + edits: action.edits, + } ); + } return nextState; } diff --git a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap index 799e5c2c0879c5..6c0308f402a826 100644 --- a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap @@ -14,6 +14,12 @@ exports[`undo Should undo to expected level intervals 1`] = ` " `; +exports[`undo should immediately create an undo level on typing 1`] = ` +" +

1

+" +`; + exports[`undo should undo typing after a pause 1`] = ` "

before pause after pause

diff --git a/packages/e2e-tests/specs/undo.test.js b/packages/e2e-tests/specs/undo.test.js index 4200e31f1ca6bb..d1b24ce2c907c4 100644 --- a/packages/e2e-tests/specs/undo.test.js +++ b/packages/e2e-tests/specs/undo.test.js @@ -29,6 +29,10 @@ describe( 'undo', () => { await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primary', 'z' ); + + expect( await getEditedPostContent() ).toBe( '' ); } ); it( 'should undo typing after non input change', async () => { @@ -43,6 +47,10 @@ describe( 'undo', () => { await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primary', 'z' ); + + expect( await getEditedPostContent() ).toBe( '' ); } ); it( 'Should undo to expected level intervals', async () => { @@ -100,4 +108,27 @@ describe( 'undo', () => { const visibleContent = await page.evaluate( () => document.activeElement.textContent ); expect( visibleContent ).toBe( 'original' ); } ); + + it( 'should immediately create an undo level on typing', async () => { + await clickBlockAppender(); + + await page.keyboard.type( '1' ); + await saveDraft(); + await page.reload(); + + // Expect undo button to be disabled. + expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).not.toBeNull(); + + await page.click( '.wp-block-paragraph' ); + + await page.keyboard.type( '2' ); + + // Expect undo button to be enabled. + expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).toBeNull(); + + await pressKeyWithModifier( 'primary', 'z' ); + + // Expect "1". + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 453cec63166696..fde5d0d28fe251 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -177,7 +177,13 @@ export function* setupEditor( post, edits, template ) { }; yield resetEditorBlocks( blocks, { __unstableShouldCreateUndoLevel: false } ); yield setupEditorState( post ); - if ( edits ) { + if ( + edits && + Object.keys( edits ).some( + ( key ) => + edits[ key ] !== ( has( post, [ key, 'raw' ] ) ? post[ key ].raw : post[ key ] ) + ) + ) { yield editPost( edits ); } yield* __experimentalSubscribeSources();