diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md
index cbf777d68429d..67dbc48ccf29d 100644
--- a/docs/reference-guides/data/data-core-block-editor.md
+++ b/docs/reference-guides/data/data-core-block-editor.md
@@ -538,7 +538,6 @@ _Parameters_
- _state_ `Object`: Editor state.
- _rootClientId_ `?string`: Optional root client ID of block list.
-- _syncStatus_ `?string`: Optional sync status to filter pattern blocks by.
_Returns_
diff --git a/packages/block-editor/src/components/inserter/block-patterns-explorer/patterns-list.js b/packages/block-editor/src/components/inserter/block-patterns-explorer/patterns-list.js
index a4e3c91cba4ba..fda1a00c1a07d 100644
--- a/packages/block-editor/src/components/inserter/block-patterns-explorer/patterns-list.js
+++ b/packages/block-editor/src/components/inserter/block-patterns-explorer/patterns-list.js
@@ -52,6 +52,7 @@ function PatternList( { filterValue, selectedCategory, patternCategories } ) {
onInsertBlocks,
destinationRootClientId
);
+
const registeredPatternCategories = useMemo(
() =>
patternCategories.map(
@@ -75,7 +76,12 @@ function PatternList( { filterValue, selectedCategory, patternCategories } ) {
);
}
return searchItems( allPatterns, filterValue );
- }, [ filterValue, selectedCategory, allPatterns ] );
+ }, [
+ filterValue,
+ allPatterns,
+ selectedCategory,
+ registeredPatternCategories,
+ ] );
// Announce search results on change.
useEffect( () => {
@@ -89,7 +95,7 @@ function PatternList( { filterValue, selectedCategory, patternCategories } ) {
count
);
debouncedSpeak( resultsFoundMessage );
- }, [ filterValue, debouncedSpeak ] );
+ }, [ filterValue, debouncedSpeak, filteredBlockPatterns.length ] );
const currentShownPatterns = useAsyncList( filteredBlockPatterns, {
step: INITIAL_INSERTER_RESULTS,
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 578791e880269..5c2720fd0502e 100644
--- a/packages/block-editor/src/components/inserter/block-patterns-tab.js
+++ b/packages/block-editor/src/components/inserter/block-patterns-tab.js
@@ -18,7 +18,6 @@ import {
Button,
} from '@wordpress/components';
import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';
-import { parse } from '@wordpress/blocks';
import { focus } from '@wordpress/dom';
/**
@@ -28,7 +27,6 @@ import usePatternsState from './hooks/use-patterns-state';
import BlockPatternList from '../block-patterns-list';
import PatternsExplorerModal from './block-patterns-explorer/explorer';
import MobileTabNavigation from './mobile-tab-navigation';
-import useBlockTypesState from './hooks/use-block-types-state';
const noop = () => {};
@@ -51,18 +49,6 @@ function usePatternsCategories( rootClientId ) {
rootClientId
);
- const [ unsyncedPatterns ] = useBlockTypesState(
- rootClientId,
- undefined,
- 'unsynced'
- );
-
- const filteredUnsyncedPatterns = useMemo( () => {
- return unsyncedPatterns.filter(
- ( { category: unsyncedPatternCategory } ) =>
- unsyncedPatternCategory === 'reusable'
- );
- }, [ unsyncedPatterns ] );
const hasRegisteredCategory = useCallback(
( pattern ) => {
if ( ! pattern.categories || ! pattern.categories.length ) {
@@ -107,20 +93,9 @@ function usePatternsCategories( rootClientId ) {
label: _x( 'Uncategorized' ),
} );
}
- if ( filteredUnsyncedPatterns.length > 0 ) {
- categories.push( {
- name: 'reusable',
- label: _x( 'Custom patterns' ),
- } );
- }
return categories;
- }, [
- allCategories,
- allPatterns,
- filteredUnsyncedPatterns.length,
- hasRegisteredCategory,
- ] );
+ }, [ allCategories, allPatterns, hasRegisteredCategory ] );
return populatedCategories;
}
@@ -169,24 +144,6 @@ export function BlockPatternsCategoryPanel( {
onInsert,
rootClientId
);
- const [ unsyncedPatterns ] = useBlockTypesState(
- rootClientId,
- onInsert,
- 'unsynced'
- );
- const filteredUnsyncedPatterns = useMemo( () => {
- return unsyncedPatterns
- .filter(
- ( { category: unsyncedPatternCategory } ) =>
- unsyncedPatternCategory === 'reusable'
- )
- .map( ( syncedPattern ) => ( {
- ...syncedPattern,
- blocks: parse( syncedPattern.content, {
- __unstableSkipMigrationLogs: true,
- } ),
- } ) );
- }, [ unsyncedPatterns ] );
const availableCategories = usePatternsCategories( rootClientId );
const currentCategoryPatterns = useMemo(
@@ -208,21 +165,15 @@ export function BlockPatternsCategoryPanel( {
return availablePatternCategories.length === 0;
} ),
- [ allPatterns, category ]
+ [ allPatterns, availableCategories, category.name ]
);
- const patterns =
- category.name === 'reusable'
- ? filteredUnsyncedPatterns
- : currentCategoryPatterns;
- const currentShownPatterns = useAsyncList( patterns );
+
+ const categoryPatternsList = useAsyncList( currentCategoryPatterns );
// Hide block pattern preview on unmount.
useEffect( () => () => onHover( null ), [] );
- if (
- ! currentCategoryPatterns.length &&
- ! filteredUnsyncedPatterns.length
- ) {
+ if ( ! currentCategoryPatterns.length ) {
return null;
}
@@ -233,8 +184,8 @@ export function BlockPatternsCategoryPanel( {
{ category.description }
{
+const useBlockTypesState = ( rootClientId, onInsert ) => {
const { categories, collections, items } = useSelect(
( select ) => {
const { getInserterItems } = select( blockEditorStore );
@@ -31,10 +30,10 @@ const useBlockTypesState = ( rootClientId, onInsert, syncStatus ) => {
return {
categories: getCategories(),
collections: getCollections(),
- items: getInserterItems( rootClientId, syncStatus ),
+ items: getInserterItems( rootClientId ),
};
},
- [ rootClientId, syncStatus ]
+ [ rootClientId ]
);
const onSelectItem = useCallback(
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 ca287b95c43b9..2a99e637ed123 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
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { useCallback } from '@wordpress/element';
+import { useCallback, useMemo } from '@wordpress/element';
import { cloneBlock } from '@wordpress/blocks';
import { useDispatch, useSelect } from '@wordpress/data';
import { __, sprintf } from '@wordpress/i18n';
@@ -12,6 +12,12 @@ import { store as noticesStore } from '@wordpress/notices';
*/
import { store as blockEditorStore } from '../../../store';
+const CUSTOM_CATEGORY = {
+ name: 'custom',
+ label: __( 'Custom patterns' ),
+ description: __( 'Custom patterns add by site users' ),
+};
+
/**
* Retrieves the block patterns inserter state.
*
@@ -25,6 +31,7 @@ const usePatternsState = ( onInsert, rootClientId ) => {
( select ) => {
const { __experimentalGetAllowedPatterns, getSettings } =
select( blockEditorStore );
+
return {
patterns: __experimentalGetAllowedPatterns( rootClientId ),
patternCategories:
@@ -33,25 +40,34 @@ const usePatternsState = ( onInsert, rootClientId ) => {
},
[ rootClientId ]
);
+
+ const allCategories = useMemo(
+ () => [ ...patternCategories, CUSTOM_CATEGORY ],
+ [ patternCategories ]
+ );
+
const { createSuccessNotice } = useDispatch( noticesStore );
- const onClickPattern = useCallback( ( pattern, blocks ) => {
- onInsert(
- ( blocks ?? [] ).map( ( block ) => cloneBlock( block ) ),
- pattern.name
- );
- createSuccessNotice(
- sprintf(
- /* translators: %s: block pattern title. */
- __( 'Block pattern "%s" inserted.' ),
- pattern.title
- ),
- {
- type: 'snackbar',
- }
- );
- }, [] );
-
- return [ patterns, patternCategories, onClickPattern ];
+ const onClickPattern = useCallback(
+ ( pattern, blocks ) => {
+ onInsert(
+ ( blocks ?? [] ).map( ( block ) => cloneBlock( block ) ),
+ pattern.name
+ );
+ createSuccessNotice(
+ sprintf(
+ /* translators: %s: block pattern title. */
+ __( 'Block pattern "%s" inserted.' ),
+ pattern.title
+ ),
+ {
+ type: 'snackbar',
+ }
+ );
+ },
+ [ createSuccessNotice, onInsert ]
+ );
+
+ return [ patterns, allCategories, onClickPattern ];
};
export default usePatternsState;
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 05d00f65b1fd3..fc314636d1195 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -1945,7 +1945,6 @@ const buildBlockTypeItem =
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
- * @param {?string} syncStatus Optional sync status to filter pattern blocks by.
*
* @return {WPEditorInserterItem[]} Items that appear in inserter.
*
@@ -1962,11 +1961,7 @@ const buildBlockTypeItem =
* @property {number} frecency Heuristic that combines frequency and recency.
*/
export const getInserterItems = createSelector(
- ( state, rootClientId = null, syncStatus ) => {
- const buildBlockTypeInserterItem = buildBlockTypeItem( state, {
- buildScope: 'inserter',
- } );
-
+ ( state, rootClientId = null ) => {
/*
* Matches block comment delimiters amid serialized content.
*
@@ -2031,13 +2026,7 @@ export const getInserterItems = createSelector(
};
};
- const blockTypeInserterItems = getBlockTypes()
- .filter( ( blockType ) =>
- canIncludeBlockTypeInInserter( state, blockType, rootClientId )
- )
- .map( buildBlockTypeInserterItem );
-
- const reusableBlockInserterItems = canInsertBlockTypeUnmemoized(
+ const syncedPatternInserterItems = canInsertBlockTypeUnmemoized(
state,
'core/block',
rootClientId
@@ -2045,13 +2034,25 @@ export const getInserterItems = createSelector(
? getReusableBlocks( state )
.filter(
( reusableBlock ) =>
- syncStatus === reusableBlock.meta?.sync_status ||
- ( ! syncStatus &&
- reusableBlock.meta?.sync_status === '' )
+ // Filter to either fully synced patterns (sync_status === 'fully'),
+ // or old school reusable blocks (sync_status === '').
+ reusableBlock.meta?.sync_status === 'fully' ||
+ reusableBlock.meta?.sync_status === '' ||
+ ! reusableBlock.meta?.sync_status
)
.map( buildReusableBlockInserterItem )
: [];
+ const buildBlockTypeInserterItem = buildBlockTypeItem( state, {
+ buildScope: 'inserter',
+ } );
+
+ const blockTypeInserterItems = getBlockTypes()
+ .filter( ( blockType ) =>
+ canIncludeBlockTypeInInserter( state, blockType, rootClientId )
+ )
+ .map( buildBlockTypeInserterItem );
+
const items = blockTypeInserterItems.reduce( ( accumulator, item ) => {
const { variations = [] } = item;
// Exclude any block type item that is to be replaced by a default variation.
@@ -2082,7 +2083,7 @@ export const getInserterItems = createSelector(
{ core: [], noncore: [] }
);
const sortedBlockTypes = [ ...coreItems, ...nonCoreItems ];
- return [ ...sortedBlockTypes, ...reusableBlockInserterItems ];
+ return [ ...sortedBlockTypes, ...syncedPatternInserterItems ];
},
( state, rootClientId ) => [
state.blockListSettings[ rootClientId ],
@@ -2306,10 +2307,32 @@ const checkAllowListRecursive = ( blocks, allowedBlockTypes ) => {
return true;
};
+function getUnsyncedPatterns( state ) {
+ const reusableBlocks =
+ state?.settings?.__experimentalReusableBlocks ?? EMPTY_ARRAY;
+
+ return reusableBlocks
+ .filter(
+ ( reusableBlock ) => reusableBlock.meta?.sync_status === 'unsynced'
+ )
+ .map( ( reusableBlock ) => {
+ return {
+ name: `core/block/${ reusableBlock.id }`,
+ title: reusableBlock.title.raw,
+ categories: [ 'custom' ],
+ content: reusableBlock.content.raw,
+ };
+ } );
+}
+
export const __experimentalGetParsedPattern = createSelector(
( state, patternName ) => {
const patterns = state.settings.__experimentalBlockPatterns;
- const pattern = patterns.find( ( { name } ) => name === patternName );
+ const unsyncedPatterns = getUnsyncedPatterns( state );
+
+ const pattern = [ ...patterns, ...unsyncedPatterns ].find(
+ ( { name } ) => name === patternName
+ );
if ( ! pattern ) {
return null;
}
@@ -2320,14 +2343,20 @@ export const __experimentalGetParsedPattern = createSelector(
} ),
};
},
- ( state ) => [ state.settings.__experimentalBlockPatterns ]
+ ( state ) => [
+ state.settings.__experimentalBlockPatterns,
+ state.settings.__experimentalReusableBlocks,
+ ]
);
const getAllAllowedPatterns = createSelector(
( state ) => {
const patterns = state.settings.__experimentalBlockPatterns;
+ const unsyncedPatterns = getUnsyncedPatterns( state );
+
const { allowedBlockTypes } = getSettings( state );
- const parsedPatterns = patterns
+
+ const parsedPatterns = [ ...patterns, ...unsyncedPatterns ]
.filter( ( { inserter = true } ) => !! inserter )
.map( ( { name } ) =>
__experimentalGetParsedPattern( state, name )
@@ -2339,6 +2368,7 @@ const getAllAllowedPatterns = createSelector(
},
( state ) => [
state.settings.__experimentalBlockPatterns,
+ state.settings.__experimentalReusableBlocks,
state.settings.allowedBlockTypes,
]
);
@@ -2365,6 +2395,7 @@ export const __experimentalGetAllowedPatterns = createSelector(
},
( state, rootClientId ) => [
state.settings.__experimentalBlockPatterns,
+ state.settings.__experimentalReusableBlocks,
state.settings.allowedBlockTypes,
state.settings.templateLock,
state.blockListSettings[ rootClientId ],