Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Support searching for patterns
  • Loading branch information
youknowriad committed May 4, 2020
commit c451082d97777ec6d16911a4058b900dcb1527e1
9 changes: 4 additions & 5 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 @@ -291,9 +292,7 @@ function InserterBlockList( {
}
if ( ! hasItems ) {
return (
<p className="block-editor-inserter__no-results">
{ __( 'No blocks found.' ) }
</p>
<InserterNoResults filterValue={ filterValue } />
);
}
return null;
Expand Down
24 changes: 19 additions & 5 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 Down Expand Up @@ -47,8 +49,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,9 +70,15 @@ function BlockPatterns( { patterns, onInsert } ) {
);
}, [] );

return (
<InserterPanel title={ _x( 'All', 'patterns' ) }>
{ patterns.map( ( pattern, index ) =>
return !! filteredPatterns.length ? (
<InserterPanel
title={
filterValue
? _x( 'Search Results', 'patterns' )
: _x( 'All', 'patterns' )
}
>
{ filteredPatterns.map( ( pattern, index ) =>
currentShownPatterns[ index ] === pattern ? (
<BlockPattern
key={ index }
Expand All @@ -78,6 +90,8 @@ function BlockPatterns( { patterns, onInsert } ) {
)
) }
</InserterPanel>
) : (
<InserterNoResults filterValue={ filterValue } />
);
}

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
29 changes: 29 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,29 @@
/**
* WordPress dependencies
*/
import { createInterpolateElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, blockDefault } from '@wordpress/icons';

function InserterNoResults( { filterValue } ) {
return (
<div className="block-editor-inserter__no-results">
<Icon
className="block-editor-inserter__no-results-icon"
icon={ blockDefault }
/>
<p>
{ createInterpolateElement(
sprintf(
/* translators: %s: search term. */
__( 'Sorry no results found on <strong>%s</strong>.' ),
filterValue
),
{ strong: <strong /> }
) }
</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 patterns 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 ).join( ' ' );
const category = getCategory( item );
const collection = getCollection( item );
const variations = getVariations( item ).join( ' ' );

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

const unmatchedTerms = removeMatchingTerms(
normalizedSearchTerms,
terms
);

return unmatchedTerms.length === 0;
} );
};
8 changes: 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ const assertNoResultsMessageToBePresent = ( element ) => {
const noResultsMessage = element.querySelector(
'.block-editor-inserter__no-results'
);
expect( noResultsMessage.textContent ).toEqual( 'No blocks found.' );
expect( noResultsMessage.textContent ).toEqual(
'Sorry no results found on random.'
);
};

const assertNoResultsMessageNotToBePresent = ( element ) => {
Expand All @@ -81,7 +83,9 @@ describe( 'InserterMenu', () => {
collections,
items: noItems,
} ) );
const element = initializeMenuDefaultStateAndReturnElement();
const element = initializeMenuDefaultStateAndReturnElement( {
filterValue: 'random',
} );
const visibleBlocks = element.querySelector(
'.block-editor-block-types-list__item'
);
Expand Down
Loading