Skip to content

Commit

Permalink
Allow template part editing in write mode (#67372)
Browse files Browse the repository at this point in the history
* Allow template part editing in write mode

* Update unit tests for template parts

* Refine block editing mode calculation - allow editing of inner template part content blocks

* Update tests for content blocks in template parts

* Add content role to site logo block

* Consider template parts a section in navigation mode

* Allow content blocks to be explicitly disabled even when they have a derived mode

First iteration of handling disabled blocks as derived block editing modes

* Avoid showing template parts when edting a page in Write Mode

* entries().some is not widely available, so switch to Array.from().some

* Update test to show explicitly disabled block

* Update unit tests

* Remove unused argument

* Add optimization for disabled blocks calculation

* Avoid state updates when setting block editor modes to the same thing as they already are

* Update DisableNonPageContentBlocks to not set block editor modes on all blocks at once

* Try breaking DisableNonPageContentBlocks into separate useEffect calls

----

Unlinked contributors: xavier-lc.

Co-authored-by: talldan <[email protected]>
Co-authored-by: getdave <[email protected]>
Co-authored-by: draganescu <[email protected]>
Co-authored-by: tellthemachines <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: ramonjd <[email protected]>
Co-authored-by: annezazu <[email protected]>
Co-authored-by: richtabor <[email protected]>
Co-authored-by: ntsekouras <[email protected]>
  • Loading branch information
10 people authored Dec 24, 2024
1 parent cdace81 commit c3ca59b
Show file tree
Hide file tree
Showing 15 changed files with 482 additions and 230 deletions.
20 changes: 15 additions & 5 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,13 +502,23 @@ export const getParentSectionBlock = ( state, clientId ) => {
* @return {boolean} Whether the block is a content locking parent.
*/
export function isSectionBlock( state, clientId ) {
const blockName = getBlockName( state, clientId );
if (
blockName === 'core/block' ||
getTemplateLock( state, clientId ) === 'contentOnly'
) {
return true;
}

// Template parts become sections in navigation mode.
const _isNavigationMode = isNavigationMode( state );
if ( _isNavigationMode && blockName === 'core/template-part' ) {
return true;
}

const sectionRootClientId = getSectionRootClientId( state );
const sectionClientIds = getBlockOrder( state, sectionRootClientId );
return (
getBlockName( state, clientId ) === 'core/block' ||
getTemplateLock( state, clientId ) === 'contentOnly' ||
( isNavigationMode( state ) && sectionClientIds.includes( clientId ) )
);
return _isNavigationMode && sectionClientIds.includes( clientId );
}

/**
Expand Down
131 changes: 112 additions & 19 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1964,8 +1964,14 @@ export function temporarilyEditingFocusModeRevert( state = '', action ) {
export function blockEditingModes( state = new Map(), action ) {
switch ( action.type ) {
case 'SET_BLOCK_EDITING_MODE':
if ( state.get( action.clientId ) === action.mode ) {
return state;
}
return new Map( state ).set( action.clientId, action.mode );
case 'UNSET_BLOCK_EDITING_MODE': {
if ( ! state.has( action.clientId ) ) {
return state;
}
const newState = new Map( state );
newState.delete( action.clientId );
return newState;
Expand Down Expand Up @@ -2186,19 +2192,19 @@ function getBlockTreeBlock( state, clientId ) {
* The callback receives the current block as its argument.
*/
function traverseBlockTree( state, clientId, callback ) {
const parentTree = getBlockTreeBlock( state, clientId );
if ( ! parentTree ) {
const tree = getBlockTreeBlock( state, clientId );
if ( ! tree ) {
return;
}

callback( parentTree );
callback( tree );

if ( ! parentTree?.innerBlocks?.length ) {
if ( ! tree?.innerBlocks?.length ) {
return;
}

for ( const block of parentTree?.innerBlocks ) {
traverseBlockTree( state, block.clientId, callback );
for ( const innerBlock of tree?.innerBlocks ) {
traverseBlockTree( state, innerBlock.clientId, callback );
}
}

Expand All @@ -2212,8 +2218,12 @@ function traverseBlockTree( state, clientId, callback ) {
* @return {string|undefined} The client ID of the parent block if found, undefined otherwise.
*/
function findParentInClientIdsList( state, clientId, clientIds ) {
if ( ! clientIds.length ) {
return;
}

let parent = state.blocks.parents.get( clientId );
while ( parent ) {
while ( parent !== undefined ) {
if ( clientIds.includes( parent ) ) {
return parent;
}
Expand Down Expand Up @@ -2258,15 +2268,65 @@ function getDerivedBlockEditingModesForTree(
// so the default block editing mode is set to disabled.
const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ];
const sectionClientIds = state.blocks.order.get( sectionRootClientId );
const syncedPatternClientIds = Object.keys(
state.blocks.controlledInnerBlocks
).filter(
( clientId ) =>
state.blocks.byClientId?.get( clientId )?.name === 'core/block'
const hasDisabledBlocks = Array.from( state.blockEditingModes ).some(
( [ , mode ] ) => mode === 'disabled'
);
const templatePartClientIds = [];
const syncedPatternClientIds = [];

Object.keys( state.blocks.controlledInnerBlocks ).forEach( ( clientId ) => {
const block = state.blocks.byClientId?.get( clientId );

if ( block?.name === 'core/template-part' ) {
templatePartClientIds.push( clientId );
}

if ( block?.name === 'core/block' ) {
syncedPatternClientIds.push( clientId );
}
} );

traverseBlockTree( state, treeClientId, ( block ) => {
const { clientId, name: blockName } = block;

// If the block already has an explicit block editing mode set,
// don't override it.
if ( state.blockEditingModes.has( clientId ) ) {
return;
}

// Disabled explicit block editing modes are inherited by children.
// It's an expensive calculation, so only do it if there are disabled blocks.
if ( hasDisabledBlocks ) {
// Look through parents to find one with an explicit block editing mode.
let ancestorBlockEditingMode;
let parent = state.blocks.parents.get( clientId );
while ( parent !== undefined ) {
// There's a chance we only just calculated this for the parent,
// if so we can return that value for a faster lookup.
if ( derivedBlockEditingModes.has( parent ) ) {
ancestorBlockEditingMode =
derivedBlockEditingModes.get( parent );
} else if ( state.blockEditingModes.has( parent ) ) {
// Checking the explicit block editing mode will be slower,
// as the block editing mode is more likely to be set on a
// distant ancestor.
ancestorBlockEditingMode =
state.blockEditingModes.get( parent );
}
if ( ancestorBlockEditingMode ) {
break;
}
parent = state.blocks.parents.get( parent );
}

// If the ancestor block editing mode is disabled, it's inherited by the child.
if ( ancestorBlockEditingMode === 'disabled' ) {
derivedBlockEditingModes.set( clientId, 'disabled' );
return;
}
}

if ( isZoomedOut || isNavMode ) {
// If the root block is the section root set its editing mode to contentOnly.
if ( clientId === sectionRootClientId ) {
Expand All @@ -2287,15 +2347,41 @@ function getDerivedBlockEditingModesForTree(

// If zoomed out, all blocks that aren't sections or the section root are
// disabled.
// If the tree root is not in a section, set its editing mode to disabled.
if (
isZoomedOut ||
! findParentInClientIdsList( state, clientId, sectionClientIds )
) {
if ( isZoomedOut ) {
derivedBlockEditingModes.set( clientId, 'disabled' );
return;
}

const isInSection = !! findParentInClientIdsList(
state,
clientId,
sectionClientIds
);
if ( ! isInSection ) {
if ( clientId === '' ) {
derivedBlockEditingModes.set( clientId, 'disabled' );
return;
}

// Allow selection of template parts outside of sections.
if ( blockName === 'core/template-part' ) {
derivedBlockEditingModes.set( clientId, 'contentOnly' );
return;
}

const isInTemplatePart = !! findParentInClientIdsList(
state,
clientId,
templatePartClientIds
);
// Allow contentOnly blocks in template parts outside of sections
// to be editable. Only disable blocks that don't fit this criteria.
if ( ! isInTemplatePart && ! isContentBlock( blockName ) ) {
derivedBlockEditingModes.set( clientId, 'disabled' );
return;
}
}

// Handle synced pattern content so the inner blocks of a synced pattern are
// properly disabled.
if ( syncedPatternClientIds.length ) {
Expand Down Expand Up @@ -2560,11 +2646,16 @@ export function withDerivedBlockEditingModes( reducer ) {
}
break;
}
case 'SET_BLOCK_EDITING_MODE':
case 'UNSET_BLOCK_EDITING_MODE':
case 'SET_HAS_CONTROLLED_INNER_BLOCKS': {
const updatedBlock = nextState.blocks.tree.get(
const updatedBlock = getBlockTreeBlock(
nextState,
action.clientId
);
// The block might have been removed.

// The block might have been removed in which case it'll be
// handled by the `REMOVE_BLOCKS` action.
if ( ! updatedBlock ) {
break;
}
Expand All @@ -2573,13 +2664,15 @@ export function withDerivedBlockEditingModes( reducer ) {
getDerivedBlockEditingModesUpdates( {
prevState: state,
nextState,
removedClientIds: [ action.clientId ],
addedBlocks: [ updatedBlock ],
isNavMode: false,
} );
const nextDerivedNavModeBlockEditingModes =
getDerivedBlockEditingModesUpdates( {
prevState: state,
nextState,
removedClientIds: [ action.clientId ],
addedBlocks: [ updatedBlock ],
isNavMode: true,
} );
Expand Down
4 changes: 1 addition & 3 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3087,9 +3087,7 @@ export const getBlockEditingMode = createRegistrySelector(
const isContent = hasContentRoleAttribute( name );
return isContent ? 'contentOnly' : 'disabled';
}
// Otherwise, check if there's an ancestor that is contentOnly
const parentMode = getBlockEditingMode( state, rootClientId );
return parentMode === 'contentOnly' ? 'default' : parentMode;
return 'default';
}
);

Expand Down
Loading

1 comment on commit c3ca59b

@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 c3ca59b.
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/12476408957
📝 Reported issues:

Please sign in to comment.