Skip to content

Commit

Permalink
Fix nested pattern overrides and disable editing inner pattern (WordP…
Browse files Browse the repository at this point in the history
…ress#58541)

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();
} );
} );

0 comments on commit 1fb5110

Please sign in to comment.