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

Navigation: Add Post, Page, Category and Tag variations to Link #24670

Merged
merged 7 commits into from
Aug 26, 2020
Merged
35 changes: 23 additions & 12 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,21 @@ import { ViewerFill } from './viewer-slot';
/**
* @typedef WPLinkControlProps
*
* @property {(WPLinkControlSetting[])=} settings An array of settings objects. Each object will used to
* render a `ToggleControl` for that setting.
* @property {boolean=} forceIsEditingLink If passed as either `true` or `false`, controls the
* internal editing state of the component to respective
* show or not show the URL input field.
* @property {WPLinkControlValue=} value Current link value.
* @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if
* the user selects a new link or updates settings.
* @property {boolean=} noDirectEntry Whether to disable direct entries or not.
* @property {boolean=} showSuggestions Whether to present suggestions when typing the URL.
* @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately.
* @property {boolean=} withCreateSuggestion Whether to allow creation of link value from suggestion.
* @property {(WPLinkControlSetting[])=} settings An array of settings objects. Each object will used to
* render a `ToggleControl` for that setting.
* @property {boolean=} forceIsEditingLink If passed as either `true` or `false`, controls the
* internal editing state of the component to respective
* show or not show the URL input field.
* @property {WPLinkControlValue=} value Current link value.
* @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if
* the user selects a new link or updates settings.
* @property {boolean=} noDirectEntry Whether to disable direct entries or not.
* @property {boolean=} showSuggestions Whether to present suggestions when typing the URL.
* @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately.
* @property {boolean=} withCreateSuggestion Whether to allow creation of link value from suggestion.
* @property {Object=} suggestionsQuery Query parameters to pass along to wp.blockEditor.__experimentalFetchLinkSuggestions.
* @property {boolean=} noURLSuggestion Whether to disable suggesting the search query as a URL.
* @property {string|Function|undefined} createSuggestionButtonText Text to use in the button that creates a suggestion.
*/

/**
Expand All @@ -109,6 +112,9 @@ function LinkControl( {
createSuggestion,
withCreateSuggestion,
inputValue: propInputValue = '',
suggestionsQuery = {},
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
noURLSuggestion = false,
createSuggestionButtonText,
} ) {
if ( withCreateSuggestion === undefined && createSuggestion ) {
withCreateSuggestion = true;
Expand Down Expand Up @@ -209,6 +215,11 @@ function LinkControl( {
showInitialSuggestions={ showInitialSuggestions }
allowDirectEntry={ ! noDirectEntry }
showSuggestions={ showSuggestions }
suggestionsQuery={ suggestionsQuery }
withURLSuggestion={ ! noURLSuggestion }
createSuggestionButtonText={
createSuggestionButtonText
}
>
<div className="block-editor-link-control__search-actions">
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import { isFunction } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -15,11 +16,26 @@ export const LinkControlSearchCreate = ( {
onClick,
itemProps,
isSelected,
buttonText,
} ) => {
if ( ! searchTerm ) {
return null;
}

let text;
if ( buttonText ) {
text = isFunction( buttonText ) ? buttonText( searchTerm ) : buttonText;
} else {
text = createInterpolateElement(
sprintf(
/* translators: %s: search term. */
__( 'Create: <mark>%s</mark>' ),
searchTerm
),
{ mark: <mark /> }
);
}

