diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 6cc1e021841b4..725ba9f99ae03 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -5,7 +5,7 @@ import { createContext, useContext, useCallback, - useEffect, + useMemo, } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; @@ -156,12 +156,12 @@ export function useEntityProp( kind, name, prop, _id ) { export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const providerId = useEntityId( kind, name ); const id = _id ?? providerId; - const { content, blocks, meta } = useSelect( + const { content, editedBlocks, meta } = useSelect( ( select ) => { const { getEditedEntityRecord } = select( STORE_NAME ); const editedRecord = getEditedEntityRecord( kind, name, id ); return { - blocks: editedRecord.blocks, + editedBlocks: editedRecord.blocks, content: editedRecord.content, meta: editedRecord.meta, }; @@ -171,23 +171,15 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const { __unstableCreateUndoLevel, editEntityRecord } = useDispatch( STORE_NAME ); - useEffect( () => { - // Load the blocks from the content if not already in state - // Guard against other instances that might have - // set content to a function already or the blocks are already in state. - if ( content && typeof content !== 'function' && ! blocks ) { - const parsedContent = parse( content ); - editEntityRecord( - kind, - name, - id, - { - blocks: parsedContent, - }, - { undoIgnore: true } - ); + const blocks = useMemo( () => { + if ( editedBlocks ) { + return editedBlocks; } - }, [ content ] ); + + return content && typeof content !== 'function' + ? parse( content ) + : EMPTY_ARRAY; + }, [ editedBlocks, content ] ); const updateFootnotes = useCallback( ( _blocks ) => { @@ -356,5 +348,5 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { [ kind, name, id, updateFootnotes, editEntityRecord ] ); - return [ blocks ?? EMPTY_ARRAY, onInput, onChange ]; + return [ blocks, onInput, onChange ]; } diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index a533851751b18..36c258aabf57d 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -10,6 +10,7 @@ import { getFreeformContentHandlerName, getDefaultBlockName, __unstableSerializeAndClean, + parse, } from '@wordpress/blocks'; import { isInTheFuture, getDate } from '@wordpress/date'; import { addQueryArgs, cleanForSlug } from '@wordpress/url'; @@ -42,15 +43,6 @@ import { getTemplatePartIcon } from '../utils/get-template-part-icon'; */ const EMPTY_OBJECT = {}; -/** - * Shared reference to an empty array for cases where it is important to avoid - * returning a new array reference on every invocation, as in a connected or - * other pure component which performs `shouldComponentUpdate` check on props. - * This should be used as a last resort, since the normalized data should be - * maintained by the reducer result in state. - */ -const EMPTY_ARRAY = []; - /** * Returns true if any past editor history snapshots exist, or false otherwise. * @@ -1088,9 +1080,18 @@ export const isPublishSidebarEnabled = createRegistrySelector( * @param {Object} state * @return {Array} Block list. */ -export function getEditorBlocks( state ) { - return getEditedPostAttribute( state, 'blocks' ) || EMPTY_ARRAY; -} +export const getEditorBlocks = createSelector( + ( state ) => { + return ( + getEditedPostAttribute( state, 'blocks' ) || + parse( getEditedPostContent( state ) ) + ); + }, + ( state ) => [ + getEditedPostAttribute( state, 'blocks' ), + getEditedPostContent( state ), + ] +); /** * A block selection object. diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 2a22b4523444b..4f713e0cf7e83 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -2150,6 +2150,7 @@ describe( 'selectors', () => { attributes: { providerNameSlug: 'instagram', }, + innerBlocks: [], }, ], }, @@ -2172,6 +2173,7 @@ describe( 'selectors', () => { clientId: 567, name: 'core/embed', attributes: {}, + innerBlocks: [], }, ], }, @@ -2194,11 +2196,13 @@ describe( 'selectors', () => { clientId: 123, name: 'core/image', attributes: {}, + innerBlocks: [], }, { clientId: 456, name: 'core/quote', attributes: {}, + innerBlocks: [], }, ], }, @@ -2222,6 +2226,7 @@ describe( 'selectors', () => { clientId: 123, name: 'core/image', attributes: {}, + innerBlocks: [], }, ], }, @@ -2245,6 +2250,7 @@ describe( 'selectors', () => { clientId: 456, name: 'core/quote', attributes: {}, + innerBlocks: [], }, ], }, @@ -2270,6 +2276,7 @@ describe( 'selectors', () => { attributes: { providerNameSlug: 'youtube', }, + innerBlocks: [], }, ], }, @@ -2295,6 +2302,7 @@ describe( 'selectors', () => { attributes: { providerNameSlug: 'soundcloud', }, + innerBlocks: [], }, ], }, @@ -2318,11 +2326,13 @@ describe( 'selectors', () => { clientId: 456, name: 'core/quote', attributes: {}, + innerBlocks: [], }, { clientId: 789, name: 'core/paragraph', attributes: {}, + innerBlocks: [], }, ], }, diff --git a/test/e2e/specs/editor/blocks/comments.spec.js b/test/e2e/specs/editor/blocks/comments.spec.js index 4b0dbf66b2a30..9a8c02c39d398 100644 --- a/test/e2e/specs/editor/blocks/comments.spec.js +++ b/test/e2e/specs/editor/blocks/comments.spec.js @@ -313,8 +313,14 @@ test.describe( 'Post Comments', () => { ).toBeVisible(); // Check the block definition has changed. - const content = await editor.getEditedPostContent(); - expect( content ).toBe( '' ); + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/comments', + attributes: { + legacy: true, + }, + }, + ] ); // Visit post await page.goto( `/?p=${ postId }` ); diff --git a/test/e2e/specs/editor/various/undo.spec.js b/test/e2e/specs/editor/various/undo.spec.js index 5c4355882ee89..17d365a340680 100644 --- a/test/e2e/specs/editor/various/undo.spec.js +++ b/test/e2e/specs/editor/various/undo.spec.js @@ -174,8 +174,14 @@ test.describe( 'undo', () => { await pageUtils.pressKeys( 'primary+a' ); await pageUtils.pressKeys( 'primary+b' ); await pageUtils.pressKeys( 'primary+z' ); - const activeElementLocator = editor.canvas.locator( ':focus' ); - await expect( activeElementLocator ).toHaveText( 'test' ); + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/paragraph', + attributes: { + content: 'test', + }, + }, + ] ); } ); test( 'Should undo/redo to expected level intervals', async ( { @@ -353,7 +359,9 @@ test.describe( 'undo', () => { // regression present was accurate, it would produce the correct // content. The issue had manifested in the form of what was shown to // the user since the blocks state failed to sync to block editor. - const activeElementLocator = editor.canvas.locator( ':focus' ); + const activeElementLocator = editor.canvas.locator( + '[data-type="core/paragraph"] >> nth=0' + ); await expect( activeElementLocator ).toHaveText( 'original' ); } );