Skip to content

Commit

Permalink
Support searching for patterns (#21944)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Duthie <andrew@andrewduthie.com>
  • Loading branch information
youknowriad and aduth authored May 4, 2020
1 parent 2a4222c commit 6fa79b0
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 128 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 5 additions & 10 deletions packages/block-editor/src/components/inserter/block-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import { compose } from '@wordpress/compose';
import BlockTypesList from '../block-types-list';
import ChildBlocks from './child-blocks';
import __experimentalInserterMenuExtension from '../inserter-menu-extension';
import { searchItems } from './search-items';
import { searchBlockItems } from './search-items';
import InserterPanel from './panel';
import InserterNoResults from './no-results';

// Copied over from the Columns block. It seems like it should become part of public API.
const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => {
Expand Down Expand Up @@ -114,7 +115,7 @@ function InserterBlockList( {
};

const filteredItems = useMemo( () => {
return searchItems( items, categories, collections, filterValue );
return searchBlockItems( items, categories, collections, filterValue );
}, [ filterValue, items, categories, collections ] );

const childItems = useMemo( () => {
Expand Down Expand Up @@ -282,19 +283,13 @@ function InserterBlockList( {
{ ( fills ) => {
if ( fills.length ) {
return (
<InserterPanel
title={ _x( 'Search Results', 'blocks' ) }
>
<InserterPanel title={ __( 'Search Results' ) }>
{ fills }
</InserterPanel>
);
}
if ( ! hasItems ) {
return (
<p className="block-editor-inserter__no-results">
{ __( 'No blocks found.' ) }
</p>
);
return <InserterNoResults />;
}
return null;
} }
Expand Down
33 changes: 25 additions & 8 deletions packages/block-editor/src/components/inserter/block-patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { __, sprintf, _x } from '@wordpress/i18n';
import BlockPreview from '../block-preview';
import useAsyncList from './use-async-list';
import InserterPanel from './panel';
import { searchItems } from './search-items';
import InserterNoResults from './no-results';

function BlockPattern( { pattern, onClick } ) {
const { content } = pattern;
Expand All @@ -30,13 +32,16 @@ function BlockPattern( { pattern, onClick } ) {
onClick={ () => onClick( pattern, blocks ) }
onKeyDown={ ( event ) => {
if ( ENTER === event.keyCode || SPACE === event.keyCode ) {
onClick( blocks );
onClick( pattern, blocks );
}
} }
tabIndex={ 0 }
aria-label={ pattern.title }
>
<BlockPreview blocks={ blocks } />
<div className="block-editor-inserter__patterns-item-title">
{ pattern.title }
</div>
</div>
);
}
Expand All @@ -47,8 +52,12 @@ function BlockPatternPlaceholder() {
);
}

function BlockPatterns( { patterns, onInsert } ) {
const currentShownPatterns = useAsyncList( patterns );
function BlockPatterns( { patterns, onInsert, filterValue } ) {
const filteredPatterns = useMemo(
() => searchItems( patterns, filterValue ),
[ filterValue, patterns ]
);
const currentShownPatterns = useAsyncList( filteredPatterns );
const { createSuccessNotice } = useDispatch( 'core/notices' );
const onClickPattern = useCallback( ( pattern, blocks ) => {
onInsert( map( blocks, ( block ) => cloneBlock( block ) ) );
Expand All @@ -64,20 +73,28 @@ function BlockPatterns( { patterns, onInsert } ) {
);
}, [] );

return (
<InserterPanel title={ _x( 'All', 'patterns' ) }>
{ patterns.map( ( pattern, index ) =>
return !! filteredPatterns.length ? (
<InserterPanel
title={
filterValue
? __( 'Search Results' )
: _x( 'All', 'patterns categories' )
}
>
{ filteredPatterns.map( ( pattern, index ) =>
currentShownPatterns[ index ] === pattern ? (
<BlockPattern
key={ index }
key={ pattern.name }
pattern={ pattern }
onClick={ onClickPattern }
/>
) : (
<BlockPatternPlaceholder key={ index } />
<BlockPatternPlaceholder key={ pattern.name } />
)
) }
</InserterPanel>
) : (
<InserterNoResults />
);
}

Expand Down
11 changes: 6 additions & 5 deletions packages/block-editor/src/components/inserter/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,7 @@ function InserterMenu( {
hideInsertionPoint,
} = useDispatch( 'core/block-editor' );
const hasPatterns =
! destinationRootClientId &&
!! patterns &&
!! patterns.length &&
! filterValue;
! destinationRootClientId && !! patterns && !! patterns.length;
const onKeyDown = ( event ) => {
if (
includes(
Expand Down Expand Up @@ -165,7 +162,11 @@ function InserterMenu( {

const patternsTab = (
<div className="block-editor-inserter__scrollable">
<BlockPatterns patterns={ patterns } onInsert={ onInsertBlocks } />
<BlockPatterns
patterns={ patterns }
onInsert={ onInsertBlocks }
filterValue={ filterValue }
/>
</div>
);

Expand Down
19 changes: 19 additions & 0 deletions packages/block-editor/src/components/inserter/no-results.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Icon, blockDefault } from '@wordpress/icons';

function InserterNoResults() {
return (
<div className="block-editor-inserter__no-results">
<Icon
className="block-editor-inserter__no-results-icon"
icon={ blockDefault }
/>
<p>{ __( 'No results found.' ) }</p>
</div>
);
}

export default InserterNoResults;
163 changes: 83 additions & 80 deletions packages/block-editor/src/components/inserter/search-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
deburr,
differenceWith,
find,
get,
intersectionWith,
isEmpty,
words,
Expand Down Expand Up @@ -44,94 +43,98 @@ const removeMatchingTerms = ( unmatchedTerms, unprocessedTerms ) => {
);
};

export const searchBlockItems = (
items,
categories,
collections,
searchTerm
) => {
const normalizedSearchTerms = normalizeSearchTerm( searchTerm );
if ( normalizedSearchTerms.length === 0 ) {
return items;
}

return searchItems( items, searchTerm, {
getCategory: ( item ) =>
find( categories, { slug: item.category } )?.title,
getCollection: ( item ) =>
collections[ item.name.split( '/' )[ 0 ] ]?.title,
getVariations: ( item ) =>
( item.variations || [] ).map( ( variation ) => variation.title ),
} ).map( ( item ) => {
if ( isEmpty( item.variations ) ) {
return item;
}

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

return {
...item,
variations: matchedVariations,
};
} );
};

/**
* Filters an item list given a search term.
*
* @param {Array} items Item list
* @param {Array} categories Available categories.
* @param {Array} collections Available collections.
* @param {string} searchTerm Search term.
*
* @return {Array} Filtered item list.
* @param {Object} config Search Config.
* @return {Array} Filtered item list.
*/
export const searchItems = ( items, categories, collections, searchTerm ) => {
export const searchItems = ( items, searchTerm, config = {} ) => {
const normalizedSearchTerms = normalizeSearchTerm( searchTerm );

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

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

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

unmatchedTerms = removeMatchingTerms(
unmatchedTerms,
keywords.join( ' ' )
);

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

unmatchedTerms = removeMatchingTerms(
unmatchedTerms,
get( find( categories, { slug: category } ), [ 'title' ] )
);

const itemCollection = collections[ name.split( '/' )[ 0 ] ];
if ( itemCollection ) {
unmatchedTerms = removeMatchingTerms(
unmatchedTerms,
itemCollection.title
);
}

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

unmatchedTerms = removeMatchingTerms(
unmatchedTerms,
variations
.map( ( variation ) => variation.title )
.join( ' ' )
);

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

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

return {
...item,
variations: matchedVariations,
};
} );
const defaultGetTitle = ( item ) => item.title;
const defaultGetKeywords = ( item ) => item.keywords || [];
const defaultGetCategory = ( item ) => item.category;
const defaultGetCollection = () => null;
const defaultGetVariations = () => [];
const {
getTitle = defaultGetTitle,
getKeywords = defaultGetKeywords,
getCategory = defaultGetCategory,
getCollection = defaultGetCollection,
getVariations = defaultGetVariations,
} = config;

return items.filter( ( item ) => {
const title = getTitle( item );
const keywords = getKeywords( item );
const category = getCategory( item );
const collection = getCollection( item );
const variations = getVariations( item );

const terms = [
title,
...keywords,
category,
collection,
...variations,
].join( ' ' );

const unmatchedTerms = removeMatchingTerms(
normalizedSearchTerms,
terms
);

return unmatchedTerms.length === 0;
} );
};
14 changes: 12 additions & 2 deletions packages/block-editor/src/components/inserter/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,15 @@ $block-inserter-tabs-height: 44px;
}

.block-editor-inserter__no-results {
font-style: italic;
padding: 24px;
padding: $grid-unit-40;
margin-top: $grid-unit-40 * 2;
text-align: center;
}

.block-editor-inserter__no-results-icon {
fill: $light-gray-800;
}

.block-editor-inserter__child-blocks {
padding: 0 $grid-unit-20;
}
Expand Down Expand Up @@ -262,3 +266,9 @@ $block-inserter-tabs-height: 44px;
min-height: 100px;
}
}

.block-editor-inserter__patterns-item-title {
padding: $grid-unit-05;
font-size: 12px;
text-align: center;
}
Loading

0 comments on commit 6fa79b0

Please sign in to comment.