return (
<Button
{ ...itemProps }
Expand All @@ -38,14 +54,7 @@ export const LinkControlSearchCreate = ( {

<span className="block-editor-link-control__search-item-header">
<span className="block-editor-link-control__search-item-title">
{ createInterpolateElement(
sprintf(
/* translators: %s: search term. */
__( 'New page: <mark>%s</mark>' ),
searchTerm
),
{ mark: <mark /> }
) }
{ text }
</span>
</span>
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,17 @@ const LinkControlSearchInput = forwardRef(
fetchSuggestions = null,
allowDirectEntry = true,
showInitialSuggestions = false,
suggestionsQuery = {},
withURLSuggestion = true,
createSuggestionButtonText,
},
ref
) => {
const genericSearchHandler = useSearchHandler(
suggestionsQuery,
allowDirectEntry,
withCreateSuggestion
withCreateSuggestion,
withURLSuggestion
);
const searchHandler = showSuggestions
? fetchSuggestions || genericSearchHandler
Expand Down Expand Up @@ -75,6 +80,7 @@ const LinkControlSearchInput = forwardRef(
instanceId,
withCreateSuggestion,
currentInputValue: value,
createSuggestionButtonText,
handleSuggestionClick: ( suggestion ) => {
if ( props.handleSuggestionClick ) {
props.handleSuggestionClick( suggestion );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export const LinkControlSearchItem = ( {
</span>
{ suggestion.type && (
<span className="block-editor-link-control__search-item-type">
{ suggestion.type }
{ /* Rename 'post_tag' to 'tag'. Ideally, the API would return the localised CPT or taxonomy label. */ }
{ suggestion.type === 'post_tag' ? 'tag' : suggestion.type }
</span>
) }
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default function LinkControlSearchResults( {
selectedSuggestion,
isLoading,
isInitialSuggestions,
createSuggestionButtonText,
} ) {
const resultsListClasses = classnames(
'block-editor-link-control__search-results',
Expand Down Expand Up @@ -87,6 +88,7 @@ export default function LinkControlSearchResults( {
return (
<LinkControlSearchCreate
searchTerm={ currentInputValue }
buttonText={ createSuggestionButtonText }
onClick={ () =>
handleSuggestionClick( suggestion )
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export const fauxEntitySuggestions = [
/* eslint-disable no-unused-vars */
export const fetchFauxEntitySuggestions = (
val = '',
{ perPage = null } = {}
{ isInitialSuggestions } = {}
) => {
const suggestions = perPage
? take( fauxEntitySuggestions, perPage )
const suggestions = isInitialSuggestions
? take( fauxEntitySuggestions, 3 )
: fauxEntitySuggestions;
return Promise.resolve( suggestions );
};
Expand Down
81 changes: 76 additions & 5 deletions packages/block-editor/src/components/link-control/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,33 @@ describe( 'Searching for a link', () => {
);
}
);

it( 'should not display a URL suggestion as a default fallback when noURLSuggestion is passed.', async () => {
act( () => {
render( <LinkControl noURLSuggestion />, container );
} );

// Search Input UI
const searchInput = getURLInput();

// Simulate searching for a term
act( () => {
Simulate.change( searchInput, {
target: { value: 'couldbeurlorentitysearchterm' },
} );
} );

// fetchFauxEntitySuggestions resolves on next "tick" of event loop
await eventLoopTick();
// TODO: select these by aria relationship to autocomplete rather than arbitrary selector.

const searchResultElements = getSearchResults();

// We should see a search result for each of the expect search suggestions and nothing else
expect( searchResultElements ).toHaveLength(
fauxEntitySuggestions.length
);
} );
} );

describe( 'Manual link entry', () => {
Expand Down Expand Up @@ -725,7 +752,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {

const createButton = first(
Array.from( searchResultElements ).filter( ( result ) =>
result.innerHTML.includes( 'New page' )
result.innerHTML.includes( 'Create:' )
)
);

Expand Down Expand Up @@ -822,7 +849,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {

const createButton = first(
Array.from( searchResultElements ).filter( ( result ) =>
result.innerHTML.includes( 'New page' )
result.innerHTML.includes( 'Create:' )
)
);

Expand Down Expand Up @@ -895,7 +922,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
const form = container.querySelector( 'form' );
const createButton = first(
Array.from( searchResultElements ).filter( ( result ) =>
result.innerHTML.includes( 'New page' )
result.innerHTML.includes( 'Create:' )
)
);

Expand Down Expand Up @@ -925,6 +952,50 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
);
} );

it( 'should allow customisation of button text', async () => {
const entityNameText = 'A new page to be created';

const LinkControlConsumer = () => {
return (
<LinkControl
createSuggestion={ () => {} }
createSuggestionButtonText="Custom suggestion text"
/>
);
};

act( () => {
render( <LinkControlConsumer />, container );
} );

// Search Input UI
const searchInput = container.querySelector(
'input[aria-label="URL"]'
);

// Simulate searching for a term
act( () => {
Simulate.change( searchInput, {
target: { value: entityNameText },
} );
} );

await eventLoopTick();

// TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
const searchResultElements = container.querySelectorAll(
'[role="listbox"] [role="option"]'
);

const createButton = first(
Array.from( searchResultElements ).filter( ( result ) =>
result.innerHTML.includes( 'Custom suggestion text' )
)
);

expect( createButton ).not.toBeNull();
} );

describe( 'Do not show create option', () => {
it.each( [ [ undefined ], [ null ], [ false ] ] )(
'should not show not show an option to create an entity when "createSuggestion" handler is %s',
Expand All @@ -949,7 +1020,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
);
const createButton = first(
Array.from( searchResultElements ).filter( ( result ) =>
result.innerHTML.includes( 'New page' )
result.innerHTML.includes( 'Create:' )
)
);

Expand Down Expand Up @@ -1074,7 +1145,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
);
let createButton = first(
Array.from( searchResultElements ).filter( ( result ) =>
result.innerHTML.includes( 'New page' )
result.innerHTML.includes( 'Create:' )
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,18 @@ export const handleDirectEntry = ( val ) => {
] );
};

export const handleEntitySearch = async (
const handleEntitySearch = async (
val,
args,
suggestionsQuery,
fetchSearchSuggestions,
directEntryHandler,
withCreateSuggestion
withCreateSuggestion,
withURLSuggestion
) => {
const { isInitialSuggestions } = suggestionsQuery;

let results = await Promise.all( [
fetchSearchSuggestions( val, {
...( args.isInitialSuggestions ? { perPage: 3 } : {} ),
} ),
fetchSearchSuggestions( val, suggestionsQuery ),
directEntryHandler( val ),
] );

Expand All @@ -64,13 +65,14 @@ export const handleEntitySearch = async (
// If it's potentially a URL search then concat on a URL search suggestion
// just for good measure. That way once the actual results run out we always
// have a URL option to fallback on.
results =
couldBeURL && ! args.isInitialSuggestions
? results[ 0 ].concat( results[ 1 ] )
: results[ 0 ];
if ( couldBeURL && withURLSuggestion && ! isInitialSuggestions ) {
results = results[ 0 ].concat( results[ 1 ] );
} else {
results = results[ 0 ];
}

// If displaying initial suggestions just return plain results.
if ( args.isInitialSuggestions ) {
if ( isInitialSuggestions ) {
return results;
}

Expand Down Expand Up @@ -101,8 +103,10 @@ export const handleEntitySearch = async (
};

export default function useSearchHandler(
suggestionsQuery,
allowDirectEntry,
withCreateSuggestion
withCreateSuggestion,
withURLSuggestion
) {
const { fetchSearchSuggestions } = useSelect( ( select ) => {
const { getSettings } = select( 'core/block-editor' );
Expand All @@ -117,15 +121,16 @@ export default function useSearchHandler(
: handleNoop;

return useCallback(
( val, args ) => {
( val, { isInitialSuggestions } ) => {
return isURLLike( val )
? directEntryHandler( val, args )
? directEntryHandler( val, { isInitialSuggestions } )
: handleEntitySearch(
val,
args,
{ ...suggestionsQuery, isInitialSuggestions },
fetchSearchSuggestions,
directEntryHandler,
withCreateSuggestion
withCreateSuggestion,
withURLSuggestion
);
},
[ directEntryHandler, fetchSearchSuggestions, withCreateSuggestion ]
Expand Down
Loading