Skip to content

Refactor: Content only template locking block editing modes to reducer #67606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ export const getEnabledClientIdsTree = createRegistrySelector( ( select ) =>
state.derivedBlockEditingModes,
state.derivedNavModeBlockEditingModes,
state.blockEditingModes,
state.settings.templateLock,
state.blockListSettings,
select( STORE_NAME ).__unstableGetEditorMode( state ),
] )
);
Expand Down
133 changes: 133 additions & 0 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,12 @@ function getDerivedBlockEditingModesForTree(
syncedPatternClientIds.push( clientId );
}
} );
const contentOnlyTemplateLockedClientIds = Object.keys(
state.blockListSettings
).filter(
( clientId ) =>
state.blockListSettings[ clientId ]?.templateLock === 'contentOnly'
);

traverseBlockTree( state, treeClientId, ( block ) => {
const { clientId, name: blockName } = block;
Expand Down Expand Up @@ -2457,6 +2463,64 @@ function getDerivedBlockEditingModesForTree(
derivedBlockEditingModes.set( clientId, 'disabled' );
}
}

// Explicitly set block editing modes take precedence over template lock.
if ( state.blockEditingModes.has( clientId ) ) {
return;
}

if ( contentOnlyTemplateLockedClientIds.length ) {
const hasContentOnlyTemplateLockedParent =
!! findParentInClientIdsList(
state,
clientId,
contentOnlyTemplateLockedClientIds
);
if ( hasContentOnlyTemplateLockedParent ) {
if ( isContentBlock( blockName ) ) {
derivedBlockEditingModes.set( clientId, 'contentOnly' );
} else {
derivedBlockEditingModes.set( clientId, 'disabled' );
}
}
}

// Disabled block editing modes cascade to all children,
// but contentOnly can override this, so this is the last
// thing to check.
if ( state.blockEditingModes.size ) {
let parent = state.blocks.parents.get( clientId );
// Look for the first parent with an explicit block editing mode.
while ( parent ) {
const parentBlockEditingMode =
state.blockEditingModes.get( parent );

// If the parent is disabled, this block should be disabled.
if ( parentBlockEditingMode === 'disabled' ) {
derivedBlockEditingModes.set( clientId, 'disabled' );
break;
}

// If the parent is contentOnly, this block should be contentOnly if it's a content block.
if ( parentBlockEditingMode === 'contentOnly' ) {
if ( isContentBlock( blockName ) ) {
derivedBlockEditingModes.set( clientId, 'contentOnly' );
} else {
derivedBlockEditingModes.set( clientId, 'disabled' );
}

break;
}

if ( parentBlockEditingMode === 'default' ) {
// If the parent is default, this block should be default.
// We can do nothing, but skip searching any further.
break;
}

parent = state.blocks.parents.get( parent );
}
}
} );

