diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab.js b/packages/block-editor/src/components/inserter/block-patterns-tab.js index 127077ae29662..803822b358bec 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab.js @@ -18,12 +18,14 @@ import usePatternsState from './hooks/use-patterns-state'; import BlockPatternList from '../block-patterns-list'; function BlockPatternsCategory( { + rootClientId, onInsert, selectedCategory, onClickCategory, } ) { const [ allPatterns, allCategories, onClick ] = usePatternsState( - onInsert + onInsert, + rootClientId ); // Remove any empty categories @@ -120,9 +122,15 @@ function BlockPatternsCategory( { ); } -function BlockPatternsTabs( { onInsert, onClickCategory, selectedCategory } ) { +function BlockPatternsTabs( { + rootClientId, + onInsert, + onClickCategory, + selectedCategory, +} ) { return ( { - const { patternCategories, patterns } = useSelect( ( select ) => { - const { - __experimentalBlockPatterns, - __experimentalBlockPatternCategories, - } = select( blockEditorStore ).getSettings(); - return { - patterns: __experimentalBlockPatterns, - patternCategories: __experimentalBlockPatternCategories, - }; - }, [] ); +const usePatternsState = ( onInsert, rootClientId ) => { + const { patternCategories, patterns } = useSelect( + ( select ) => { + const { __experimentalGetAllowedPatterns, getSettings } = select( + blockEditorStore + ); + return { + patterns: __experimentalGetAllowedPatterns( rootClientId ), + patternCategories: getSettings() + .__experimentalBlockPatternCategories, + }; + }, + [ rootClientId ] + ); const { createSuccessNotice } = useDispatch( noticesStore ); const onClickPattern = useCallback( ( pattern, blocks ) => { onInsert( diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 0220092baf1f0..9cd0346d30ba9 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -47,19 +47,24 @@ function InserterMenu( { selectBlockOnInsert: __experimentalSelectBlockOnInsert, insertionIndex: __experimentalInsertionIndex, } ); - const { hasPatterns, hasReusableBlocks } = useSelect( ( select ) => { - const { - __experimentalBlockPatterns, - __experimentalReusableBlocks, - } = select( blockEditorStore ).getSettings(); + const { showPatterns, hasReusableBlocks } = useSelect( + ( select ) => { + const { __experimentalGetAllowedPatterns, getSettings } = select( + blockEditorStore + ); - return { - hasPatterns: !! __experimentalBlockPatterns?.length, - hasReusableBlocks: !! __experimentalReusableBlocks?.length, - }; - }, [] ); - - const showPatterns = ! destinationRootClientId && hasPatterns; + return { + showPatterns: + ! destinationRootClientId || + !! __experimentalGetAllowedPatterns( + destinationRootClientId + ).length, + hasReusableBlocks: !! getSettings().__experimentalReusableBlocks + ?.length, + }; + }, + [ destinationRootClientId ] + ); const onInsert = useCallback( ( blocks ) => { @@ -126,12 +131,18 @@ function InserterMenu( { const patternsTab = useMemo( () => ( ), - [ onInsertPattern, onClickPatternCategory, selectedPatternCategory ] + [ + destinationRootClientId, + onInsertPattern, + onClickPatternCategory, + selectedPatternCategory, + ] ); const reusableBlocksTab = useMemo( diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 6af305dffadcd..ad3a804fc54dc 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -45,9 +45,11 @@ export default function QuickInserter( { onInsertBlocks ); - const [ patterns ] = usePatternsState( onInsertBlocks ); - const showPatterns = - ! destinationRootClientId && patterns.length && !! filterValue; + const [ patterns ] = usePatternsState( + onInsertBlocks, + destinationRootClientId + ); + const showPatterns = patterns.length && !! filterValue; const showSearch = ( showPatterns && patterns.length > SEARCH_THRESHOLD ) || blockTypes.length > SEARCH_THRESHOLD; diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js index 56f17a95f05f4..00d34c814170b 100644 --- a/packages/block-editor/src/components/inserter/search-results.js +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -54,7 +54,8 @@ function InserterSearchResults( { onSelectBlockType, ] = useBlockTypesState( destinationRootClientId, onInsertBlocks ); const [ patterns, , onSelectBlockPattern ] = usePatternsState( - onInsertBlocks + onInsertBlocks, + destinationRootClientId ); const filteredBlockTypes = useMemo( () => { diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index f9da0fce9538f..6062a0d1fd8c6 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -15,6 +15,7 @@ import { filter, mapKeys, orderBy, + every, } from 'lodash'; import createSelector from 'rememo'; @@ -1741,6 +1742,50 @@ export const __experimentalGetAllowedBlocks = createSelector( ] ); +const __experimentalGetParsedPatterns = createSelector( + ( state ) => { + const patterns = state.settings.__experimentalBlockPatterns; + return map( patterns, ( pattern ) => ( { + ...pattern, + contentBlocks: parse( pattern.content ), + } ) ); + }, + ( state ) => [ state.settings.__experimentalBlockPatterns ] +); + +/** + * Returns the list of allowed patterns for inner blocks children + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional target root client ID. + * + * @return {Array?} The list of allowed block types. + */ +export const __experimentalGetAllowedPatterns = createSelector( + ( state, rootClientId = null ) => { + const patterns = __experimentalGetParsedPatterns( state ); + + if ( ! rootClientId ) { + return patterns; + } + + const patternsAllowed = filter( patterns, ( { contentBlocks } ) => { + return every( contentBlocks, ( { name } ) => + canInsertBlockType( state, name, rootClientId ) + ); + } ); + + return patternsAllowed; + }, + ( state, rootClientId ) => [ + state.settings.__experimentalBlockPatterns, + state.settings.allowedBlockTypes, + state.settings.templateLock, + state.blockListSettings[ rootClientId ], + state.blocks.byClientId[ rootClientId ], + ] +); + /** * Returns the Block List settings of a block, if any exist. * diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 964b105047082..e27baa322c2d4 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -71,6 +71,7 @@ const { getLowestCommonAncestorWithSelectedBlock, __experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames, __experimentalGetParsedReusableBlock, + __experimentalGetAllowedPatterns, } = selectors; describe( 'selectors', () => { @@ -3349,6 +3350,58 @@ describe( 'selectors', () => { ).toEqual( 'client-id-03' ); } ); } ); + + describe( '__experimentalGetAllowedPatterns', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-b' }, + }, + attributes: { + block1: {}, + block2: {}, + }, + }, + blockListSettings: { + block1: { + allowedBlocks: [ 'core/test-block-b' ], + }, + block2: { + allowedBlocks: [], + }, + }, + settings: { + __experimentalBlockPatterns: [ + { + title: 'pattern with a', + content: ``, + }, + { + title: 'pattern with b', + content: + '', + }, + ], + }, + }; + + it( 'should return all patterns for root level', () => { + expect( + __experimentalGetAllowedPatterns( state, null ) + ).toHaveLength( 2 ); + } ); + + it( 'should return patterns that consists of blocks allowed for the specified client ID', () => { + expect( + __experimentalGetAllowedPatterns( state, 'block1' ) + ).toHaveLength( 1 ); + + expect( + __experimentalGetAllowedPatterns( state, 'block2' ) + ).toHaveLength( 0 ); + } ); + } ); } ); describe( '__experimentalGetParsedReusableBlock', () => { diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 89c0e8efae5c6..11a82a1e899d3 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -82,6 +82,10 @@ function Editor( { const isFSETheme = getEditorSettings().isFSETheme; const isViewable = getPostType( postType )?.viewable ?? false; + // Prefetch and parse patterns. This ensures patterns are loaded and parsed when + // the editor is loaded rather than degrading the performance of the inserter. + select( 'core/block-editor' ).__experimentalGetAllowedPatterns(); + return { hasFixedToolbar: isFeatureActive( 'fixedToolbar' ) || diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 5c8abe0c7c9bc..f7dfacb8824b4 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -72,6 +72,10 @@ function Editor( { initialSettings } ) { const postType = getEditedPostType(); const postId = getEditedPostId(); + // Prefetch and parse patterns. This ensures patterns are loaded and parsed when + // the editor is loaded rather than degrading the performance of the inserter. + select( 'core/block-editor' ).__experimentalGetAllowedPatterns(); + // The currently selected entity to display. Typically template or template part. return { isInserterOpen: isInserterOpened(),