diff --git a/packages/block-editor/src/components/provider/test/use-block-sync.js b/packages/block-editor/src/components/provider/test/use-block-sync.js index 4b2340aa5e218..32c6d71ad2c9b 100644 --- a/packages/block-editor/src/components/provider/test/use-block-sync.js +++ b/packages/block-editor/src/components/provider/test/use-block-sync.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + /** * External dependencies */ @@ -19,6 +24,15 @@ const TestWrapper = withRegistryProvider( ( props ) => { } ); describe( 'useBlockSync hook', () => { + beforeAll( () => { + registerBlockType( 'test/test-block', { + title: 'Test block', + attributes: { + foo: { type: 'number' }, + }, + } ); + } ); + afterEach( () => { jest.clearAllMocks(); } ); @@ -101,7 +115,12 @@ describe( 'useBlockSync hook', () => { ); const testBlocks = [ - { clientId: 'a', innerBlocks: [], attributes: { foo: 1 } }, + { + name: 'test/test-block', + clientId: 'a', + innerBlocks: [], + attributes: { foo: 1 }, + }, ]; await act( async () => { root.update( @@ -131,7 +150,12 @@ describe( 'useBlockSync hook', () => { const onInput = jest.fn(); const value1 = [ - { clientId: 'a', innerBlocks: [], attributes: { foo: 1 } }, + { + name: 'test/test-block', + clientId: 'a', + innerBlocks: [], + attributes: { foo: 1 }, + }, ]; let root; let registry; @@ -282,7 +306,12 @@ describe( 'useBlockSync hook', () => { const onInput = jest.fn(); const value1 = [ - { clientId: 'a', innerBlocks: [], attributes: { foo: 1 } }, + { + name: 'test/test-block', + clientId: 'a', + innerBlocks: [], + attributes: { foo: 1 }, + }, ]; await act( async () => { @@ -325,7 +354,12 @@ describe( 'useBlockSync hook', () => { const onInput = jest.fn(); const value1 = [ - { clientId: 'a', innerBlocks: [], attributes: { foo: 1 } }, + { + name: 'test/test-block', + clientId: 'a', + innerBlocks: [], + attributes: { foo: 1 }, + }, ]; let registry; @@ -352,7 +386,14 @@ describe( 'useBlockSync hook', () => { expect( replaceInnerBlocks ).not.toHaveBeenCalled(); expect( onChange ).toHaveBeenCalledWith( - [ { clientId: 'a', innerBlocks: [], attributes: { foo: 2 } } ], + [ + { + name: 'test/test-block', + clientId: 'a', + innerBlocks: [], + attributes: { foo: 2 }, + }, + ], { selectionEnd: {}, selectionStart: {} } ); expect( onInput ).not.toHaveBeenCalled(); diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index 0bcd7f9f329a7..6eba648f4d702 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -63,8 +63,6 @@ import { cloneBlock } from '@wordpress/blocks'; * change has been made in the block-editor blocks * for the given clientId. When this is called, * controlling sources do not become dirty. - * @param {boolean} props.__unstableCloneValue Whether or not to clone each of - * the blocks provided in the value prop. */ export default function useBlockSync( { clientId = null, @@ -73,7 +71,6 @@ export default function useBlockSync( { selectionEnd: controlledSelectionEnd, onChange = noop, onInput = noop, - __unstableCloneValue = true, } ) { const registry = useRegistry(); @@ -101,12 +98,9 @@ export default function useBlockSync( { if ( clientId ) { setHasControlledInnerBlocks( clientId, true ); __unstableMarkNextChangeAsNotPersistent(); - let storeBlocks = controlledBlocks; - if ( __unstableCloneValue ) { - storeBlocks = controlledBlocks.map( ( block ) => - cloneBlock( block ) - ); - } + const storeBlocks = controlledBlocks.map( ( block ) => + cloneBlock( block ) + ); if ( subscribed.current ) { pendingChanges.current.incoming = storeBlocks; } diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 05f7346983fa9..89b88b4e58750 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Change + +- `cloneBlock` now sanitizes the attributes to match the same logic `createBlock` has. [#28379](https://github.com/WordPress/gutenberg/pull/28379) + ## 6.25.0 (2020-12-17) ### New Feature diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index b7c6113277290..ad0e9ca4f39a4 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -4,7 +4,6 @@ import { v4 as uuid } from 'uuid'; import { every, - reduce, castArray, findIndex, isObjectLike, @@ -31,7 +30,7 @@ import { getBlockTypes, getGroupingBlockName, } from './registration'; -import { normalizeBlockType } from './utils'; +import { normalizeBlockType, sanitizeBlockAttributes } from './utils'; /** * Returns a block object given its type and attributes. @@ -43,40 +42,7 @@ import { normalizeBlockType } from './utils'; * @return {Object} Block object. */ export function createBlock( name, attributes = {}, innerBlocks = [] ) { - // Get the type definition associated with a registered block. - const blockType = getBlockType( name ); - - if ( undefined === blockType ) { - throw new Error( `Block type '${ name }' is not registered.` ); - } - - // Ensure attributes contains only values defined by block type, and merge - // default values for missing attributes. - const sanitizedAttributes = reduce( - blockType.attributes, - ( accumulator, schema, key ) => { - const value = attributes[ key ]; - - if ( undefined !== value ) { - accumulator[ key ] = value; - } else if ( schema.hasOwnProperty( 'default' ) ) { - accumulator[ key ] = schema.default; - } - - if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) { - // Ensure value passed is always an array, which we're expecting in - // the RichText component to handle the deprecated value. - if ( typeof accumulator[ key ] === 'string' ) { - accumulator[ key ] = [ accumulator[ key ] ]; - } else if ( ! Array.isArray( accumulator[ key ] ) ) { - accumulator[ key ] = []; - } - } - - return accumulator; - }, - {} - ); + const sanitizedAttributes = sanitizeBlockAttributes( name, attributes ); const clientId = uuid(); @@ -134,13 +100,15 @@ export function createBlocksFromInnerBlocksTemplate( export function cloneBlock( block, mergeAttributes = {}, newInnerBlocks ) { const clientId = uuid(); + const sanitizedAttributes = sanitizeBlockAttributes( block.name, { + ...block.attributes, + ...mergeAttributes, + } ); + return { ...block, clientId, - attributes: { - ...block.attributes, - ...mergeAttributes, - }, + attributes: sanitizedAttributes, innerBlocks: newInnerBlocks || block.innerBlocks.map( ( innerBlock ) => cloneBlock( innerBlock ) ), diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index 47679b1cb8d05..b503664b9e4da 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -159,6 +159,23 @@ describe( 'block factory', () => { content: 'test', } ); } ); + + it( 'should sanitize attributes not defined in the block type', () => { + registerBlockType( 'core/test-block', { + ...defaultBlockSettings, + attributes: { + align: { + type: 'string', + }, + }, + } ); + + const block = createBlock( 'core/test-block', { + notDefined: 'not-defined', + } ); + + expect( block.attributes ).toEqual( {} ); + } ); } ); describe( 'createBlocksFromInnerBlocksTemplate', () => { @@ -274,6 +291,31 @@ describe( 'block factory', () => { type: 'boolean', default: false, }, + includesDefault: { + type: 'boolean', + default: true, + }, + includesFalseyDefault: { + type: 'number', + default: 0, + }, + content: { + type: 'array', + source: 'children', + }, + defaultContent: { + type: 'array', + source: 'children', + default: 'test', + }, + unknownDefaultContent: { + type: 'array', + source: 'children', + default: 1, + }, + htmlContent: { + source: 'html', + }, }, save: noop, category: 'text', @@ -287,12 +329,19 @@ describe( 'block factory', () => { const clonedBlock = cloneBlock( block, { isDifferent: true, + htmlContent: 'test', } ); expect( clonedBlock.name ).toEqual( block.name ); expect( clonedBlock.attributes ).toEqual( { + includesDefault: true, + includesFalseyDefault: 0, align: 'left', isDifferent: true, + content: [], + defaultContent: [ 'test' ], + unknownDefaultContent: [], + htmlContent: 'test', } ); expect( clonedBlock.innerBlocks ).toHaveLength( 1 ); expect( typeof clonedBlock.clientId ).toBe( 'string' ); @@ -375,6 +424,27 @@ describe( 'block factory', () => { block.innerBlocks[ 1 ].attributes ); } ); + + it( 'should sanitize attributes not defined in the block type', () => { + registerBlockType( 'core/test-block', { + ...defaultBlockSettings, + attributes: { + align: { + type: 'string', + }, + }, + } ); + + const block = createBlock( 'core/test-block', { + notDefined: 'not-defined', + } ); + + const clonedBlock = cloneBlock( block, { + notDefined2: 'not-defined-2', + } ); + + expect( clonedBlock.attributes ).toEqual( {} ); + } ); } ); describe( 'getPossibleBlockTransformations()', () => { diff --git a/packages/blocks/src/api/test/utils.js b/packages/blocks/src/api/test/utils.js index f77e6ea2300ba..b9b5458a0c0c0 100644 --- a/packages/blocks/src/api/test/utils.js +++ b/packages/blocks/src/api/test/utils.js @@ -17,6 +17,7 @@ import { isUnmodifiedDefaultBlock, getAccessibleBlockLabel, getBlockLabel, + sanitizeBlockAttributes, } from '../utils'; describe( 'block helpers', () => { @@ -212,3 +213,88 @@ describe( 'getAccessibleBlockLabel', () => { ); } ); } ); + +describe( 'sanitizeBlockAttributes', () => { + afterEach( () => { + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + } ); + + it( 'sanitize block attributes not defined in the block type', () => { + registerBlockType( 'core/test-block', { + attributes: { + defined: { + type: 'string', + }, + }, + title: 'Test block', + } ); + + const attributes = sanitizeBlockAttributes( 'core/test-block', { + defined: 'defined-attribute', + notDefined: 'not-defined-attribute', + } ); + + expect( attributes ).toEqual( { + defined: 'defined-attribute', + } ); + } ); + + it( 'throws error if the block is not registered', () => { + expect( () => { + sanitizeBlockAttributes( 'core/not-registered-test-block', {} ); + } ).toThrowErrorMatchingInlineSnapshot( + `"Block type 'core/not-registered-test-block' is not registered."` + ); + } ); + + it( 'handles undefined values and default values', () => { + registerBlockType( 'core/test-block', { + attributes: { + hasDefaultValue: { + type: 'string', + default: 'default-value', + }, + noDefaultValue: { + type: 'string', + }, + }, + title: 'Test block', + } ); + + const attributes = sanitizeBlockAttributes( 'core/test-block', {} ); + + expect( attributes ).toEqual( { + hasDefaultValue: 'default-value', + } ); + } ); + + it( 'handles node and children sources as arrays', () => { + registerBlockType( 'core/test-block', { + attributes: { + nodeContent: { + source: 'node', + }, + childrenContent: { + source: 'children', + }, + withDefault: { + source: 'children', + default: 'test', + }, + }, + title: 'Test block', + } ); + + const attributes = sanitizeBlockAttributes( 'core/test-block', { + nodeContent: [ 'test-1', 'test-2' ], + } ); + + expect( attributes ).toEqual( { + nodeContent: [ 'test-1', 'test-2' ], + childrenContent: [], + withDefault: [ 'test' ], + } ); + } ); +} ); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index a45a80e972c8a..70bf206263960 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { every, has, isFunction, isString, startCase } from 'lodash'; +import { every, has, isFunction, isString, startCase, reduce } from 'lodash'; import { default as tinycolor, mostReadable } from 'tinycolor2'; /** @@ -246,3 +246,46 @@ export function getAccessibleBlockLabel( title ); } + +/** + * Ensure attributes contains only values defined by block type, and merge + * default values for missing attributes. + * + * @param {string} name The block's name. + * @param {Object} attributes The block's attributes. + * @return {Object} The sanitized attributes. + */ +export function sanitizeBlockAttributes( name, attributes ) { + // Get the type definition associated with a registered block. + const blockType = getBlockType( name ); + + if ( undefined === blockType ) { + throw new Error( `Block type '${ name }' is not registered.` ); + } + + return reduce( + blockType.attributes, + ( accumulator, schema, key ) => { + const value = attributes[ key ]; + + if ( undefined !== value ) { + accumulator[ key ] = value; + } else if ( schema.hasOwnProperty( 'default' ) ) { + accumulator[ key ] = schema.default; + } + + if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) { + // Ensure value passed is always an array, which we're expecting in + // the RichText component to handle the deprecated value. + if ( typeof accumulator[ key ] === 'string' ) { + accumulator[ key ] = [ accumulator[ key ] ]; + } else if ( ! Array.isArray( accumulator[ key ] ) ) { + accumulator[ key ] = []; + } + } + + return accumulator; + }, + {} + ); +} diff --git a/packages/e2e-tests/specs/widgets/adding-widgets.test.js b/packages/e2e-tests/specs/widgets/adding-widgets.test.js index 3fd2fe3c744a5..b48582615fb1e 100644 --- a/packages/e2e-tests/specs/widgets/adding-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/adding-widgets.test.js @@ -306,6 +306,86 @@ describe( 'Widgets screen', () => { } ` ); } ); + + it( 'Should duplicate the widgets', async () => { + const firstWidgetArea = await page.$( + '[aria-label="Block: Widget Area"][role="group"]' + ); + + const addParagraphBlock = await getParagraphBlockInGlobalInserter(); + await addParagraphBlock.click(); + + const firstParagraphBlock = await firstWidgetArea.$( + '[data-block][data-type="core/paragraph"][aria-label^="Empty block"]' + ); + await page.keyboard.type( 'First Paragraph' ); + + // Trigger the toolbar to appear. + await page.keyboard.down( 'Shift' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.up( 'Shift' ); + + const blockToolbar = await page.waitForSelector( + '[role="toolbar"][aria-label="Block tools"]' + ); + const moreOptionsButton = await blockToolbar.$( + 'button[aria-label="Options"]' + ); + await moreOptionsButton.click(); + + const optionsMenu = await page.waitForSelector( + '[role="menu"][aria-label="Options"]' + ); + const [ duplicateButton ] = await optionsMenu.$x( + '//*[@role="menuitem"][*[text()="Duplicate"]]' + ); + await duplicateButton.click(); + + await page.waitForFunction( + ( paragraph ) => + paragraph.nextSibling && + paragraph.nextSibling.matches( '[data-block]' ), + {}, + firstParagraphBlock + ); + const duplicatedParagraphBlock = await firstParagraphBlock.evaluateHandle( + ( paragraph ) => paragraph.nextSibling + ); + + const firstParagraphBlockClientId = await firstParagraphBlock.evaluate( + ( node ) => node.dataset.block + ); + const duplicatedParagraphBlockClientId = await duplicatedParagraphBlock.evaluate( + ( node ) => node.dataset.block + ); + + expect( firstParagraphBlockClientId ).not.toBe( + duplicatedParagraphBlockClientId + ); + expect( + await duplicatedParagraphBlock.evaluate( + ( node ) => node.textContent + ) + ).toBe( 'First Paragraph' ); + expect( + await duplicatedParagraphBlock.evaluate( + ( node ) => node === document.activeElement + ) + ).toBe( true ); + + await saveWidgets(); + const serializedWidgetAreas = await getSerializedWidgetAreas(); + expect( serializedWidgetAreas ).toMatchInlineSnapshot( ` + Object { + "sidebar-1": "
+

First Paragraph

+
+
+

First Paragraph

+
", + } + ` ); + } ); } ); async function saveWidgets() { diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js index a0f2ce95a7881..0bdb7d71ff5a4 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js @@ -192,12 +192,10 @@ function LegacyWidgetEdit( { export default withSelect( ( select, { clientId, attributes } ) => { const { widgetClass, referenceWidgetName } = attributes; - let widgetId = select( editWidgetsStore ).getWidgetIdForClientId( - clientId - ); + let widgetId = attributes.__internalWidgetId; const widget = select( editWidgetsStore ).getWidget( widgetId ); - const widgetArea = select( editWidgetsStore ).getWidgetAreaForClientId( - clientId + const widgetArea = select( editWidgetsStore ).getWidgetAreaForWidgetId( + widgetId ); const editorSettings = select( 'core/block-editor' ).getSettings(); const { availableLegacyWidgets } = editorSettings; diff --git a/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js b/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js index 713f70089aad2..7b27a404a2f6c 100644 --- a/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js +++ b/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js @@ -16,11 +16,6 @@ export default function WidgetAreaInnerBlocks() { onChange={ onChange } templateLock={ false } renderAppender={ InnerBlocks.DefaultBlockAppender } - // HACK: The widget editor relies on a mapping of block client IDs - // to widget IDs. We therefore instruct `useBlockSync` to not clone - // the blocks it receives which would change the block client IDs`. - // See https://github.com/WordPress/gutenberg/issues/27173. - __unstableCloneValue={ false } /> ); } diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 448aae908c2ea..8c236f0efb201 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { invert } from 'lodash'; - /** * WordPress dependencies */ @@ -14,7 +9,7 @@ import { store as interfaceStore } from '@wordpress/interface'; * Internal dependencies */ import { STATE_SUCCESS } from './batch-processing/constants'; -import { dispatch, select, getWidgetToClientIdMapping } from './controls'; +import { dispatch, select } from './controls'; import { transformBlockToWidget } from './transformers'; import { buildWidgetAreaPostId, @@ -99,8 +94,6 @@ export function* saveWidgetAreas( widgetAreas ) { export function* saveWidgetArea( widgetAreaId ) { const widgets = yield select( editWidgetsStoreName, 'getWidgets' ); - const widgetIdToClientId = yield getWidgetToClientIdMapping(); - const clientIdToWidgetId = invert( widgetIdToClientId ); const post = yield select( 'core', @@ -128,7 +121,7 @@ export function* saveWidgetArea( widgetAreaId ) { const sidebarWidgetsIds = []; for ( let i = 0; i < widgetsBlocks.length; i++ ) { const block = widgetsBlocks[ i ]; - const widgetId = clientIdToWidgetId[ block.clientId ]; + const widgetId = block.attributes.__internalWidgetId; const oldWidget = widgets[ widgetId ]; const widget = transformBlockToWidget( block, oldWidget ); // We'll replace the null widgetId after save, but we track it here @@ -222,13 +215,10 @@ export function* saveWidgetArea( widgetAreaId ) { for ( let i = 0; i < batch.sortedItemIds.length; i++ ) { const itemId = batch.sortedItemIds[ i ]; const widget = batch.results[ itemId ]; - const { clientId, position } = batchMeta[ i ]; + const { position } = batchMeta[ i ]; if ( ! sidebarWidgetsIds[ position ] ) { sidebarWidgetsIds[ position ] = widget.id; } - if ( clientId !== widgetIdToClientId[ widget.id ] ) { - yield setWidgetIdForClientId( clientId, widget.id ); - } } yield dispatch( diff --git a/packages/edit-widgets/src/store/controls.js b/packages/edit-widgets/src/store/controls.js index b017c91ab6f8c..d71b6bba4a8c1 100644 --- a/packages/edit-widgets/src/store/controls.js +++ b/packages/edit-widgets/src/store/controls.js @@ -54,17 +54,6 @@ export function isProcessingPost( postId ) { }; } -/** - * Selects widgetId -> clientId mapping (necessary for saving widgets). - * - * @return {Object} Action. - */ -export function getWidgetToClientIdMapping() { - return { - type: 'GET_WIDGET_TO_CLIENT_ID_MAPPING', - }; -} - /** * Resolves navigation post for given menuId. * @@ -165,12 +154,6 @@ const controls = { } ), - GET_WIDGET_TO_CLIENT_ID_MAPPING: createRegistryControl( - ( registry ) => () => { - return getState( registry ).mapping || {}; - } - ), - DISPATCH: createRegistryControl( ( registry ) => ( { registryName, actionName, args } ) => { return registry.dispatch( registryName )[ actionName ]( ...args ); diff --git a/packages/edit-widgets/src/store/reducer.js b/packages/edit-widgets/src/store/reducer.js index 27db2f5bd2bd5..93e650c88769f 100644 --- a/packages/edit-widgets/src/store/reducer.js +++ b/packages/edit-widgets/src/store/reducer.js @@ -3,31 +3,6 @@ */ import { combineReducers } from '@wordpress/data'; -/** - * Internal to edit-widgets package. - * - * Stores widgetId -> clientId mapping which is necessary for saving the navigation. - * - * @param {Object} state Redux state - * @param {Object} action Redux action - * @return {Object} Updated state - */ -export function mapping( state, action ) { - const { type, ...rest } = action; - if ( type === 'SET_WIDGET_TO_CLIENT_ID_MAPPING' ) { - return rest.mapping; - } - if ( type === 'SET_WIDGET_ID_FOR_CLIENT_ID' ) { - const newMapping = { - ...state, - }; - newMapping[ action.widgetId ] = action.clientId; - return newMapping; - } - - return state || {}; -} - /** * Controls the open state of the widget areas. * @@ -69,7 +44,6 @@ function isInserterOpened( state = false, action ) { } export default combineReducers( { - mapping, isInserterOpened, widgetAreasOpenState, } ); diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index d992e5999ed72..44d0f4614f7f9 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { invert, keyBy } from 'lodash'; +import { keyBy } from 'lodash'; /** * WordPress dependencies @@ -53,32 +53,26 @@ export const getWidgetAreas = createRegistrySelector( ( select ) => () => { ); } ); -export const getWidgetIdForClientId = ( state, clientId ) => { - const widgetIdToClientId = state.mapping; - const clientIdToWidgetId = invert( widgetIdToClientId ); - return clientIdToWidgetId[ clientId ]; -}; - /** - * Returns widgetArea containing a block identify by given clientId + * Returns widgetArea containing a block identify by given widgetId * - * @param {string} clientId The ID of the block. + * @param {string} widgetId The ID of the widget. * @return {Object} Containing widget area. */ -export const getWidgetAreaForClientId = createRegistrySelector( - ( select ) => ( state, clientId ) => { +export const getWidgetAreaForWidgetId = createRegistrySelector( + ( select ) => ( state, widgetId ) => { const widgetAreas = select( editWidgetsStoreName ).getWidgetAreas(); - for ( const widgetArea of widgetAreas ) { + return widgetAreas.find( ( widgetArea ) => { const post = select( 'core' ).getEditedEntityRecord( KIND, POST_TYPE, buildWidgetAreaPostId( widgetArea.id ) ); - const clientIds = post.blocks.map( ( block ) => block.clientId ); - if ( clientIds.includes( clientId ) ) { - return widgetArea; - } - } + const blockWidgetIds = post.blocks.map( + ( block ) => block.attributes.__internalWidgetId + ); + return blockWidgetIds.includes( widgetId ); + } ); } ); diff --git a/packages/edit-widgets/src/store/transformers.js b/packages/edit-widgets/src/store/transformers.js index 24fd6751297ff..ac1a40134c1f1 100644 --- a/packages/edit-widgets/src/store/transformers.js +++ b/packages/edit-widgets/src/store/transformers.js @@ -3,13 +3,26 @@ */ import { createBlock, parse, serialize } from '@wordpress/blocks'; +function __experimentalAddWidgetIdToBlock( block, widgetId ) { + return { + ...block, + attributes: { + ...( block.attributes || {} ), + __internalWidgetId: widgetId, + }, + }; +} + export function transformWidgetToBlock( widget ) { if ( widget.widget_class === 'WP_Widget_Block' ) { const parsedBlocks = parse( widget.settings.content ); if ( ! parsedBlocks.length ) { - return createBlock( 'core/paragraph', {}, [] ); + return __experimentalAddWidgetIdToBlock( + createBlock( 'core/paragraph', {}, [] ), + widget.id + ); } - return parsedBlocks[ 0 ]; + return __experimentalAddWidgetIdToBlock( parsedBlocks[ 0 ], widget.id ); } const attributes = { @@ -27,7 +40,10 @@ export function transformWidgetToBlock( widget ) { attributes.widgetClass = widget.widget_class; } - return createBlock( 'core/legacy-widget', attributes, [] ); + return __experimentalAddWidgetIdToBlock( + createBlock( 'core/legacy-widget', attributes, [] ), + widget.id + ); } export function transformBlockToWidget( block, relatedWidget = {} ) {