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

Support searching for patterns #21944

Merged
merged 11 commits into from
May 4, 2020
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,
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
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;
}
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
)
.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;
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
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( ' ' );
youknowriad marked this conversation as resolved.
Show resolved Hide resolved

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