Skip to content

Commit

Permalink
Add a control per block to reset pattern overrides (#57907)
Browse files Browse the repository at this point in the history
* Add a control per block to reset pattern overrides

* Reword the control

* Update code to check for new block condensed bindings format

* Fix the name field

* Add e2e test

* Fix nested block

---------

Co-authored-by: kevin940726 <kevin940726@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>
Co-authored-by: glendaviesnz <glendaviesnz@git.wordpress.org>
  • Loading branch information
4 people authored Feb 7, 2024
1 parent c9568e2 commit 0d2f5e9
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 6 deletions.
17 changes: 15 additions & 2 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string, { values: Record<string, unknown>}>} */
const content = {};
Expand All @@ -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 ] =
Expand Down
27 changes: 23 additions & 4 deletions packages/editor/src/hooks/pattern-partial-syncing.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { unlock } from '../lock-unlock';

const {
PartialSyncingControls,
ResetOverridesControl,
PATTERN_TYPES,
PARTIAL_SYNCING_SUPPORTED_BLOCKS,
} = unlock( patternsPrivateApis );
Expand Down Expand Up @@ -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 === 'core/pattern-overrides'
);

const shouldShowPartialSyncingControls =
isEditingPattern && blockEditingMode === 'default';
const shouldShowResetOverridesControl =
! isEditingPattern &&
!! props.attributes.metadata?.id &&
blockEditingMode !== 'disabled' &&
hasPatternBindings;

return (
isEditingPattern &&
blockEditingMode === 'default' && (
<PartialSyncingControls { ...props } />
)
<>
{ shouldShowPartialSyncingControls && (
<PartialSyncingControls { ...props } />
) }
{ shouldShowResetOverridesControl && (
<ResetOverridesControl { ...props } />
) }
</>
);
}

Expand Down
78 changes: 78 additions & 0 deletions packages/patterns/src/components/reset-overrides-control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* 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 ) {
for ( const block of blocks ) {
if ( block.attributes.metadata?.id === id ) {
return block;
}

const found = recursivelyFindBlockWithId( block.innerBlocks, id );
if ( found ) {
return found;
}
}
}

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.content?.[ 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 (
<BlockControls group="other">
<ToolbarGroup>
<ToolbarButton
onClick={ resetOverrides }
disabled={ ! patternWithOverrides }
__experimentalIsFocusable
>
{ __( 'Reset' ) }
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
);
}
2 changes: 2 additions & 0 deletions packages/patterns/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,6 +34,7 @@ lock( privateApis, {
PatternsMenuItems,
RenamePatternCategoryModal,
PartialSyncingControls,
ResetOverridesControl,
PATTERN_TYPES,
PATTERN_DEFAULT_CATEGORY,
PATTERN_USER_CATEGORY,
Expand Down
91 changes: 91 additions & 0 deletions test/e2e/specs/editor/various/pattern-overrides.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<!-- wp:heading {"metadata":{"id":"${ headingId }","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
<h2 class="wp-block-heading">Heading</h2>
<!-- /wp:heading -->
<!-- wp:paragraph {"metadata":{"id":"${ paragraphId }","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
<p>Paragraph</p>
<!-- /wp: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();
} );
} );

1 comment on commit 0d2f5e9

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 0d2f5e9.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/7810212394
📝 Reported issues:

Please sign in to comment.