Skip to content

Commit

Permalink
Fix nested pattern overrides and disable editing inner pattern (#58541)
Browse files Browse the repository at this point in the history
Co-authored-by: kevin940726 <[email protected]>
Co-authored-by: talldan <[email protected]>
Co-authored-by: glendaviesnz <[email protected]>

* Fix nested pattern overrides and disable editing inner pattern

* Address code reviews
  • Loading branch information
kevin940726 authored Feb 6, 2024
1 parent 519824a commit 1fb5110
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 31 deletions.
79 changes: 49 additions & 30 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { parse, cloneBlock } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { name as patternBlockName } from './index';
import { unlock } from '../lock-unlock';

const { useLayoutClasses } = unlock( blockEditorPrivateApis );
Expand Down Expand Up @@ -134,6 +135,7 @@ function getContentValuesFromInnerBlocks( blocks, defaultValues ) {
/** @type {Record<string, { values: Record<string, unknown>}>} */
const content = {};
for ( const block of blocks ) {
if ( block.name === patternBlockName ) continue;
Object.assign(
content,
getContentValuesFromInnerBlocks( block.innerBlocks, defaultValues )
Expand Down Expand Up @@ -166,7 +168,13 @@ function setBlockEditMode( setEditMode, blocks, mode ) {
mode ||
( hasOverridableAttributes( block ) ? 'contentOnly' : 'disabled' );
setEditMode( block.clientId, editMode );
setBlockEditMode( setEditMode, block.innerBlocks, mode );

setBlockEditMode(
setEditMode,
block.innerBlocks,
// Disable editing for nested patterns.
block.name === patternBlockName ? 'disabled' : mode
);
} );
}

Expand Down Expand Up @@ -200,28 +208,34 @@ export default function ReusableBlockEdit( {
} = useDispatch( blockEditorStore );
const { syncDerivedUpdates } = unlock( useDispatch( blockEditorStore ) );

const { innerBlocks, userCanEdit, getBlockEditingMode, getPostLinkProps } =
useSelect(
( select ) => {
const { canUser } = select( coreStore );
const {
getBlocks,
getBlockEditingMode: editingMode,
getSettings,
} = select( blockEditorStore );
const blocks = getBlocks( patternClientId );
const canEdit = canUser( 'update', 'blocks', ref );

// For editing link to the site editor if the theme and user permissions support it.
return {
innerBlocks: blocks,
userCanEdit: canEdit,
getBlockEditingMode: editingMode,
getPostLinkProps: getSettings().getPostLinkProps,
};
},
[ patternClientId, ref ]
);
const {
innerBlocks,
userCanEdit,
getBlockEditingMode,
getPostLinkProps,
editingMode,
} = useSelect(
( select ) => {
const { canUser } = select( coreStore );
const {
getBlocks,
getSettings,
getBlockEditingMode: _getBlockEditingMode,
} = select( blockEditorStore );
const blocks = getBlocks( patternClientId );
const canEdit = canUser( 'update', 'blocks', ref );

// For editing link to the site editor if the theme and user permissions support it.
return {
innerBlocks: blocks,
userCanEdit: canEdit,
getBlockEditingMode: _getBlockEditingMode,
getPostLinkProps: getSettings().getPostLinkProps,
editingMode: _getBlockEditingMode( patternClientId ),
};
},
[ patternClientId, ref ]
);

const editOriginalProps = getPostLinkProps
? getPostLinkProps( {
Expand All @@ -230,10 +244,15 @@ export default function ReusableBlockEdit( {
} )
: {};

useEffect(
() => setBlockEditMode( setBlockEditingMode, innerBlocks ),
[ innerBlocks, setBlockEditingMode ]
);
// Sync the editing mode of the pattern block with the inner blocks.
useEffect( () => {
setBlockEditMode(
setBlockEditingMode,
innerBlocks,
// Disable editing if the pattern itself is disabled.
editingMode === 'disabled' ? 'disabled' : undefined
);
}, [ editingMode, innerBlocks, setBlockEditingMode ] );

const canOverrideBlocks = useMemo(
() => hasOverridableBlocks( innerBlocks ),
Expand All @@ -253,7 +272,8 @@ export default function ReusableBlockEdit( {
// Apply the initial overrides from the pattern block to the inner blocks.
useEffect( () => {
defaultContent.current = {};
const editingMode = getBlockEditingMode( patternClientId );
const originalEditingMode = getBlockEditingMode( patternClientId );
// Replace the contents of the blocks with the overrides.
registry.batch( () => {
setBlockEditingMode( patternClientId, 'default' );
syncDerivedUpdates( () => {
Expand All @@ -266,7 +286,7 @@ export default function ReusableBlockEdit( {
)
);
} );
setBlockEditingMode( patternClientId, editingMode );
setBlockEditingMode( patternClientId, originalEditingMode );
} );
}, [
__unstableMarkNextChangeAsNotPersistent,
Expand Down Expand Up @@ -323,7 +343,6 @@ export default function ReusableBlockEdit( {
}, [ syncDerivedUpdates, patternClientId, registry, setAttributes ] );

const handleEditOriginal = ( event ) => {
setBlockEditMode( setBlockEditingMode, innerBlocks, 'default' );
editOriginalProps.onClick( event );
};

Expand Down
19 changes: 18 additions & 1 deletion packages/e2e-test-utils-playwright/src/editor/get-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,28 @@ export async function getBlocks(

return await this.page.evaluate(
( [ _full, _clientId ] ) => {
// Serialize serializable attributes of blocks.
function serializeAttributes(
attributes: Record< string, unknown >
) {
return Object.fromEntries(
Object.entries( attributes ).map( ( [ key, value ] ) => {
// Serialize RichTextData to string.
if (
value instanceof window.wp.richText.RichTextData
) {
return [ key, ( value as string ).toString() ];
}
return [ key, value ];
} )
);
}

// Remove other unpredictable properties like clientId from blocks for testing purposes.
function recursivelyTransformBlocks( blocks: Block[] ): Block[] {
return blocks.map( ( block ) => ( {
name: block.name,
attributes: block.attributes,
attributes: serializeAttributes( block.attributes ),
innerBlocks: recursivelyTransformBlocks(
block.innerBlocks
),
Expand Down
119 changes: 119 additions & 0 deletions test/e2e/specs/editor/various/pattern-overrides.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,123 @@ test.describe( 'Pattern Overrides', () => {
await expect( buttonLink ).toHaveAttribute( 'target', '' );
await expect( buttonLink ).toHaveAttribute( 'rel', /^\s*nofollow\s*$/ );
} );

test( 'disables editing of nested patterns', async ( {
page,
admin,
requestUtils,
editor,
} ) => {
const paragraphId = 'paragraph-id';
const headingId = 'heading-id';
const innerPattern = await requestUtils.createBlock( {
title: 'Inner Pattern',
content: `<!-- wp:paragraph {"metadata":{"id":"${ paragraphId }","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
<p>Inner paragraph</p>
<!-- /wp:paragraph -->`,
status: 'publish',
} );
const outerPattern = await requestUtils.createBlock( {
title: 'Outer Pattern',
content: `<!-- wp:heading {"metadata":{"id":"${ headingId }","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
<h2 class="wp-block-heading">Outer heading</h2>
<!-- /wp:heading -->
<!-- wp:block {"ref":${ innerPattern.id },"overrides":{"${ paragraphId }":{"content":"Inner paragraph (edited)"}}} /-->`,
status: 'publish',
} );

await admin.createNewPost();

await editor.insertBlock( {
name: 'core/block',
attributes: { ref: outerPattern.id },
} );

// Make an edit to the outer pattern heading.
await editor.canvas
.getByRole( 'document', { name: 'Block: Heading' } )
.fill( 'Outer heading (edited)' );

const postId = await editor.publishPost();

// Check it renders correctly.
await expect.poll( editor.getBlocks ).toMatchObject( [
{
name: 'core/block',
attributes: {
ref: outerPattern.id,
content: {
[ headingId ]: {
values: { content: 'Outer heading (edited)' },
},
},
},
innerBlocks: [
{
name: 'core/heading',
attributes: { content: 'Outer heading (edited)' },
},
{
name: 'core/block',
attributes: {
ref: innerPattern.id,
content: {
[ paragraphId ]: {
values: {
content: 'Inner paragraph (edited)',
},
},
},
},
innerBlocks: [
{
name: 'core/paragraph',
attributes: {
content: 'Inner paragraph (edited)',
},
},
],
},
],
},
] );

await expect(
editor.canvas.getByRole( 'document', {
name: 'Block: Paragraph',
includeHidden: true,
} ),
'The inner paragraph should not be editable'
).toHaveAttribute( 'inert', 'true' );

// Edit the outer pattern.
await editor.selectBlocks(
editor.canvas
.getByRole( 'document', { name: 'Block: Pattern' } )
.first()
);
await editor.showBlockToolbar();
await page
.getByRole( 'toolbar', { name: 'Block tools' } )
.getByRole( 'link', { name: 'Edit original' } )
.click();

// The inner paragraph should be editable in the pattern focus mode.
await expect(
editor.canvas.getByRole( 'document', {
name: 'Block: Paragraph',
} ),
'The inner paragraph should not be editable'
).not.toHaveAttribute( 'inert', 'true' );

// Visit the post on the frontend.
await page.goto( `/?p=${ postId }` );

await expect( page.getByRole( 'heading', { level: 2 } ) ).toHaveText(
'Outer heading (edited)'
);
await expect(
page.getByText( 'Inner paragraph (edited)' )
).toBeVisible();
} );
} );

1 comment on commit 1fb5110

@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 1fb5110.
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/7798014851
📝 Reported issues:

Please sign in to comment.