diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index c53f52bfb703e..53c7764c0e245 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 @@ -117,6 +118,16 @@ function applyInitialOverrides( blocks, overrides = {}, defaultValues ) { } ); } +function isAttributeEqual( attribute1, attribute2 ) { + if ( + attribute1 instanceof RichTextData && + attribute2 instanceof RichTextData + ) { + return attribute1.toString() === attribute2.toString(); + } + return attribute1 === attribute2; +} + function getOverridesFromBlocks( blocks, defaultValues ) { /** @type {Record>} */ const overrides = {}; @@ -130,8 +141,10 @@ function getOverridesFromBlocks( blocks, defaultValues ) { const attributes = getPartiallySyncedAttributes( block ); for ( const attributeKey of attributes ) { if ( - block.attributes[ attributeKey ] !== - defaultValues[ blockId ][ attributeKey ] + ! isAttributeEqual( + block.attributes[ attributeKey ], + defaultValues[ blockId ][ attributeKey ] + ) ) { overrides[ blockId ] ??= {}; // TODO: We need a way to represent `undefined` in the serialized overrides. diff --git a/packages/editor/src/hooks/pattern-partial-syncing.js b/packages/editor/src/hooks/pattern-partial-syncing.js index a940890dfa693..2b5ef3c183c3c 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 ); @@ -57,10 +58,51 @@ const withPartialSyncingControls = createHigherOrderComponent( } ); +const withResetPatternOverrides = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const bindings = props.attributes.metadata?.bindings; + const hasPatternBindings = + !! bindings && + Object.values( bindings ).some( + ( binding ) => binding.source?.name === 'pattern_attributes' + ); + const isEditingPattern = useSelect( + ( select ) => + select( editorStore ).getCurrentPostType() === + PATTERN_TYPES.user, + [] + ); + + const shouldShowResetOverridesControl = + props.isSelected && + ! isEditingPattern && + !! props.attributes.metadata?.id && + hasPatternBindings && + Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes( + props.name + ); + + return ( + <> + + { shouldShowResetOverridesControl && ( + + ) } + + ); + } +); + if ( window.__experimentalPatternPartialSyncing ) { addFilter( 'editor.BlockEdit', 'core/editor/with-partial-syncing-controls', withPartialSyncingControls ); + + addFilter( + 'editor.BlockEdit', + 'core/editor/with-reset-pattern-overrides', + withResetPatternOverrides + ); } 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,