Skip to content

Commit

Permalink
Blocks: Match blocks in the inserter using keywords from patterns (#1…
Browse files Browse the repository at this point in the history
…9243)

* Blocks: Match blocks in the inserter using keywords from patterns

* Ensure that matched patterns with the search term are marked

* Introduce scopes for block patterns

* Make it possible to apply initial attributes and inner blocks directly from the inserter

* Update block preview in the inserter to use attributes from the variation

* Change the way patterns are handled in the inserter

* Update packages/block-editor/src/components/block-types-list/index.js

Co-Authored-By: Miguel Fonseca <[email protected]>

* Improve the way patterns are added to the inserter

* Rename pattern label to patter title to align with block types

* Inserter: Don't auto-add block if it has variations

Co-authored-by: Miguel Fonseca <[email protected]>
  • Loading branch information
gziolo and mcsf authored Jan 28, 2020
1 parent 08182bf commit bc38a3d
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function BlockPatternPicker( {
iconSize={ 48 }
onClick={ () => onSelect( pattern ) }
className="block-editor-block-pattern-picker__pattern"
label={ pattern.label }
label={ pattern.title }
/>
</li>
) ) }
Expand Down
70 changes: 52 additions & 18 deletions packages/block-editor/src/components/block-types-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,65 @@ import { getBlockMenuDefaultClassName } from '@wordpress/blocks';
*/
import InserterListItem from '../inserter-list-item';