return derivedBlockEditingModes;
Expand Down Expand Up @@ -2628,6 +2692,75 @@ export function withDerivedBlockEditingModes( reducer ) {
}
break;
}
case 'UPDATE_BLOCK_LIST_SETTINGS': {
// Handle the addition and removal of contentOnly template locked blocks.
const addedBlocks = [];
const removedClientIds = [];

const updates =
typeof action.clientId === 'string'
? { [ action.clientId ]: action.settings }
: action.clientId;

for ( const clientId in updates ) {
const isNewContentOnlyBlock =
state.blockListSettings[ clientId ]?.templateLock !==
'contentOnly' &&
nextState.blockListSettings[ clientId ]
?.templateLock === 'contentOnly';

const wasContentOnlyBlock =
state.blockListSettings[ clientId ]?.templateLock ===
'contentOnly' &&
nextState.blockListSettings[ clientId ]
?.templateLock !== 'contentOnly';

if ( isNewContentOnlyBlock ) {
addedBlocks.push(
nextState.blocks.tree.get( clientId )
);
} else if ( wasContentOnlyBlock ) {
removedClientIds.push( clientId );
}
}

if ( ! addedBlocks.length && ! removedClientIds.length ) {
break;
}

const nextDerivedBlockEditingModes =
getDerivedBlockEditingModesUpdates( {
prevState: state,
nextState,
addedBlocks,
removedClientIds,
isNavMode: false,
} );
const nextDerivedNavModeBlockEditingModes =
getDerivedBlockEditingModesUpdates( {
prevState: state,
nextState,
addedBlocks,
removedClientIds,
isNavMode: true,
} );

if (
nextDerivedBlockEditingModes ||
nextDerivedNavModeBlockEditingModes
) {
return {
...nextState,
derivedBlockEditingModes:
nextDerivedBlockEditingModes ??
state.derivedBlockEditingModes,
derivedNavModeBlockEditingModes:
nextDerivedNavModeBlockEditingModes ??
state.derivedNavModeBlockEditingModes,
};
}
break;
}
case 'SET_BLOCK_EDITING_MODE':
case 'UNSET_BLOCK_EDITING_MODE':
case 'SET_HAS_CONTROLLED_INNER_BLOCKS': {
Expand Down
76 changes: 25 additions & 51 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3040,62 +3040,36 @@ export function __unstableIsWithinBlockOverlay( state, clientId ) {
* @return {BlockEditingMode} The block editing mode. One of `'disabled'`,
* `'contentOnly'`, or `'default'`.
*/
export const getBlockEditingMode = createRegistrySelector(
( select ) =>
( state, clientId = '' ) => {
// Some selectors that call this provide `null` as the default
// rootClientId, but the default rootClientId is actually `''`.
if ( clientId === null ) {
clientId = '';
}
export function getBlockEditingMode( state, clientId = '' ) {
// Some selectors that call this provide `null` as the default
// rootClientId, but the default rootClientId is actually `''`.
if ( clientId === null ) {
clientId = '';
}

const isNavMode = isNavigationMode( state );

// If the editor is currently not in navigation mode, check if the clientId
// has an editing mode set in the regular derived map.
// There may be an editing mode set here for synced patterns or in zoomed out
// mode.
if (
! isNavMode &&
state.derivedBlockEditingModes?.has( clientId )
) {
return state.derivedBlockEditingModes.get( clientId );
}
const isNavMode = isNavigationMode( state );

// If the editor *is* in navigation mode, the block editing mode states
// are stored in the derivedNavModeBlockEditingModes map.
if (
isNavMode &&
state.derivedNavModeBlockEditingModes?.has( clientId )
) {
return state.derivedNavModeBlockEditingModes.get( clientId );
}
// If the editor is currently not in navigation mode, check if the clientId
// has an editing mode set in the regular derived map.
// There may be an editing mode set here for synced patterns or in zoomed out
// mode.
if ( ! isNavMode && state.derivedBlockEditingModes?.has( clientId ) ) {
return state.derivedBlockEditingModes.get( clientId );
}

// In normal mode, consider that an explicitly set editing mode takes over.
const blockEditingMode = state.blockEditingModes.get( clientId );
if ( blockEditingMode ) {
return blockEditingMode;
}
// If the editor *is* in navigation mode, the block editing mode states
// are stored in the derivedNavModeBlockEditingModes map.
if ( isNavMode && state.derivedNavModeBlockEditingModes?.has( clientId ) ) {
return state.derivedNavModeBlockEditingModes.get( clientId );
}

// In normal mode, top level is default mode.
if ( clientId === '' ) {
return 'default';
}
// In normal mode, consider that an explicitly set editing mode takes over.
if ( state.blockEditingModes.has( clientId ) ) {
return state.blockEditingModes.get( clientId );
}

const rootClientId = getBlockRootClientId( state, clientId );
const templateLock = getTemplateLock( state, rootClientId );
// If the parent of the block is contentOnly locked, check whether it's a content block.
if ( templateLock === 'contentOnly' ) {
const name = getBlockName( state, clientId );
const { hasContentRoleAttribute } = unlock(
select( blocksStore )
);
const isContent = hasContentRoleAttribute( name );
return isContent ? 'contentOnly' : 'disabled';
}
return 'default';
}
);
return 'default';
}

/**
* Indicates if a block is ungroupable.
Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/src/store/test/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,10 @@
[ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ],
[ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ],
] ),
derivedBlockEditingModes: new Map( [
[ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ],
[ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ],
] ),
blockListSettings: {},
};
expect(
Expand All @@ -511,7 +515,7 @@
state,
'4c2b7140-fffd-44b4-b2a7-820c670a6514'
)
).toEqual( [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' ] );

Check failure on line 518 in packages/block-editor/src/store/test/private-selectors.js

View workflow job for this annotation

GitHub Actions / JavaScript (Node.js 22) 1/4

Error: expect(received).toEqual(expected) // deep equality - Expected - 3 + Received + 1 - Array [ - "e178812d-ce5e-48c7-a945-8ae4ffcbbb7c", - ] + Array [] at Object.toEqual (/home/runner/work/gutenberg/gutenberg/packages/block-editor/src/store/test/private-selectors.js:518:6) at Promise.then.completed (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/utils.js:300:28) at new Promise (<anonymous>) at callAsyncCircusFn (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/utils.js:233:10) at _callCircusTest (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:315:40) at processTicksAndRejections (node:internal/process/task_queues:105:5) at _runTest (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:251:3) at _runTestsForDescribeBlock (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:125:9) at _runTestsForDescribeBlock (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:120:9) at _runTestsForDescribeBlock (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:120:9) at run (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:70:3) at runAndTransformResultsToJestFormat (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) at jestAdapter (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) at runTestInternal (/home/runner/work/gutenberg/gutenberg/node_modules/jest-runner/build/runTest.js:367:16) at runTest (/home/runner/work/gutenberg/gutenberg/node_modules/jest-runner/build/runTest.js:444:34) at Object.worker (/home/runner/work/gutenberg/gutenberg/node_modules/jest-runner/build/testWorker.js:106:12)

Check failure on line 518 in packages/block-editor/src/store/test/private-selectors.js

View workflow job for this annotation

GitHub Actions / JavaScript (Node.js 20) 1/4

Error: expect(received).toEqual(expected) // deep equality - Expected - 3 + Received + 1 - Array [ - "e178812d-ce5e-48c7-a945-8ae4ffcbbb7c", - ] + Array [] at Object.toEqual (/home/runner/work/gutenberg/gutenberg/packages/block-editor/src/store/test/private-selectors.js:518:6) at Promise.then.completed (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/utils.js:300:28) at new Promise (<anonymous>) at callAsyncCircusFn (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/utils.js:233:10) at _callCircusTest (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:315:40) at processTicksAndRejections (node:internal/process/task_queues:95:5) at _runTest (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:251:3) at _runTestsForDescribeBlock (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:125:9) at _runTestsForDescribeBlock (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:120:9) at _runTestsForDescribeBlock (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:120:9) at run (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/run.js:70:3) at runAndTransformResultsToJestFormat (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) at jestAdapter (/home/runner/work/gutenberg/gutenberg/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) at runTestInternal (/home/runner/work/gutenberg/gutenberg/node_modules/jest-runner/build/runTest.js:367:16) at runTest (/home/runner/work/gutenberg/gutenberg/node_modules/jest-runner/build/runTest.js:444:34) at Object.worker (/home/runner/work/gutenberg/gutenberg/node_modules/jest-runner/build/testWorker.js:106:12)
} );

it( 'should order from bottom to top if ascending is true', () => {
Expand Down
Loading
Loading