diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js index 0bcc00cb5f6ae8..10e16a0779cd63 100644 --- a/packages/block-editor/src/store/index.js +++ b/packages/block-editor/src/store/index.js @@ -10,6 +10,7 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as privateActions from './private-actions'; import * as privateSelectors from './private-selectors'; +import * as resolvers from './resolvers'; import * as actions from './actions'; import { STORE_NAME } from './constants'; import { unlock } from '../lock-unlock'; @@ -22,6 +23,7 @@ import { unlock } from '../lock-unlock'; export const storeConfig = { reducer, selectors, + resolvers, actions, }; diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index e8230eea89daa3..acde4dfbee3149 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -3,6 +3,11 @@ */ import createSelector from 'rememo'; +/** + * WordPress dependencies + */ +import { createRegistrySelector } from '@wordpress/data'; + /** * Internal dependencies */ @@ -11,11 +16,12 @@ import { getBlockParents, getBlockEditingMode, getSettings, - __experimentalGetParsedPattern, canInsertBlockType, - __experimentalGetAllowedPatterns, } from './selectors'; -import { getAllPatterns, checkAllowListRecursive } from './utils'; +import { checkAllowListRecursive } from './utils'; +import { INSERTER_PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; +import { store } from './'; +import { unlock } from '../lock-unlock'; /** * Returns true if the block interface is hidden, or false otherwise. @@ -242,6 +248,10 @@ export const getInserterMediaCategories = createSelector( ] ); +export function getFetchedPatterns( state ) { + return state.blockPatterns; +} + /** * Returns whether there is at least one allowed pattern for inner blocks children. * This is useful for deferring the parsing of all patterns until needed. @@ -251,29 +261,85 @@ export const getInserterMediaCategories = createSelector( * * @return {boolean} If there is at least one allowed pattern. */ -export const hasAllowedPatterns = createSelector( - ( state, rootClientId = null ) => { - const patterns = getAllPatterns( state ); - const { allowedBlockTypes } = getSettings( state ); - return patterns.some( ( { name, inserter = true } ) => { - if ( ! inserter ) { - return false; - } - const { blocks } = __experimentalGetParsedPattern( state, name ); - return ( - checkAllowListRecursive( blocks, allowedBlockTypes ) && - blocks.every( ( { name: blockName } ) => - canInsertBlockType( state, blockName, rootClientId ) - ) +export const hasAllowedPatterns = createRegistrySelector( ( select ) => + createSelector( + ( state, rootClientId = null ) => { + const { getAllPatterns, __experimentalGetParsedPattern } = unlock( + select( store ) ); - } ); - }, - ( state, rootClientId ) => [ - ...__experimentalGetAllowedPatterns.getDependants( - state, - rootClientId - ), - ] + const patterns = getAllPatterns(); + const { allowedBlockTypes } = getSettings( state ); + return patterns.some( ( { name, inserter = true } ) => { + if ( ! inserter ) { + return false; + } + const { blocks } = __experimentalGetParsedPattern( name ); + return ( + checkAllowListRecursive( blocks, allowedBlockTypes ) && + blocks.every( ( { name: blockName } ) => + canInsertBlockType( state, blockName, rootClientId ) + ) + ); + } ); + }, + ( state, rootClientId ) => [ + state.blockPatterns, + state.settings.allowedBlockTypes, + state.settings.templateLock, + state.blockListSettings[ rootClientId ], + state.blocks.byClientId.get( rootClientId ), + ] + ) +); + +export const getAllPatterns = createRegistrySelector( ( select ) => + createSelector( + ( state ) => { + // This setting is left for back compat. + const { + __experimentalBlockPatterns, + __experimentalFetchBlockPatterns, + __experimentalUserPatternCategories = [], + __experimentalReusableBlocks = [], + } = state.settings; + const userPatterns = ( __experimentalReusableBlocks ?? [] ).map( + ( userPattern ) => { + return { + name: `core/block/${ userPattern.id }`, + id: userPattern.id, + type: INSERTER_PATTERN_TYPES.user, + title: userPattern.title.raw, + categories: userPattern.wp_pattern_category.map( + ( catId ) => { + const category = ( + __experimentalUserPatternCategories ?? [] + ).find( ( { id } ) => id === catId ); + return category ? category.slug : catId; + } + ), + content: userPattern.content.raw, + syncStatus: userPattern.wp_pattern_sync_status, + }; + } + ); + return [ + ...userPatterns, + ...__experimentalBlockPatterns, + ...unlock( select( store ) ).getFetchedPatterns( + __experimentalFetchBlockPatterns + ), + ]; + }, + ( state ) => { + return [ + state.settings.__experimentalBlockPatterns, + state.settings.__experimentalUserPatternCategories, + state.settings.__experimentalReusableBlocks, + state.settings.__experimentalFetchBlockPatterns, + state.blockPatterns, + ]; + } + ) ); /** diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index fa6c8942e66add..8d841fe07547f4 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2017,6 +2017,15 @@ export function lastFocus( state = false, action ) { return state; } +function blockPatterns( state = [], action ) { + switch ( action.type ) { + case 'RECEIVE_BLOCK_PATTERNS': + return action.patterns; + } + + return state; +} + const combinedReducers = combineReducers( { blocks, isTyping, @@ -2047,6 +2056,7 @@ const combinedReducers = combineReducers( { blockRemovalRules, openedBlockSettingsMenu, registeredInserterMediaCategories, + blockPatterns, } ); function withAutomaticChangeReset( reducer ) { diff --git a/packages/block-editor/src/store/resolvers.js b/packages/block-editor/src/store/resolvers.js new file mode 100644 index 00000000000000..85d1846d78ec1e --- /dev/null +++ b/packages/block-editor/src/store/resolvers.js @@ -0,0 +1,6 @@ +export const getFetchedPatterns = + ( fetch ) => + async ( { dispatch } ) => { + const patterns = await fetch(); + dispatch( { type: 'RECEIVE_BLOCK_PATTERNS', patterns } ); + }; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 5e47e966ef3345..3b443045f09d98 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -26,12 +26,10 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { - getAllPatterns, - checkAllowListRecursive, - checkAllowList, -} from './utils'; +import { checkAllowListRecursive, checkAllowList } from './utils'; import { orderBy } from '../utils/sorting'; +import { store } from './'; +import { unlock } from '../lock-unlock'; /** * A block selection object. @@ -2239,39 +2237,27 @@ export const __experimentalGetDirectInsertBlock = createSelector( ] ); -export const __experimentalGetParsedPattern = createSelector( - ( state, patternName ) => { - const patterns = getAllPatterns( state ); - const pattern = patterns.find( ( { name } ) => name === patternName ); - if ( ! pattern ) { - return null; - } - return { - ...pattern, - blocks: parse( pattern.content, { - __unstableSkipMigrationLogs: true, - } ), - }; - }, - ( state ) => [ getAllPatterns( state ) ] -); - -const getAllAllowedPatterns = createSelector( - ( state ) => { - const patterns = getAllPatterns( state ); - const { allowedBlockTypes } = getSettings( state ); - - const parsedPatterns = patterns - .filter( ( { inserter = true } ) => !! inserter ) - .map( ( { name } ) => - __experimentalGetParsedPattern( state, name ) - ); - const allowedPatterns = parsedPatterns.filter( ( { blocks } ) => - checkAllowListRecursive( blocks, allowedBlockTypes ) - ); - return allowedPatterns; - }, - ( state ) => [ getAllPatterns( state ), state.settings.allowedBlockTypes ] +export const __experimentalGetParsedPattern = createRegistrySelector( + ( select ) => + createSelector( + ( state, patternName ) => { + const { getAllPatterns } = unlock( select( store ) ); + const patterns = getAllPatterns(); + const pattern = patterns.find( + ( { name } ) => name === patternName + ); + if ( ! pattern ) { + return null; + } + return { + ...pattern, + blocks: parse( pattern.content, { + __unstableSkipMigrationLogs: true, + } ), + }; + }, + ( state ) => [ state.blockPatterns ] + ) ); /** @@ -2282,24 +2268,44 @@ const getAllAllowedPatterns = createSelector( * * @return {Array?} The list of allowed patterns. */ -export const __experimentalGetAllowedPatterns = createSelector( - ( state, rootClientId = null ) => { - const availableParsedPatterns = getAllAllowedPatterns( state ); - const patternsAllowed = availableParsedPatterns.filter( - ( { blocks } ) => - blocks.every( ( { name } ) => - canInsertBlockType( state, name, rootClientId ) - ) - ); +export const __experimentalGetAllowedPatterns = createRegistrySelector( + ( select ) => { + return createSelector( + ( state, rootClientId = null ) => { + const { + getAllPatterns, + __experimentalGetParsedPattern: getParsedPattern, + } = unlock( select( store ) ); + const patterns = getAllPatterns(); + const { allowedBlockTypes } = getSettings( state ); + + const parsedPatterns = patterns + .filter( ( { inserter = true } ) => !! inserter ) + .map( ( { name } ) => getParsedPattern( name ) ); + const availableParsedPatterns = parsedPatterns.filter( + ( { blocks } ) => + checkAllowListRecursive( blocks, allowedBlockTypes ) + ); + const patternsAllowed = availableParsedPatterns.filter( + ( { blocks } ) => + blocks.every( ( { name } ) => + canInsertBlockType( state, name, rootClientId ) + ) + ); - return patternsAllowed; - }, - ( state, rootClientId ) => [ - getAllAllowedPatterns( state ), - state.settings.templateLock, - state.blockListSettings[ rootClientId ], - state.blocks.byClientId.get( rootClientId ), - ] + return patternsAllowed; + }, + ( state, rootClientId ) => { + return [ + state.blockPatterns, + state.settings.allowedBlockTypes, + state.settings.templateLock, + state.blockListSettings[ rootClientId ], + state.blocks.byClientId.get( rootClientId ), + ]; + } + ); + } ); /** @@ -2315,32 +2321,35 @@ export const __experimentalGetAllowedPatterns = createSelector( * * @return {Array} The list of matched block patterns based on declared `blockTypes` and block name. */ -export const getPatternsByBlockTypes = createSelector( - ( state, blockNames, rootClientId = null ) => { - if ( ! blockNames ) return EMPTY_ARRAY; - const patterns = __experimentalGetAllowedPatterns( - state, - rootClientId - ); - const normalizedBlockNames = Array.isArray( blockNames ) - ? blockNames - : [ blockNames ]; - const filteredPatterns = patterns.filter( ( pattern ) => - pattern?.blockTypes?.some?.( ( blockName ) => - normalizedBlockNames.includes( blockName ) - ) - ); - if ( filteredPatterns.length === 0 ) { - return EMPTY_ARRAY; - } - return filteredPatterns; - }, - ( state, blockNames, rootClientId ) => [ - ...__experimentalGetAllowedPatterns.getDependants( - state, - rootClientId - ), - ] +export const getPatternsByBlockTypes = createRegistrySelector( ( select ) => + createSelector( + ( state, blockNames, rootClientId = null ) => { + if ( ! blockNames ) return EMPTY_ARRAY; + const patterns = select( store ).__experimentalGetAllowedPatterns( + state, + rootClientId + ); + const normalizedBlockNames = Array.isArray( blockNames ) + ? blockNames + : [ blockNames ]; + const filteredPatterns = patterns.filter( ( pattern ) => + pattern?.blockTypes?.some?.( ( blockName ) => + normalizedBlockNames.includes( blockName ) + ) + ); + if ( filteredPatterns.length === 0 ) { + return EMPTY_ARRAY; + } + return filteredPatterns; + }, + ( state, blockNames, rootClientId ) => [ + state.blockPatterns, + state.settings.allowedBlockTypes, + state.settings.templateLock, + state.blockListSettings[ rootClientId ], + state.blocks.byClientId.get( rootClientId ), + ] + ) ); export const __experimentalGetPatternsByBlockTypes = createSelector( diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js index 7587dcdf56fd79..5dec1171763792 100644 --- a/packages/block-editor/src/store/utils.js +++ b/packages/block-editor/src/store/utils.js @@ -1,53 +1,3 @@ -/** - * External dependencies - */ -import createSelector from 'rememo'; - -/** - * Internal dependencies - */ -import { INSERTER_PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; - -export const getUserPatterns = createSelector( - ( state ) => { - const userPatterns = state.settings.__experimentalReusableBlocks ?? []; - const userPatternCategories = - state.settings.__experimentalUserPatternCategories ?? []; - return userPatterns.map( ( userPattern ) => { - return { - name: `core/block/${ userPattern.id }`, - id: userPattern.id, - type: INSERTER_PATTERN_TYPES.user, - title: userPattern.title.raw, - categories: userPattern.wp_pattern_category.map( ( catId ) => { - const category = userPatternCategories.find( - ( { id } ) => id === catId - ); - return category ? category.slug : catId; - } ), - content: userPattern.content.raw, - syncStatus: userPattern.wp_pattern_sync_status, - }; - } ); - }, - ( state ) => [ - state.settings.__experimentalReusableBlocks, - state.settings.__experimentalUserPatternCategories, - ] -); - -export const getAllPatterns = createSelector( - ( state ) => { - const patterns = state.settings.__experimentalBlockPatterns; - const userPatterns = getUserPatterns( state ); - return [ ...userPatterns, ...patterns ]; - }, - ( state ) => [ - state.settings.__experimentalBlockPatterns, - getUserPatterns( state ), - ] -); - export const checkAllowList = ( list, item, defaultResult = null ) => { if ( typeof list === 'boolean' ) { return list; diff --git a/packages/core-data/src/fetch/index.js b/packages/core-data/src/fetch/index.js index 8d4d28e3b0db82..09274c27a8c5ea 100644 --- a/packages/core-data/src/fetch/index.js +++ b/packages/core-data/src/fetch/index.js @@ -1,2 +1,26 @@ +/** + * External dependencies + */ +import { camelCase } from 'change-case'; + +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; + export { default as __experimentalFetchLinkSuggestions } from './__experimental-fetch-link-suggestions'; export { default as __experimentalFetchUrlData } from './__experimental-fetch-url-data'; + +export async function fetchBlockPatterns() { + const restPatterns = await apiFetch( { + path: '/wp/v2/block-patterns/patterns', + } ); + return restPatterns?.map( ( pattern ) => + Object.fromEntries( + Object.entries( pattern ).map( ( [ key, value ] ) => [ + camelCase( key ), + value, + ] ) + ) + ); +} diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 807005ec4a6e8d..7f4d3f54776d41 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -17,6 +17,7 @@ import { STORE_NAME } from './name'; import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities'; import { forwardResolver, getNormalizedCommaSeparable } from './utils'; import { getSyncProvider } from './sync'; +import { fetchBlockPatterns } from './fetch'; /** * Requests authors from the REST API. @@ -619,17 +620,7 @@ getCurrentThemeGlobalStylesRevisions.shouldInvalidate = ( action ) => { export const getBlockPatterns = () => async ( { dispatch } ) => { - const restPatterns = await apiFetch( { - path: '/wp/v2/block-patterns/patterns', - } ); - const patterns = restPatterns?.map( ( pattern ) => - Object.fromEntries( - Object.entries( pattern ).map( ( [ key, value ] ) => [ - camelCase( key ), - value, - ] ) - ) - ); + const patterns = await fetchBlockPatterns(); dispatch( { type: 'RECEIVE_BLOCK_PATTERNS', patterns } ); }; diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 5a9b3a82b1bdbb..f605a0a78f0b34 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -7,6 +7,7 @@ import { store as coreStore, __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, + fetchBlockPatterns, } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -101,7 +102,6 @@ function useBlockEditorSettings( settings, postType, postId ) { pageOnFront, pageForPosts, userPatternCategories, - restBlockPatterns, restBlockPatternCategories, } = useSelect( ( select ) => { @@ -112,7 +112,6 @@ function useBlockEditorSettings( settings, postType, postId ) { getEntityRecord, getUserPatternCategories, getEntityRecords, - getBlockPatterns, getBlockPatternCategories, } = select( coreStore ); const { get } = select( preferencesStore ); @@ -148,7 +147,6 @@ function useBlockEditorSettings( settings, postType, postId ) { pageOnFront: siteSettings?.page_on_front, pageForPosts: siteSettings?.page_for_posts, userPatternCategories: getUserPatternCategories(), - restBlockPatterns: getBlockPatterns(), restBlockPatternCategories: getBlockPatternCategories(), }; }, @@ -162,26 +160,6 @@ function useBlockEditorSettings( settings, postType, postId ) { settings.__experimentalAdditionalBlockPatternCategories ?? // WP 6.0 settings.__experimentalBlockPatternCategories; // WP 5.9 - const blockPatterns = useMemo( - () => - [ - ...( settingsBlockPatterns || [] ), - ...( restBlockPatterns || [] ), - ] - .filter( - ( x, index, arr ) => - index === arr.findIndex( ( y ) => x.name === y.name ) - ) - .filter( ( { postTypes } ) => { - return ( - ! postTypes || - ( Array.isArray( postTypes ) && - postTypes.includes( postType ) ) - ); - } ), - [ settingsBlockPatterns, restBlockPatterns, postType ] - ); - const blockPatternCategories = useMemo( () => [ @@ -254,8 +232,26 @@ function useBlockEditorSettings( settings, postType, postId ) { isDistractionFree, keepCaretInsideBlock, mediaUpload: hasUploadPermissions ? mediaUpload : undefined, + __experimentalFetchBlockPatterns: async () => { + const restPatterns = await fetchBlockPatterns(); + return [ + ...( settingsBlockPatterns || [] ), + ...( restPatterns || [] ), + ] + .filter( + ( x, index, arr ) => + index === + arr.findIndex( ( y ) => x.name === y.name ) + ) + .filter( ( { postTypes } ) => { + return ( + ! postTypes || + ( Array.isArray( postTypes ) && + postTypes.includes( postType ) ) + ); + } ); + }, __experimentalReusableBlocks: reusableBlocks, - __experimentalBlockPatterns: blockPatterns, __experimentalBlockPatternCategories: blockPatternCategories, __experimentalUserPatternCategories: userPatternCategories, __experimentalFetchLinkSuggestions: ( search, searchOptions ) => @@ -296,7 +292,7 @@ function useBlockEditorSettings( settings, postType, postId ) { hasUploadPermissions, reusableBlocks, userPatternCategories, - blockPatterns, + settingsBlockPatterns, blockPatternCategories, canUseUnfilteredHTML, undo,