diff --git a/lib/block-patterns.php b/lib/block-patterns.php
index d376fb59aea1e..da42a0b25f8ae 100644
--- a/lib/block-patterns.php
+++ b/lib/block-patterns.php
@@ -5,34 +5,38 @@
* @package gutenberg
*/
+register_block_pattern_category( 'Query', array( 'label' => __( 'Query', 'gutenberg' ) ) );
+
// Initial Query block patterns.
register_block_pattern(
'query/large-posts',
array(
- 'title' => __( 'Large', 'gutenberg' ),
- 'scope' => array(
- 'inserter' => false,
- 'block' => array( 'core/query' ),
- ),
- 'content' => '
+ 'title' => __( 'Large', 'gutenberg' ),
+ 'blockTypes' => array( 'core/query' ),
+ 'categories' => array( 'Query' ),
+ 'content' => '
+
+
- ',
+
+
+ ',
)
);
register_block_pattern(
'query/medium-posts',
array(
- 'title' => __( 'Medium', 'gutenberg' ),
- 'scope' => array(
- 'inserter' => false,
- 'block' => array( 'core/query' ),
- ),
- 'content' => '
+ 'title' => __( 'Medium', 'gutenberg' ),
+ 'blockTypes' => array( 'core/query' ),
+ 'categories' => array( 'Query' ),
+ 'content' => '
+
+
- ',
+
+
+ ',
)
);
register_block_pattern(
'query/small-posts',
array(
- 'title' => __( 'Small', 'gutenberg' ),
- 'scope' => array(
- 'inserter' => false,
- 'block' => array( 'core/query' ),
- ),
- 'content' => '
+ 'title' => __( 'Small', 'gutenberg' ),
+ 'blockTypes' => array( 'core/query' ),
+ 'categories' => array( 'Query' ),
+ 'content' => '
+
+
- ',
+
+
+ ',
)
);
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 1b14286c821bf..5b5090e6d7b2b 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
@@ -31,13 +31,8 @@ const usePatternsState = ( onInsert, rootClientId ) => {
const { __experimentalGetAllowedPatterns, getSettings } = select(
blockEditorStore
);
- const inserterPatterns = __experimentalGetAllowedPatterns(
- rootClientId
- ).filter(
- ( pattern ) => ! pattern.scope || pattern.scope.inserter
- );
return {
- patterns: inserterPatterns,
+ patterns: __experimentalGetAllowedPatterns( rootClientId ),
patternCategories: getSettings()
.__experimentalBlockPatternCategories,
};
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index d2288f9fc67cf..3af1ae3ba200b 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -1838,29 +1838,40 @@ export const __experimentalGetAllowedPatterns = createSelector(
);
/**
- * Returns the list of patterns based on specific `scope` and
- * a block's name.
- * `inserter` scope should be handled differently, probably in
- * combination with `__experimentalGetAllowedPatterns`.
- * For now `__experimentalGetScopedBlockPatterns` handles properly
- * all other scopes.
- * Since both APIs are experimental we should revisit this.
+ * Returns the list of patterns based on their declared `blockTypes`
+ * and a block's name.
+ * Patterns can use `blockTypes` to integrate in work flows like
+ * suggesting appropriate patterns in a Placeholder state(during insertion)
+ * or blocks transformations.
*
* @param {Object} state Editor state.
- * @param {string} scope Block pattern scope.
- * @param {string} blockName Block's name.
+ * @param {string|string[]} blockNames Block's name or array of block names to find matching pattens.
+ * @param {?string} rootClientId Optional target root client ID.
*
- * @return {Array} The list of matched block patterns based on provided scope and block name.
+ * @return {Array} The list of matched block patterns based on declared `blockTypes` and block name.
*/
-export const __experimentalGetScopedBlockPatterns = createSelector(
- ( state, scope, blockName ) => {
- if ( ! scope && ! blockName ) return EMPTY_ARRAY;
- const patterns = state.settings.__experimentalBlockPatterns;
+export const __experimentalGetPatternsByBlockTypes = createSelector(
+ ( state, blockNames, rootClientId = null ) => {
+ if ( ! blockNames ) return EMPTY_ARRAY;
+ const patterns = __experimentalGetAllowedPatterns(
+ state,
+ rootClientId
+ );
+ const normalizedBlockNames = Array.isArray( blockNames )
+ ? blockNames
+ : [ blockNames ];
return patterns.filter( ( pattern ) =>
- pattern.scope?.[ scope ]?.includes?.( blockName )
+ pattern?.blockTypes?.some?.( ( blockName ) =>
+ normalizedBlockNames.includes( blockName )
+ )
);
},
- ( state ) => [ state.settings.__experimentalBlockPatterns ]
+ ( state, rootClientId ) => [
+ ...__experimentalGetAllowedPatterns.getDependants(
+ state,
+ rootClientId
+ ),
+ ]
);
/**
diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js
index 167c830f51036..6563be65dad33 100644
--- a/packages/block-editor/src/store/test/selectors.js
+++ b/packages/block-editor/src/store/test/selectors.js
@@ -72,7 +72,7 @@ const {
__experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames,
__experimentalGetParsedReusableBlock,
__experimentalGetAllowedPatterns,
- __experimentalGetScopedBlockPatterns,
+ __experimentalGetPatternsByBlockTypes,
__unstableGetClientIdWithClientIdsTree,
__unstableGetClientIdsTree,
} = selectors;
@@ -3407,52 +3407,77 @@ describe( 'selectors', () => {
).toHaveLength( 0 );
} );
} );
- describe( '__experimentalGetScopedBlockPatterns', () => {
+ describe( '__experimentalGetPatternsByBlockTypes', () => {
const state = {
- blocks: {},
+ blocks: {
+ byClientId: {
+ block1: { name: 'core/test-block-a' },
+ },
+ },
+ blockListSettings: {
+ block1: {
+ allowedBlocks: [ 'core/test-block-b' ],
+ },
+ },
settings: {
__experimentalBlockPatterns: [
{
name: 'pattern-a',
+ blockTypes: [ 'test/block-a' ],
title: 'pattern a',
- scope: { block: [ 'test/block-a' ] },
+ content:
+ '',
},
{
name: 'pattern-b',
+ blockTypes: [ 'test/block-b' ],
title: 'pattern b',
- scope: { block: [ 'test/block-b' ] },
+ content:
+ '',
+ },
+ {
+ title: 'pattern c',
+ blockTypes: [ 'test/block-a' ],
+ content:
+ '',
},
],
},
};
- it( 'should return empty array if no scope and block name is provided', () => {
- expect( __experimentalGetScopedBlockPatterns( state ) ).toEqual(
+ it( 'should return empty array if no block name is provided', () => {
+ expect( __experimentalGetPatternsByBlockTypes( state ) ).toEqual(
[]
);
- expect(
- __experimentalGetScopedBlockPatterns( state, 'block' )
- ).toEqual( [] );
} );
it( 'shoud return empty array if no match is found', () => {
- const patterns = __experimentalGetScopedBlockPatterns(
+ const patterns = __experimentalGetPatternsByBlockTypes(
state,
- 'block',
'test/block-not-exists'
);
expect( patterns ).toEqual( [] );
} );
it( 'should return proper results when there are matched block patterns', () => {
- const patterns = __experimentalGetScopedBlockPatterns(
+ const patterns = __experimentalGetPatternsByBlockTypes(
state,
- 'block',
'test/block-a'
);
+ expect( patterns ).toHaveLength( 2 );
+ expect( patterns ).toEqual(
+ expect.arrayContaining( [
+ expect.objectContaining( { title: 'pattern a' } ),
+ expect.objectContaining( { title: 'pattern c' } ),
+ ] )
+ );
+ } );
+ it( 'should return proper result with matched patterns and allowed blocks from rootClientId', () => {
+ const patterns = __experimentalGetPatternsByBlockTypes(
+ state,
+ 'test/block-a',
+ 'block1'
+ );
expect( patterns ).toHaveLength( 1 );
expect( patterns[ 0 ] ).toEqual(
- expect.objectContaining( {
- title: 'pattern a',
- scope: { block: [ 'test/block-a' ] },
- } )
+ expect.objectContaining( { title: 'pattern c' } )
);
} );
} );
diff --git a/packages/block-library/src/query/edit/block-setup/layout-step.js b/packages/block-library/src/query/edit/block-setup/layout-step.js
index a2a206dbb0dde..9df6893856722 100644
--- a/packages/block-library/src/query/edit/block-setup/layout-step.js
+++ b/packages/block-library/src/query/edit/block-setup/layout-step.js
@@ -2,8 +2,8 @@
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
-import { useState, useMemo } from '@wordpress/element';
-import { parse, store as blocksStore } from '@wordpress/blocks';
+import { useState } from '@wordpress/element';
+import { store as blocksStore } from '@wordpress/blocks';
import { useInstanceId } from '@wordpress/compose';
import {
BlockPreview,
@@ -36,14 +36,11 @@ const LayoutSetupStep = ( {
const { getBlockVariations, getDefaultBlockVariation } = select(
blocksStore
);
- const { __experimentalGetScopedBlockPatterns } = select(
+ const { __experimentalGetPatternsByBlockTypes } = select(
blockEditorStore
);
const { name } = blockType;
- const _patterns = __experimentalGetScopedBlockPatterns(
- 'block',
- name
- );
+ const _patterns = __experimentalGetPatternsByBlockTypes( name );
const _blockVariations = getBlockVariations( name, 'block' );
return {
defaultVariation: getDefaultBlockVariation( name, 'block' ),
@@ -126,8 +123,7 @@ const LayoutSetupStep = ( {
};
function BlockPattern( { pattern, onSelect, composite } ) {
- const { content, viewportWidth } = pattern;
- const blocks = useMemo( () => parse( content ), [ content ] );
+ const { viewportWidth, blocks } = pattern;
const descriptionId = useInstanceId(
BlockPattern,
'block-setup-block-layout-list__item-description'
diff --git a/packages/block-library/src/query/edit/query-block-setup.js b/packages/block-library/src/query/edit/query-block-setup.js
index 2a89734275d67..f7837da313fc3 100644
--- a/packages/block-library/src/query/edit/query-block-setup.js
+++ b/packages/block-library/src/query/edit/query-block-setup.js
@@ -1,10 +1,17 @@
+/**
+ * External dependencies
+ */
+import { cloneDeep } from 'lodash';
/**
* WordPress dependencies
*/
import { useDispatch } from '@wordpress/data';
import { __, _x } from '@wordpress/i18n';
import { SelectControl, ToggleControl } from '@wordpress/components';
-import { createBlocksFromInnerBlocksTemplate } from '@wordpress/blocks';
+import {
+ cloneBlock,
+ createBlocksFromInnerBlocksTemplate,
+} from '@wordpress/blocks';
import { store as blockEditorStore } from '@wordpress/block-editor';
/**
@@ -20,29 +27,55 @@ const QueryBlockSetup = ( {
name: blockName,
} ) => {
const { postType, inherit } = query;
- const { replaceInnerBlocks } = useDispatch( blockEditorStore );
+ const { replaceBlocks, replaceInnerBlocks } = useDispatch(
+ blockEditorStore
+ );
const { postTypesSelectOptions } = usePostTypes();
const updateQuery = ( newQuery ) =>
setAttributes( { query: { ...query, ...newQuery } } );
- const onFinish = ( innerBlocks ) => {
- if ( innerBlocks ) {
- replaceInnerBlocks(
- clientId,
- createBlocksFromInnerBlocksTemplate( innerBlocks ),
- false
- );
- }
- };
const onVariationSelect = ( nextVariation ) => {
if ( nextVariation.attributes ) {
setAttributes( nextVariation.attributes );
}
if ( nextVariation.innerBlocks ) {
- onFinish( nextVariation.innerBlocks );
+ replaceInnerBlocks(
+ clientId,
+ createBlocksFromInnerBlocksTemplate(
+ nextVariation.innerBlocks
+ ),
+ false
+ );
}
};
const onBlockPatternSelect = ( blocks ) => {
- onFinish( [ [ 'core/query-loop', {}, blocks ] ] );
+ const clonedBlocks = blocks.map( ( block ) => {
+ const clone = cloneBlock( block );
+ /**
+ * TODO: this check will be revised with the ongoing work on block patterns.
+ * For now we keep the value of posts per page (`query.perPage`) from Query patterns
+ * so as to preview the pattern as intended, without possible big previews.
+ * During insertion, we need to override the Query's attributes that can be set in
+ * the Placeholder and we unset the `perPage` value to be set appropriately by Query block.
+ */
+ if ( block.name === 'core/query' ) {
+ /**
+ * We need to `cloneDeep` the Query's attributes, as `cloneBlock` does a swallow
+ * copy of the block.
+ */
+ const queryAttributes = cloneDeep( clone.attributes );
+ Object.assign( queryAttributes.query, {
+ inherit: query.inherit,
+ postType: query.postType,
+ perPage: null,
+ } );
+ return {
+ ...clone,
+ attributes: queryAttributes,
+ };
+ }
+ return clone;
+ } );
+ replaceBlocks( clientId, clonedBlocks );
};
const inheritToggleHelp = !! inherit
? _x(