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' => ' + +
@@ -40,25 +44,29 @@
- ', + + + ', ) ); 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(