diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 2f0fa70d616fd9..5db4514ee7ede9 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2131,6 +2131,170 @@ const combinedReducers = combineReducers( { zoomLevel, } ); +function recurseBlocks( _blocks, callback ) { + if ( ! _blocks?.length ) { + return; + } + + for ( const block of _blocks ) { + callback( block ); + recurseBlocks( block?.innerBlocks, callback ); + } +} + +function hasParentWithName( state, clientId, name ) { + let parent = state.blocks.parents.get( clientId ); + while ( parent ) { + if ( state.blocks.byClientId.get( parent ).name === name ) { + return true; + } + parent = state.blocks.parents.get( parent ); + } + return false; +} + +function getPatternBlockEditingModes( state ) { + if ( ! state.patternClientIds?.size ) { + return new Map(); + } + + const patternBlockEditingModes = new Map(); + for ( const clientId of state.patternClientIds ) { + // The pattern block is a controlled block, so the inner blocks are stored + // under a special key in the tree. + // Fallback to the normal key if the special key doesn't exist, as it will + // still allow adding the editing mode of the pattern block itself. + const patternTree = + state.blocks.tree.get( `controlled||${ clientId }` ) ?? + state.blocks.tree.get( clientId ); + + if ( hasParentWithName( state, clientId, 'core/block' ) ) { + // This is a nested pattern block, it should be set to disabled, + // along with all its child blocks. + patternBlockEditingModes.set( clientId, 'disabled' ); + recurseBlocks( patternTree?.innerBlocks, ( block ) => { + patternBlockEditingModes.set( block.clientId, 'disabled' ); + } ); + } else { + // Set the parent pattern block to contentOnly. + patternBlockEditingModes.set( clientId, 'contentOnly' ); + recurseBlocks( patternTree?.innerBlocks, ( block ) => { + // If an inner block has bindings, it should be set to contentOnly. + // Else it should be set to disabled. + if ( + block?.attributes?.metadata?.bindings && + Object.keys( block?.attributes?.metadata?.bindings ).length + ) { + patternBlockEditingModes.set( + block.clientId, + 'contentOnly' + ); + } else { + patternBlockEditingModes.set( block.clientId, 'disabled' ); + } + } ); + } + } + return patternBlockEditingModes; +} + +const withPatternBlockEditingModes = ( reducer ) => { + return ( state, action ) => { + const nextState = reducer( state, action ); + + if ( nextState === state ) { + return state; + } + + nextState.patternClientIds = state?.patternClientIds ?? new Set(); + nextState.patternBlockEditingModes = + state?.patternBlockEditingModes ?? new Map(); + + switch ( action.type ) { + case 'RESET_BLOCKS': { + nextState.patternClientIds = new Set(); + recurseBlocks( action.blocks, ( block ) => { + if ( block.name === 'core/block' ) { + nextState.patternClientIds.add( block.clientId ); + } + } ); + nextState.patternBlockEditingModes = + getPatternBlockEditingModes( nextState ); + break; + } + case 'INSERT_BLOCKS': { + recurseBlocks( action.blocks, ( block ) => { + if ( block.name === 'core/block' ) { + nextState.patternClientIds.add( block.clientId ); + } + } ); + nextState.patternBlockEditingModes = + getPatternBlockEditingModes( nextState ); + break; + } + case 'REMOVE_BLOCKS': { + for ( const removedClientId of action.clientIds ) { + const removedTree = + state.blocks.tree.get( + `controlled||${ removedClientId }` + ) ?? state.blocks.tree.get( removedClientId ); + if ( removedTree ) { + recurseBlocks( [ removedTree ], ( block ) => { + nextState.patternClientIds.delete( block.clientId ); + } ); + } + } + nextState.patternBlockEditingModes = + getPatternBlockEditingModes( nextState ); + break; + } + case 'REPLACE_INNER_BLOCKS': { + const rootTree = + state.blocks.tree.get( + `controlled||${ action.rootClientId }` + ) ?? state.blocks.tree.get( action.rootClientId ); + + if ( rootTree ) { + recurseBlocks( rootTree?.innerBlocks, ( block ) => { + nextState.patternClientIds.delete( block.clientId ); + } ); + } + recurseBlocks( action.blocks, ( block ) => { + if ( block.name === 'core/block' ) { + nextState.patternClientIds.add( block.clientId ); + } + } ); + nextState.patternBlockEditingModes = + getPatternBlockEditingModes( nextState ); + break; + } + case 'REPLACE_BLOCKS': { + for ( const removedClientId of action.clientIds ) { + const removedTree = + state.blocks.tree.get( + `controlled||${ removedClientId }` + ) ?? state.blocks.tree.get( removedClientId ); + if ( removedTree ) { + recurseBlocks( [ removedTree ], ( block ) => { + nextState.patternClientIds.delete( block.clientId ); + } ); + } + } + recurseBlocks( action.blocks, ( block ) => { + if ( block.name === 'core/block' ) { + nextState.patternClientIds.add( block.clientId ); + } + } ); + nextState.patternBlockEditingModes = + getPatternBlockEditingModes( nextState ); + break; + } + } + + return nextState; + }; +}; + function withAutomaticChangeReset( reducer ) { return ( state, action ) => { const nextState = reducer( state, action ); @@ -2184,4 +2348,6 @@ function withAutomaticChangeReset( reducer ) { }; } -export default withAutomaticChangeReset( combinedReducers ); +export default withPatternBlockEditingModes( + withAutomaticChangeReset( combinedReducers ) +); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 598b6b4ea480de..5725cd6b4a25a6 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -3071,6 +3071,10 @@ export const getBlockEditingMode = createRegistrySelector( return 'disabled'; } + if ( state?.patternBlockEditingModes?.has( clientId ) ) { + return state?.patternBlockEditingModes.get( clientId ); + } + const editorMode = __unstableGetEditorMode( state ); if ( editorMode === 'navigation' ) { const sectionRootClientId = getSectionRootClientId( state ); diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 104b07157cba74..3d4d07e52b386a 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useRef, useMemo, useEffect } from '@wordpress/element'; +import { useRef, useMemo } from '@wordpress/element'; import { useEntityRecord, store as coreStore, @@ -37,12 +37,10 @@ import { getBlockBindingsSource } from '@wordpress/blocks'; /** * Internal dependencies */ -import { name as patternBlockName } from './index'; import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); -const { isOverridableBlock, hasOverridableBlocks } = - unlock( patternsPrivateApis ); +const { hasOverridableBlocks } = unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; @@ -75,22 +73,6 @@ const useInferredLayout = ( blocks, parentLayout ) => { }, [ blocks, parentLayout ] ); }; -function setBlockEditMode( setEditMode, blocks, mode ) { - blocks.forEach( ( block ) => { - const editMode = - mode || - ( isOverridableBlock( block ) ? 'contentOnly' : 'disabled' ); - setEditMode( block.clientId, editMode ); - - setBlockEditMode( - setEditMode, - block.innerBlocks, - // Disable editing for nested patterns. - block.name === patternBlockName ? 'disabled' : mode - ); - } ); -} - function RecursionWarning() { const blockProps = useBlockProps(); return ( @@ -171,7 +153,6 @@ function ReusableBlockEdit( { name, attributes: { ref, content }, __unstableParentLayout: parentLayout, - clientId: patternClientId, setAttributes, } ) { const { record, hasResolved } = useEntityRecord( @@ -184,49 +165,24 @@ function ReusableBlockEdit( { } ); const isMissing = hasResolved && ! record; - const { setBlockEditingMode, __unstableMarkLastChangeAsPersistent } = + const { __unstableMarkLastChangeAsPersistent } = useDispatch( blockEditorStore ); - const { - innerBlocks, - onNavigateToEntityRecord, - editingMode, - hasPatternOverridesSource, - } = useSelect( + const { onNavigateToEntityRecord, hasPatternOverridesSource } = useSelect( ( select ) => { - const { getBlocks, getSettings, getBlockEditingMode } = - select( blockEditorStore ); + const { getSettings } = select( blockEditorStore ); // For editing link to the site editor if the theme and user permissions support it. return { - innerBlocks: getBlocks( patternClientId ), onNavigateToEntityRecord: getSettings().onNavigateToEntityRecord, - editingMode: getBlockEditingMode( patternClientId ), hasPatternOverridesSource: !! getBlockBindingsSource( 'core/pattern-overrides' ), }; }, - [ patternClientId ] + [] ); - // 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' || ! hasPatternOverridesSource - ? 'disabled' - : undefined - ); - }, [ - editingMode, - innerBlocks, - setBlockEditingMode, - hasPatternOverridesSource, - ] ); - const canOverrideBlocks = useMemo( () => hasPatternOverridesSource && hasOverridableBlocks( blocks ), [ hasPatternOverridesSource, blocks ] @@ -244,7 +200,6 @@ function ReusableBlockEdit( { } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { - templateLock: 'all', layout, value: blocks, onInput: NOOP,