Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Patterns: Remove scope from Query patterns and introduce blockTypes #30471

Merged
merged 4 commits into from
Apr 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions lib/block-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,68 @@
* @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' => '<!-- wp:post-title {"isLink":true} /-->
'title' => __( 'Large', 'gutenberg' ),
'blockTypes' => array( 'core/query' ),
'categories' => array( 'Query' ),
'content' => '<!-- wp:query {"query":{"perPage":1,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} -->
<!-- wp:query-loop -->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-featured-image {"isLink":true,"align":"wide"} /-->
<!-- wp:post-excerpt /-->
<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->
<!-- wp:post-date /-->',
<!-- wp:post-date /-->
<!-- /wp:query-loop -->
<!-- /wp:query -->',
)
);

register_block_pattern(
'query/medium-posts',
array(
'title' => __( 'Medium', 'gutenberg' ),
'scope' => array(
'inserter' => false,
'block' => array( 'core/query' ),
),
'content' => '<!-- wp:columns {"align":"wide"} -->
'title' => __( 'Medium', 'gutenberg' ),
'blockTypes' => array( 'core/query' ),
'categories' => array( 'Query' ),
'content' => '<!-- wp:query {"query":{"perPage":1,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} -->
<!-- wp:query-loop -->
<!-- wp:columns {"align":"wide"} -->
<div class="wp-block-columns alignwide"><!-- wp:column {"width":"66.66%"} -->
<div class="wp-block-column" style="flex-basis:66.66%"><!-- wp:post-featured-image {"isLink":true} /--></div>
<!-- /wp:column -->
<!-- wp:column {"width":"33.33%"} -->
<div class="wp-block-column" style="flex-basis:33.33%"><!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-excerpt /--></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->',
<!-- /wp:columns -->
<!-- /wp:query-loop -->
<!-- /wp:query -->',
)
);

register_block_pattern(
'query/small-posts',
array(
'title' => __( 'Small', 'gutenberg' ),
'scope' => array(
'inserter' => false,
'block' => array( 'core/query' ),
),
'content' => '<!-- wp:columns {"verticalAlignment":"center"} -->
'title' => __( 'Small', 'gutenberg' ),
'blockTypes' => array( 'core/query' ),
'categories' => array( 'Query' ),
'content' => '<!-- wp:query {"query":{"perPage":1,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} -->
<!-- wp:query-loop -->
<!-- wp:columns {"verticalAlignment":"center"} -->
<div class="wp-block-columns are-vertically-aligned-center"><!-- wp:column {"verticalAlignment":"center","width":"25%"} -->
<div class="wp-block-column is-vertically-aligned-center" style="flex-basis:25%"><!-- wp:post-featured-image {"isLink":true} /--></div>
<!-- /wp:column -->
<!-- wp:column {"verticalAlignment":"center","width":"75%"} -->
<div class="wp-block-column is-vertically-aligned-center" style="flex-basis:75%"><!-- wp:post-title {"isLink":true} /--></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->',
<!-- /wp:columns -->
<!-- /wp:query-loop -->
<!-- /wp:query -->',
)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
43 changes: 27 additions & 16 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
]
);

/**
Expand Down
61 changes: 43 additions & 18 deletions packages/block-editor/src/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const {
__experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames,
__experimentalGetParsedReusableBlock,
__experimentalGetAllowedPatterns,
__experimentalGetScopedBlockPatterns,
__experimentalGetPatternsByBlockTypes,
__unstableGetClientIdWithClientIdsTree,
__unstableGetClientIdsTree,
} = selectors;
Expand Down Expand Up @@ -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:
'<!-- wp:test-block-a --><!-- /wp:test-block-a -->',
},
{
name: 'pattern-b',
blockTypes: [ 'test/block-b' ],
title: 'pattern b',
scope: { block: [ 'test/block-b' ] },
content:
'<!-- wp:test-block-b --><!-- /wp:test-block-b -->',
},
{
title: 'pattern c',
blockTypes: [ 'test/block-a' ],
content:
'<!-- wp:test-block-b --><!-- /wp:test-block-b -->',
},
],
},
};
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' } )
);
} );
} );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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' ),
Expand Down Expand Up @@ -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'
Expand Down
59 changes: 46 additions & 13 deletions packages/block-library/src/query/edit/query-block-setup.js
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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(
Expand Down