From 3486af9bf55aad4755f5fb24ec8e719bd9115e01 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 16 Jan 2024 12:51:57 +0800 Subject: [PATCH 1/6] Add a control per block to reset pattern overrides --- packages/block-library/src/block/edit.js | 17 ++++- .../src/hooks/pattern-partial-syncing.js | 27 ++++++- .../src/components/reset-overrides-control.js | 75 +++++++++++++++++++ packages/patterns/src/private-apis.js | 2 + 4 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 packages/patterns/src/components/reset-overrides-control.js diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index f8359c9889312..647bffa0a8290 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -29,6 +29,7 @@ import { } from '@wordpress/block-editor'; import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { parse, cloneBlock } from '@wordpress/blocks'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies @@ -131,6 +132,16 @@ function applyInitialContentValuesToInnerBlocks( } ); } +function isAttributeEqual( attribute1, attribute2 ) { + if ( + attribute1 instanceof RichTextData && + attribute2 instanceof RichTextData + ) { + return attribute1.toString() === attribute2.toString(); + } + return attribute1 === attribute2; +} + function getContentValuesFromInnerBlocks( blocks, defaultValues ) { /** @type {Record}>} */ const content = {}; @@ -145,8 +156,10 @@ function getContentValuesFromInnerBlocks( blocks, defaultValues ) { const attributes = getOverridableAttributes( block ); for ( const attributeKey of attributes ) { if ( - block.attributes[ attributeKey ] !== - defaultValues[ blockId ][ attributeKey ] + ! isAttributeEqual( + block.attributes[ attributeKey ], + defaultValues[ blockId ][ attributeKey ] + ) ) { content[ blockId ] ??= { values: {} }; content[ blockId ].values[ attributeKey ] = diff --git a/packages/editor/src/hooks/pattern-partial-syncing.js b/packages/editor/src/hooks/pattern-partial-syncing.js index 0ddfea8d9d8e3..f81e014683838 100644 --- a/packages/editor/src/hooks/pattern-partial-syncing.js +++ b/packages/editor/src/hooks/pattern-partial-syncing.js @@ -15,6 +15,7 @@ import { unlock } from '../lock-unlock'; const { PartialSyncingControls, + ResetOverridesControl, PATTERN_TYPES, PARTIAL_SYNCING_SUPPORTED_BLOCKS, } = unlock( patternsPrivateApis ); @@ -54,12 +55,30 @@ function ControlsWithStoreSubscription( props ) { select( editorStore ).getCurrentPostType() === PATTERN_TYPES.user, [] ); + const bindings = props.attributes.metadata?.bindings; + const hasPatternBindings = + !! bindings && + Object.values( bindings ).some( + ( binding ) => binding.source?.name === 'core/pattern-overrides' + ); + + const shouldShowPartialSyncingControls = + isEditingPattern && blockEditingMode === 'default'; + const shouldShowResetOverridesControl = + ! isEditingPattern && + !! props.attributes.metadata?.id && + blockEditingMode !== 'disabled' && + hasPatternBindings; return ( - isEditingPattern && - blockEditingMode === 'default' && ( - - ) + <> + { shouldShowPartialSyncingControls && ( + + ) } + { shouldShowResetOverridesControl && ( + + ) } + ); } diff --git a/packages/patterns/src/components/reset-overrides-control.js b/packages/patterns/src/components/reset-overrides-control.js new file mode 100644 index 0000000000000..cd8dfb2704f60 --- /dev/null +++ b/packages/patterns/src/components/reset-overrides-control.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { + store as blockEditorStore, + BlockControls, +} from '@wordpress/block-editor'; +import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; +import { useSelect, useRegistry } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { parse } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; + +function recursivelyFindBlockWithId( blocks, id ) { + return blocks.find( ( block ) => { + if ( block.attributes.metadata?.id === id ) { + return block; + } + + return recursivelyFindBlockWithId( block.innerBlocks, id ); + } ); +} + +export default function ResetOverridesControl( props ) { + const registry = useRegistry(); + const id = props.attributes.metadata?.id; + const patternWithOverrides = useSelect( + ( select ) => { + if ( ! id ) { + return undefined; + } + + const { getBlockParentsByBlockName, getBlocksByClientId } = + select( blockEditorStore ); + const patternBlock = getBlocksByClientId( + getBlockParentsByBlockName( props.clientId, 'core/block' ) + )[ 0 ]; + + if ( ! patternBlock?.attributes.overrides?.[ id ] ) { + return undefined; + } + + return patternBlock; + }, + [ props.clientId, id ] + ); + + const resetOverrides = async () => { + const editedRecord = await registry + .resolveSelect( coreStore ) + .getEditedEntityRecord( + 'postType', + 'wp_block', + patternWithOverrides.attributes.ref + ); + const blocks = editedRecord.blocks ?? parse( editedRecord.content ); + const block = recursivelyFindBlockWithId( blocks, id ); + + props.setAttributes( block.attributes ); + }; + + return ( + + + + { __( 'Reset to original' ) } + + + + ); +} diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index 046e20dd30003..099e4ae8ffed4 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -14,6 +14,7 @@ import RenamePatternModal from './components/rename-pattern-modal'; import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; import PartialSyncingControls from './components/partial-syncing-controls'; +import ResetOverridesControl from './components/reset-overrides-control'; import { PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, @@ -33,6 +34,7 @@ lock( privateApis, { PatternsMenuItems, RenamePatternCategoryModal, PartialSyncingControls, + ResetOverridesControl, PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, PATTERN_USER_CATEGORY, From 24659174f1a6ce4ed7cff820b7f4f93db0acbbfb Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Sun, 4 Feb 2024 23:41:41 +0800 Subject: [PATCH 2/6] Reword the control --- packages/patterns/src/components/reset-overrides-control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/patterns/src/components/reset-overrides-control.js b/packages/patterns/src/components/reset-overrides-control.js index cd8dfb2704f60..f623f98d5412d 100644 --- a/packages/patterns/src/components/reset-overrides-control.js +++ b/packages/patterns/src/components/reset-overrides-control.js @@ -67,7 +67,7 @@ export default function ResetOverridesControl( props ) { disabled={ ! patternWithOverrides } __experimentalIsFocusable > - { __( 'Reset to original' ) } + { __( 'Reset' ) } From 878acd91ca20ccc1c768eb3e5abf1069270fee2d Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 6 Feb 2024 13:44:49 +0800 Subject: [PATCH 3/6] Update code to check for new block condensed bindings format --- packages/editor/src/hooks/pattern-partial-syncing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/hooks/pattern-partial-syncing.js b/packages/editor/src/hooks/pattern-partial-syncing.js index f81e014683838..f86268cb49546 100644 --- a/packages/editor/src/hooks/pattern-partial-syncing.js +++ b/packages/editor/src/hooks/pattern-partial-syncing.js @@ -59,7 +59,7 @@ function ControlsWithStoreSubscription( props ) { const hasPatternBindings = !! bindings && Object.values( bindings ).some( - ( binding ) => binding.source?.name === 'core/pattern-overrides' + ( binding ) => binding.source === 'core/pattern-overrides' ); const shouldShowPartialSyncingControls = From 64fbe18bae666dfb1e7be2b274a5d9d83eb94042 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 6 Feb 2024 16:10:53 +0800 Subject: [PATCH 4/6] Fix the name field --- packages/patterns/src/components/reset-overrides-control.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/patterns/src/components/reset-overrides-control.js b/packages/patterns/src/components/reset-overrides-control.js index f623f98d5412d..d1a7481f27556 100644 --- a/packages/patterns/src/components/reset-overrides-control.js +++ b/packages/patterns/src/components/reset-overrides-control.js @@ -36,7 +36,7 @@ export default function ResetOverridesControl( props ) { getBlockParentsByBlockName( props.clientId, 'core/block' ) )[ 0 ]; - if ( ! patternBlock?.attributes.overrides?.[ id ] ) { + if ( ! patternBlock?.attributes.content?.[ id ] ) { return undefined; } @@ -60,7 +60,7 @@ export default function ResetOverridesControl( props ) { }; return ( - + Date: Tue, 6 Feb 2024 16:30:04 +0800 Subject: [PATCH 5/6] Add e2e test --- .../editor/various/pattern-overrides.spec.js | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index c5cb9d2599170..ba619b66fc474 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -493,4 +493,95 @@ test.describe( 'Pattern Overrides', () => { page.getByText( 'Inner paragraph (edited)' ) ).toBeVisible(); } ); + + test( 'resets overrides after clicking the reset button', async ( { + page, + admin, + requestUtils, + editor, + } ) => { + const headingId = 'heading-id'; + const paragraphId = 'paragraph-id'; + const { id } = await requestUtils.createBlock( { + title: 'Pattern', + content: ` +

Heading

+ + +

Paragraph

+`, + status: 'publish', + } ); + + await admin.createNewPost(); + + await editor.insertBlock( { + name: 'core/block', + attributes: { ref: id }, + } ); + + // Make an edit to the heading. + await editor.canvas + .getByRole( 'document', { name: 'Block: Heading' } ) + .fill( 'Heading (edited)' ); + + const patternBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Pattern', + } ); + const headingBlock = patternBlock.getByRole( 'document', { + name: 'Block: Heading', + } ); + const paragraphBlock = patternBlock.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + const resetButton = page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Reset' } ); + + // Assert the pattern block. + await editor.selectBlocks( patternBlock ); + await editor.showBlockToolbar(); + await expect( + resetButton, + 'The pattern block should have the reset button enabled' + ).toBeEnabled(); + + // Assert the modified heading block with overrides. + await editor.selectBlocks( headingBlock ); + await editor.showBlockToolbar(); + await expect( + resetButton, + 'The heading block should have the reset button enabled' + ).toBeEnabled(); + + // Assert the unmodified paragraph block (no overrides). + await editor.selectBlocks( paragraphBlock ); + await editor.showBlockToolbar(); + await expect( + resetButton, + 'The paragraph block should not have the reset button enabled' + ).toBeDisabled(); + + // Reset the whole pattern. + await editor.selectBlocks( patternBlock ); + await editor.showBlockToolbar(); + await resetButton.click(); + await expect( headingBlock ).toHaveText( 'Heading' ); + + // Undo should work + await page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Undo' } ) + .click(); + await expect( headingBlock ).toHaveText( 'Heading (edited)' ); + + // Reset the individual heading block. + await editor.selectBlocks( headingBlock ); + await editor.showBlockToolbar(); + await resetButton.click(); + await expect( headingBlock ).toHaveText( 'Heading' ); + await editor.selectBlocks( patternBlock ); + await editor.showBlockToolbar(); + await expect( resetButton ).toBeDisabled(); + } ); } ); From 7421a50c4ff378c4bf8907e764a5f48a08206800 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 6 Feb 2024 18:11:11 +0800 Subject: [PATCH 6/6] Fix nested block --- .../patterns/src/components/reset-overrides-control.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/patterns/src/components/reset-overrides-control.js b/packages/patterns/src/components/reset-overrides-control.js index d1a7481f27556..03d520d2e9b81 100644 --- a/packages/patterns/src/components/reset-overrides-control.js +++ b/packages/patterns/src/components/reset-overrides-control.js @@ -12,13 +12,16 @@ import { parse } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; function recursivelyFindBlockWithId( blocks, id ) { - return blocks.find( ( block ) => { + for ( const block of blocks ) { if ( block.attributes.metadata?.id === id ) { return block; } - return recursivelyFindBlockWithId( block.innerBlocks, id ); - } ); + const found = recursivelyFindBlockWithId( block.innerBlocks, id ); + if ( found ) { + return found; + } + } } export default function ResetOverridesControl( props ) {