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 ],