function BlockTypesList( { items, onSelect, onHover = () => {}, children } ) {
function BlockTypesList( { items = [], onSelect, onHover = () => {}, children } ) {
const normalizedItems = items.reduce( ( result, item ) => {
const { patterns = [] } = item;
const hasDefaultPattern = patterns.some( ( { isDefault } ) => isDefault );

// If there is no default inserter pattern provided,
// then default block type is displayed.
if ( ! hasDefaultPattern ) {
result.push( item );
}

if ( patterns.length ) {
result = result.concat( patterns.map( ( pattern ) => {
return {
...item,
id: `${ item.id }-${ pattern.name }`,
icon: pattern.icon || item.icon,
title: pattern.title || item.title,
description: pattern.description || item.description,
// If `example` is explicitly undefined for the pattern, the preview will not be shown.
example: pattern.hasOwnProperty( 'example' ) ? pattern.example : item.example,
initialAttributes: {
...item.initialAttributes,
...pattern.attributes,
},
innerBlocks: pattern.innerBlocks,
};
} ) );
}

return result;
}, [] );

return (
/*
* Disable reason: The `list` ARIA role is redundant but
* Safari+VoiceOver won't announce the list otherwise.
*/
/* eslint-disable jsx-a11y/no-redundant-roles */
<ul role="list" className="block-editor-block-types-list">
{ items && items.map( ( item ) =>
<InserterListItem
key={ item.id }
className={ getBlockMenuDefaultClassName( item.id ) }
icon={ item.icon }
onClick={ () => {
onSelect( item );
onHover( null );
} }
onFocus={ () => onHover( item ) }
onMouseEnter={ () => onHover( item ) }
onMouseLeave={ () => onHover( null ) }
onBlur={ () => onHover( null ) }
isDisabled={ item.isDisabled }
title={ item.title }
/>
) }
{ normalizedItems.map( ( item ) => {
return (
<InserterListItem
key={ item.id }
className={ getBlockMenuDefaultClassName( item.id ) }
icon={ item.icon }
onClick={ () => {
onSelect( item );
onHover( null );
} }
onFocus={ () => onHover( item ) }
onMouseEnter={ () => onHover( item ) }
onMouseLeave={ () => onHover( null ) }
onBlur={ () => onHover( null ) }
isDisabled={ item.isDisabled }
title={ item.title }
/>
);
} ) }
{ children }
</ul>
/* eslint-enable jsx-a11y/no-redundant-roles */
Expand Down
10 changes: 8 additions & 2 deletions packages/block-editor/src/components/inserter/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { get } from 'lodash';
import { size } from 'lodash';
/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -140,12 +140,18 @@ export default compose( [
hasInserterItems,
__experimentalGetAllowedBlocks,
} = select( 'core/block-editor' );
const {
__experimentalGetBlockPatterns: getBlockPatterns,
} = select( 'core/blocks' );

rootClientId = rootClientId || getBlockRootClientId( clientId ) || undefined;

const allowedBlocks = __experimentalGetAllowedBlocks( rootClientId );

const hasSingleBlockType = allowedBlocks && ( get( allowedBlocks, [ 'length' ], 0 ) === 1 );
const hasSingleBlockType = (
size( allowedBlocks ) === 1 &&
size( getBlockPatterns( allowedBlocks[ 0 ].name, 'inserter' ) ) === 0
);

let allowedBlockType = false;
if ( hasSingleBlockType ) {
Expand Down
27 changes: 23 additions & 4 deletions packages/block-editor/src/components/inserter/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ const stopKeyPropagation = ( event ) => event.stopPropagation();

const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ];

// Copied over from the Columns block. It seems like it should become part of public API.
const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => {
return map(
innerBlocksTemplate,
( [ name, attributes, innerBlocks = [] ] ) =>
createBlock( name, attributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) )
);
};

export class InserterMenu extends Component {
constructor() {
super( ...arguments );
Expand Down Expand Up @@ -383,7 +392,7 @@ export class InserterMenu extends Component {
{ hoveredItem && (
<>
{ ! isReusableBlock( hoveredItem ) && (
<BlockCard blockType={ hoveredItemBlockType } />
<BlockCard blockType={ hoveredItem } />
) }
<div className="block-editor-inserter__preview">
{ ( isReusableBlock( hoveredItem ) || hoveredItemBlockType.example ) ? (
Expand All @@ -393,7 +402,13 @@ export class InserterMenu extends Component {
viewportWidth={ 500 }
blocks={
hoveredItemBlockType.example ?
getBlockFromExample( hoveredItem.name, hoveredItemBlockType.example ) :
getBlockFromExample( hoveredItem.name, {
attributes: {
...hoveredItemBlockType.example.attributes,
...hoveredItem.initialAttributes,
},
innerBlocks: hoveredItemBlockType.example.innerBlocks,
} ) :
createBlock( hoveredItem.name, hoveredItem.initialAttributes )
}
/>
Expand Down Expand Up @@ -534,9 +549,13 @@ export default compose(
onSelect,
__experimentalSelectBlockOnInsert: selectBlockOnInsert,
} = ownProps;
const { name, title, initialAttributes } = item;
const { name, title, initialAttributes, innerBlocks } = item;
const selectedBlock = getSelectedBlock();
const insertedBlock = createBlock( name, initialAttributes );
const insertedBlock = createBlock(
name,
initialAttributes,
createBlocksFromInnerBlocksTemplate( innerBlocks )
);

if ( ! isAppender && selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) {
replaceBlocks( selectedBlock.clientId, insertedBlock );
Expand Down
40 changes: 36 additions & 4 deletions packages/block-editor/src/components/inserter/search-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
differenceWith,
find,
get,
intersectionWith,
isEmpty,
words,
} from 'lodash';

Expand Down Expand Up @@ -52,15 +54,15 @@ const removeMatchingTerms = ( unmatchedTerms, unprocessedTerms ) => {
* @return {Array} Filtered item list.
*/
export const searchItems = ( items, categories, collections, searchTerm ) => {
const normalizedTerms = normalizeSearchTerm( searchTerm );
const normalizedSearchTerms = normalizeSearchTerm( searchTerm );

if ( normalizedTerms.length === 0 ) {
if ( normalizedSearchTerms.length === 0 ) {
return items;
}

return items.filter( ( { name, title, category, keywords = [] } ) => {
return items.filter( ( { name, title, category, keywords = [], patterns = [] } ) => {
let unmatchedTerms = removeMatchingTerms(
normalizedTerms,
normalizedSearchTerms,
title
);

Expand Down Expand Up @@ -90,6 +92,36 @@ export const searchItems = ( items, categories, collections, searchTerm ) => {
);
}

if ( unmatchedTerms.length === 0 ) {
return true;
}

unmatchedTerms = removeMatchingTerms(
unmatchedTerms,
patterns.map( ( pattern ) => pattern.title ).join( ' ' ),
);

return unmatchedTerms.length === 0;
} ).map( ( item ) => {
if ( isEmpty( item.patterns ) ) {
return item;
}

const matchedPatterns = item.patterns.filter( ( pattern ) => {
return intersectionWith(
normalizedSearchTerms,
normalizeSearchTerm( pattern.title ),
( termToMatch, labelTerm ) => labelTerm.includes( termToMatch )
).length > 0;
} );
// When no partterns matched, fallback to all patterns.
if ( isEmpty( matchedPatterns ) ) {
return item;
}

return {
...item,
patterns: matchedPatterns,
};
} );
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ export const textItem = {
utility: 1,
};

export const withPatternsItem = {
id: 'core/block-with-patterns',
name: 'core/block-with-patterns',
initialAttributes: {},
title: 'With Patterns',
category: 'widgets',
isDisabled: false,
utility: 0,
patterns: [
{
name: 'pattern-one',
title: 'Pattern One',
},
{
name: 'pattern-two',
title: 'Pattern Two',
},
{
name: 'pattern-three',
title: 'Pattern Three',
},
],
};

export const advancedTextItem = {
id: 'core/advanced-text-block',
name: 'core/advanced-text-block',
Expand Down Expand Up @@ -87,6 +111,7 @@ export const reusableItem = {

export default [
textItem,
withPatternsItem,
advancedTextItem,
someOtherItem,
moreItem,
Expand Down
16 changes: 16 additions & 0 deletions packages/block-editor/src/components/inserter/test/search-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,20 @@ describe( 'searchItems', () => {
[ youtubeItem ]
);
} );

it( 'should match words using also patterns and return all matched patterns', () => {
const filteredItems = searchItems( items, categories, collections, 'pattern' );

expect( filteredItems ).toHaveLength( 1 );
expect( filteredItems[ 0 ].patterns ).toHaveLength( 3 );
} );

it( 'should match words using also patterns and filter out unmatched patterns', () => {
const filteredItems = searchItems( items, categories, collections, 'patterns two three' );

expect( filteredItems ).toHaveLength( 1 );
expect( filteredItems[ 0 ].patterns ).toHaveLength( 2 );
expect( filteredItems[ 0 ].patterns[ 0 ].title ).toBe( 'Pattern Two' );
expect( filteredItems[ 0 ].patterns[ 1 ].title ).toBe( 'Pattern Three' );
} );
} );
6 changes: 6 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1255,15 +1255,21 @@ export const getInserterItems = createSelector(

const isContextual = isArray( blockType.parent );
const { time, count = 0 } = getInsertUsage( state, id ) || {};
const inserterPatterns = blockType.patterns.filter(
( { scope } ) => ! scope || scope.includes( 'inserter' )
);

return {
id,
name: blockType.name,
initialAttributes: {},
title: blockType.title,
description: blockType.description,
icon: blockType.icon,
category: blockType.category,
keywords: blockType.keywords,
patterns: inserterPatterns,
example: blockType.example,
isDisabled,
utility: calculateUtility( blockType.category, count, isContextual ),
frecency: calculateFrecency( time, count ),
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,7 @@ describe( 'selectors', () => {
},
category: 'formatting',
keywords: [ 'testing' ],
patterns: [],
isDisabled: false,
utility: 0,
frecency: 0,
Expand Down
4 changes: 2 additions & 2 deletions packages/block-library/src/columns/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ const ColumnsEdit = ( props ) => {

return {
blockType: getBlockType( name ),
defaultPattern: __experimentalGetDefaultBlockPattern( name ),
defaultPattern: __experimentalGetDefaultBlockPattern( name, 'block' ),
hasInnerBlocks: select( 'core/block-editor' ).getBlocks( clientId ).length > 0,
patterns: __experimentalGetBlockPatterns( name ),
patterns: __experimentalGetBlockPatterns( name, 'block' ),
};
}, [ clientId, name ] );

Expand Down
Loading

0 comments on commit bc38a3d

Please sign in to comment.