Skip to content

Commit

Permalink
Allow template part editing in write mode (WordPress#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

0 comments on commit c3ca59b

Please sign in to comment.