Skip to content

Commit

Permalink
TemplateContentPanel: Don't show content blocks that are in a Query L…
Browse files Browse the repository at this point in the history
…oop (WordPress#63732)

* TemplateContentPanel: Don't show content blocks that are in a Query Loop

* Make getBlocksByName private

* No need to rename getBlockName

* Use createSelector

* Add tests

* Call applyFilters within useMemo consistently
  • Loading branch information
noisysocks authored Jul 25, 2024
1 parent 69899cb commit ddbfee3
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,46 @@
*/
import { useSelect, useRegistry } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { useEffect } from '@wordpress/element';
import { useEffect, useMemo } from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks';

const DEFAULT_CONTENT_ONLY_BLOCKS = [
/**
* Internal dependencies
*/
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';

const POST_CONTENT_BLOCK_TYPES = [
'core/post-title',
'core/post-featured-image',
'core/post-content',
'core/template-part',
];

/**
* Component that when rendered, makes it so that the site editor allows only
* page content to be edited.
*/
export default function DisableNonPageContentBlocks() {
const contentOnlyBlocks = applyFilters(
'editor.postContentBlockTypes',
DEFAULT_CONTENT_ONLY_BLOCKS
const contentOnlyBlockTypes = useMemo(
() => [
...applyFilters(
'editor.postContentBlockTypes',
POST_CONTENT_BLOCK_TYPES
),
'core/template-part',
],
[]
);

// Note that there are two separate subscription because the result for each
// Note that there are two separate subscriptions because the result for each
// returns a new array.
const contentOnlyIds = useSelect( ( select ) => {
const { getBlocksByName, getBlockParents, getBlockName } =
select( blockEditorStore );
return getBlocksByName( contentOnlyBlocks ).filter( ( clientId ) =>
getBlockParents( clientId ).every( ( parentClientId ) => {
const parentBlockName = getBlockName( parentClientId );
return (
// Ignore descendents of the query block.
parentBlockName !== 'core/query' &&
// Enable only the top-most block.
! contentOnlyBlocks.includes( parentBlockName )
);
} )
);
}, [] );
const contentOnlyIds = useSelect(
( select ) => {
const { getPostBlocksByName } = unlock( select( editorStore ) );
return getPostBlocksByName( contentOnlyBlockTypes );
},
[ contentOnlyBlockTypes ]
);
const disabledIds = useSelect( ( select ) => {
const { getBlocksByName, getBlockOrder } = select( blockEditorStore );
return getBlocksByName( [ 'core/template-part' ] ).flatMap(
Expand Down
57 changes: 36 additions & 21 deletions packages/editor/src/components/template-content-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import {
store as blockEditorStore,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { store as interfaceStore } from '@wordpress/interface';
import { applyFilters } from '@wordpress/hooks';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -19,30 +18,46 @@ import { store as editorStore } from '../../store';

const { BlockQuickNavigation } = unlock( blockEditorPrivateApis );

const PAGE_CONTENT_BLOCKS = [
'core/post-content',
'core/post-featured-image',
const POST_CONTENT_BLOCK_TYPES = [
'core/post-title',
'core/post-featured-image',
'core/post-content',
];

const TEMPLATE_PART_BLOCK = 'core/template-part';

export default function TemplateContentPanel() {
const { enableComplementaryArea } = useDispatch( interfaceStore );
const { clientIds, postType, renderingMode } = useSelect( ( select ) => {
const { getBlocksByName } = select( blockEditorStore );
const { getCurrentPostType } = select( editorStore );
const _postType = getCurrentPostType();
return {
postType: _postType,
clientIds: getBlocksByName(
TEMPLATE_POST_TYPE === _postType
? TEMPLATE_PART_BLOCK
: PAGE_CONTENT_BLOCKS
const postContentBlockTypes = useMemo(
() =>
applyFilters(
'editor.postContentBlockTypes',
POST_CONTENT_BLOCK_TYPES
),
renderingMode: select( editorStore ).getRenderingMode(),
};
}, [] );
[]
);

const { clientIds, postType, renderingMode } = useSelect(
( select ) => {
const {
getCurrentPostType,
getPostBlocksByName,
getRenderingMode,
} = unlock( select( editorStore ) );
const _postType = getCurrentPostType();
return {
postType: _postType,
clientIds: getPostBlocksByName(
TEMPLATE_POST_TYPE === _postType
? TEMPLATE_PART_BLOCK
: postContentBlockTypes
),
renderingMode: getRenderingMode(),
};
},
[ postContentBlockTypes ]
);

const { enableComplementaryArea } = useDispatch( interfaceStore );

if ( renderingMode === 'post-only' && postType !== TEMPLATE_POST_TYPE ) {
return null;
Expand Down
33 changes: 33 additions & 0 deletions packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,36 @@ export const hasPostMetaChanges = createRegistrySelector(
export function getEntityActions( state, ...args ) {
return _getEntityActions( state.dataviews, ...args );
}

/**
* Similar to getBlocksByName in @wordpress/block-editor, but only returns the top-most
* blocks that aren't descendants of the query block.
*
* @param {Object} state Global application state.
* @param {Array|string} blockNames Block names of the blocks to retrieve.
*
* @return {Array} Block client IDs.
*/
export const getPostBlocksByName = createRegistrySelector( ( select ) =>
createSelector(
( state, blockNames ) => {
blockNames = Array.isArray( blockNames )
? blockNames
: [ blockNames ];
const { getBlocksByName, getBlockParents, getBlockName } =
select( blockEditorStore );
return getBlocksByName( blockNames ).filter( ( clientId ) =>
getBlockParents( clientId ).every( ( parentClientId ) => {
const parentBlockName = getBlockName( parentClientId );
return (
// Ignore descendents of the query block.
parentBlockName !== 'core/query' &&
// Enable only the top-most block.
! blockNames.includes( parentBlockName )
);
} )
);
},
() => [ select( blockEditorStore ).getBlocks() ]
)
);
78 changes: 78 additions & 0 deletions packages/editor/src/store/test/private-selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Internal dependencies
*/
import { getPostBlocksByName } from '../private-selectors';

describe( 'getPostBlocksByName', () => {
const state = {
blocks: {
byClientId: new Map( [
[ 'block1', { name: 'core/paragraph' } ],
[ 'block2', { name: 'core/heading' } ],
[ 'block3', { name: 'core/paragraph' } ],
[ 'block4', { name: 'core/query' } ],
[ 'block5', { name: 'core/paragraph' } ],
[ 'block6', { name: 'core/heading' } ],
] ),
order: new Map( [
[ '', [ 'block1', 'block2', 'block3', 'block4' ] ],
[ 'block4', [ 'block5', 'block6' ] ],
] ),
parents: new Map( [
[ 'block1', '' ],
[ 'block2', '' ],
[ 'block3', '' ],
[ 'block4', '' ],
[ 'block5', 'block4' ],
[ 'block6', 'block4' ],
] ),
},
};

getPostBlocksByName.registry = {
select: () => ( {
getBlocksByName: ( blockNames ) =>
Array.from( state.blocks.byClientId.keys() ).filter(
( clientId ) =>
blockNames.includes(
state.blocks.byClientId.get( clientId ).name
)
),
getBlockParents: ( clientId ) => {
const parents = [];
let parent = state.blocks.parents.get( clientId );
while ( parent ) {
parents.push( parent );
parent = state.blocks.parents.get( parent );
}
return parents;
},
getBlockName: ( clientId ) =>
state.blocks.byClientId.get( clientId ).name,
getBlocks: () => [],
} ),
};

it( 'should return top-level blocks of the specified name', () => {
const result = getPostBlocksByName( state, 'core/paragraph' );
expect( result ).toEqual( [ 'block1', 'block3' ] );
} );

it( 'should return an empty array if no blocks match', () => {
const result = getPostBlocksByName( state, 'core/non-existent' );
expect( result ).toEqual( [] );
} );

it( 'should ignore blocks inside a query block', () => {
const result = getPostBlocksByName( state, 'core/paragraph' );
expect( result ).toEqual( [ 'block1', 'block3' ] );
} );

it( 'should handle multiple block names', () => {
const result = getPostBlocksByName( state, [
'core/paragraph',
'core/heading',
] );
expect( result ).toEqual( [ 'block1', 'block2', 'block3' ] );
} );
} );

0 comments on commit ddbfee3

Please sign in to comment.