diff --git a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js index f8b083d4eedf1..13dae7f2ed7c0 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js +++ b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js @@ -11,33 +11,66 @@ import { store as noticesStore } from '@wordpress/notices'; * Internal dependencies */ import { store as blockEditorStore } from '../../../store'; +import { unlock } from '../../../lock-unlock'; import { INSERTER_PATTERN_TYPES } from '../block-patterns-tab/utils'; +import { getParsedPattern } from '../../../store/utils'; /** * Retrieves the block patterns inserter state. * * @param {Function} onInsert function called when inserter a list of blocks. * @param {string=} rootClientId Insertion's root client ID. - * * @param {string} selectedCategory The selected pattern category. + * @param {boolean} isQuick For the quick inserter render only allowed patterns. + * * @return {Array} Returns the patterns state. (patterns, categories, onSelect handler) */ -const usePatternsState = ( onInsert, rootClientId, selectedCategory ) => { - const { patternCategories, patterns, userPatternCategories } = useSelect( +const usePatternsState = ( + onInsert, + rootClientId, + selectedCategory, + isQuick +) => { + const { patternCategories, allPatterns, userPatternCategories } = useSelect( ( select ) => { - const { __experimentalGetAllowedPatterns, getSettings } = - select( blockEditorStore ); + const { + getAllPatterns, + getSettings, + __experimentalGetAllowedPatterns, + } = unlock( select( blockEditorStore ) ); const { __experimentalUserPatternCategories, __experimentalBlockPatternCategories, } = getSettings(); return { - patterns: __experimentalGetAllowedPatterns( rootClientId ), + allPatterns: isQuick + ? __experimentalGetAllowedPatterns() + : getAllPatterns(), userPatternCategories: __experimentalUserPatternCategories, patternCategories: __experimentalBlockPatternCategories, }; }, - [ rootClientId ] + [ isQuick ] + ); + const { getClosestAllowedInsertionPointForPattern } = unlock( + useSelect( blockEditorStore ) + ); + + const patterns = useMemo( + () => + isQuick + ? allPatterns + : allPatterns + .filter( ( { inserter = true } ) => !! inserter ) + .map( ( pattern ) => { + return { + ...pattern, + get blocks() { + return getParsedPattern( pattern ).blocks; + }, + }; + } ), + [ isQuick, allPatterns ] ); const allCategories = useMemo( () => { @@ -58,6 +91,15 @@ const usePatternsState = ( onInsert, rootClientId, selectedCategory ) => { const { createSuccessNotice } = useDispatch( noticesStore ); const onClickPattern = useCallback( ( pattern, blocks ) => { + const destinationRootClientId = isQuick + ? rootClientId + : getClosestAllowedInsertionPointForPattern( + pattern, + rootClientId + ); + if ( destinationRootClientId === null ) { + return; + } const patternBlocks = pattern.type === INSERTER_PATTERN_TYPES.user && pattern.syncStatus !== 'unsynced' @@ -77,7 +119,9 @@ const usePatternsState = ( onInsert, rootClientId, selectedCategory ) => { } return clonedBlock; } ), - pattern.name + pattern.name, + false, + destinationRootClientId ); createSuccessNotice( sprintf( @@ -91,7 +135,14 @@ const usePatternsState = ( onInsert, rootClientId, selectedCategory ) => { } ); }, - [ createSuccessNotice, onInsert, selectedCategory ] + [ + createSuccessNotice, + onInsert, + selectedCategory, + rootClientId, + getClosestAllowedInsertionPointForPattern, + isQuick, + ] ); return [ patterns, allCategories, onClickPattern ]; diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index faf2c20514f67..4bc26196cb524 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -114,9 +114,9 @@ function InserterMenu( ); const onInsertPattern = useCallback( - ( blocks, patternName ) => { + ( blocks, patternName, ...args ) => { onToggleInsertionPoint( false ); - onInsertBlocks( blocks, { patternName } ); + onInsertBlocks( blocks, { patternName }, ...args ); onSelect(); }, [ onInsertBlocks, onSelect ] diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 4a79ad6b1f083..f40af12feddf4 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -47,10 +47,11 @@ export default function QuickInserter( { onInsertBlocks, true ); - const [ patterns ] = usePatternsState( onInsertBlocks, - destinationRootClientId + destinationRootClientId, + undefined, + true ); const { setInserterIsOpened, insertionIndex } = useSelect( diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 02a37b94ec27f..a98c5af93c86a 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -642,24 +642,30 @@ export function isZoomOut( state ) { /** * Finds the closest block where the block is allowed to be inserted. * - * @param {Object} state Editor state. - * @param {string} name Block name. - * @param {string} clientId Default insertion point. + * @param {Object} state Editor state. + * @param {string[] | string} name Block name or names. + * @param {string} clientId Default insertion point. * * @return {string} clientID of the closest container when the block name can be inserted. */ export function getClosestAllowedInsertionPoint( state, name, clientId = '' ) { + const blockNames = Array.isArray( name ) ? name : [ name ]; + const areBlockNamesAllowedInClientId = ( id ) => + blockNames.every( ( currentName ) => + canInsertBlockType( state, currentName, id ) + ); + // If we're trying to insert at the root level and it's not allowed // Try the section root instead. if ( ! clientId ) { - if ( canInsertBlockType( state, name, clientId ) ) { + if ( areBlockNamesAllowedInClientId( clientId ) ) { return clientId; } const sectionRootClientId = getSectionRootClientId( state ); if ( sectionRootClientId && - canInsertBlockType( state, name, sectionRootClientId ) + areBlockNamesAllowedInClientId( sectionRootClientId ) ) { return sectionRootClientId; } @@ -668,10 +674,27 @@ export function getClosestAllowedInsertionPoint( state, name, clientId = '' ) { // Traverse the block tree up until we find a place where we can insert. let current = clientId; - while ( current !== null && ! canInsertBlockType( state, name, current ) ) { + while ( current !== null && ! areBlockNamesAllowedInClientId( current ) ) { const parentClientId = getBlockRootClientId( state, current ); current = parentClientId; } return current; } + +export function getClosestAllowedInsertionPointForPattern( + state, + pattern, + clientId +) { + const { allowedBlockTypes } = getSettings( state ); + const isAllowed = checkAllowListRecursive( + getGrammar( pattern ), + allowedBlockTypes + ); + if ( ! isAllowed ) { + return null; + } + const names = getGrammar( pattern ).map( ( { blockName: name } ) => name ); + return getClosestAllowedInsertionPoint( state, names, clientId ); +} diff --git a/test/e2e/specs/editor/various/allowed-patterns.spec.js b/test/e2e/specs/editor/various/allowed-patterns.spec.js index e592f776c61dd..894f143d19bb8 100644 --- a/test/e2e/specs/editor/various/allowed-patterns.spec.js +++ b/test/e2e/specs/editor/various/allowed-patterns.spec.js @@ -14,10 +14,7 @@ test.describe( 'Allowed Patterns', () => { ); } ); - test( 'should show all patterns when all blocks are allowed', async ( { - admin, - page, - } ) => { + test( 'should show all patterns by default', async ( { admin, page } ) => { await admin.createNewPost(); await page .getByRole( 'toolbar', { name: 'Document tools' } ) @@ -57,7 +54,7 @@ test.describe( 'Allowed Patterns', () => { ); } ); - test( 'should show only allowed patterns', async ( { + test( 'should show all patterns even if not allowed', async ( { admin, page, } ) => { @@ -80,7 +77,11 @@ test.describe( 'Allowed Patterns', () => { page .getByRole( 'listbox', { name: 'Block patterns' } ) .getByRole( 'option' ) - ).toHaveText( [ 'Test: Single heading' ] ); + ).toHaveText( [ + 'Test: Single heading', + 'Test: Single paragraph', + 'Test: Paragraph inside group', + ] ); } ); } ); } );