diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 9dba8d36721f12..9698ceeb3d7474 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -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 allow turning a URL-like search query directly into a link. + * @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 add a fallback suggestion which treats the search query as a URL. + * @property {string|Function|undefined} createSuggestionButtonText The text to use in the button that calls createSuggestion. */ /** @@ -109,6 +112,9 @@ function LinkControl( { createSuggestion, withCreateSuggestion, inputValue: propInputValue = '', + suggestionsQuery = {}, + noURLSuggestion = false, + createSuggestionButtonText, } ) { if ( withCreateSuggestion === undefined && createSuggestion ) { withCreateSuggestion = true; @@ -209,6 +215,11 @@ function LinkControl( { showInitialSuggestions={ showInitialSuggestions } allowDirectEntry={ ! noDirectEntry } showSuggestions={ showSuggestions } + suggestionsQuery={ suggestionsQuery } + withURLSuggestion={ ! noURLSuggestion } + createSuggestionButtonText={ + createSuggestionButtonText + } >
diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index a9852d96139ece..4e5dd10cad2229 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -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 @@ -75,6 +80,7 @@ const LinkControlSearchInput = forwardRef( instanceId, withCreateSuggestion, currentInputValue: value, + createSuggestionButtonText, handleSuggestionClick: ( suggestion ) => { if ( props.handleSuggestionClick ) { props.handleSuggestionClick( suggestion ); diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js index a7ef58e36d4911..940121c0e0fe4c 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -56,7 +56,8 @@ export const LinkControlSearchItem = ( { { suggestion.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 } ) } diff --git a/packages/block-editor/src/components/link-control/search-results.js b/packages/block-editor/src/components/link-control/search-results.js index db5db4920340e5..2eac1f94ea0af1 100644 --- a/packages/block-editor/src/components/link-control/search-results.js +++ b/packages/block-editor/src/components/link-control/search-results.js @@ -28,6 +28,7 @@ export default function LinkControlSearchResults( { selectedSuggestion, isLoading, isInitialSuggestions, + createSuggestionButtonText, } ) { const resultsListClasses = classnames( 'block-editor-link-control__search-results', @@ -87,6 +88,7 @@ export default function LinkControlSearchResults( { return ( handleSuggestionClick( suggestion ) } diff --git a/packages/block-editor/src/components/link-control/test/fixtures/index.js b/packages/block-editor/src/components/link-control/test/fixtures/index.js index b470155977a30b..173d7963a8e851 100644 --- a/packages/block-editor/src/components/link-control/test/fixtures/index.js +++ b/packages/block-editor/src/components/link-control/test/fixtures/index.js @@ -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 ); }; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 3732b658d72a19..0dfaaef70c690b 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -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( , 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', () => { @@ -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:' ) ) ); @@ -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:' ) ) ); @@ -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:' ) ) ); @@ -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 ( + {} } + createSuggestionButtonText="Custom suggestion text" + /> + ); + }; + + act( () => { + render( , 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', @@ -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:' ) ) ); @@ -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:' ) ) ); diff --git a/packages/block-editor/src/components/link-control/use-search-handler.js b/packages/block-editor/src/components/link-control/use-search-handler.js index 930b5232e2fcf4..ef5e25f0765fa9 100644 --- a/packages/block-editor/src/components/link-control/use-search-handler.js +++ b/packages/block-editor/src/components/link-control/use-search-handler.js @@ -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 ), ] ); @@ -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; } @@ -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' ); @@ -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 ] diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 369c9cc044bee5..706bfc43f79a0e 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -25,7 +25,7 @@ import { ToolbarGroup, } from '@wordpress/components'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { BlockControls, InnerBlocks, @@ -35,7 +35,13 @@ import { __experimentalBlock as Block, } from '@wordpress/block-editor'; import { isURL, prependHTTP } from '@wordpress/url'; -import { Fragment, useState, useEffect, useRef } from '@wordpress/element'; +import { + Fragment, + useState, + useEffect, + useRef, + createInterpolateElement, +} from '@wordpress/element'; import { placeCaretAtHorizontalEdge } from '@wordpress/dom'; import { link as linkIcon } from '@wordpress/icons'; @@ -92,6 +98,27 @@ const useIsDraggingWithin = ( elementRef ) => { return isDraggingWithin; }; +/** + * Given the Link block's type attribute, return the query params to give to + * /wp/v2/search. + * + * @param {string} type Link block's type attribute. + * @return {{ type?: string, subtype?: string }} Search query params. + */ +function getSuggestionsQuery( type ) { + switch ( type ) { + case 'post': + case 'page': + return { type: 'post', subtype: type }; + case 'category': + return { type: 'term', subtype: 'category' }; + case 'tag': + return { type: 'term', subtype: 'post_tag' }; + default: + return {}; + } +} + function NavigationLinkEdit( { attributes, hasDescendants, @@ -107,11 +134,12 @@ function NavigationLinkEdit( { rgbBackgroundColor, selectedBlockHasDescendants, userCanCreatePages = false, + userCanCreatePosts = false, insertBlocksAfter, mergeBlocks, onReplace, } ) { - const { label, opensInNewTab, url, description, rel } = attributes; + const { label, type, opensInNewTab, url, description, rel } = attributes; const link = { url, opensInNewTab, @@ -178,16 +206,24 @@ function NavigationLinkEdit( { selection.addRange( range ); } - async function handleCreatePage( pageTitle ) { - const type = 'page'; - const page = await saveEntityRecord( 'postType', type, { + let userCanCreate = false; + if ( ! type || type === 'page' ) { + userCanCreate = userCanCreatePages; + } else if ( type === 'post' ) { + userCanCreate = userCanCreatePosts; + } + + async function handleCreate( pageTitle ) { + const postType = type || 'page'; + + const page = await saveEntityRecord( 'postType', postType, { title: pageTitle, status: 'publish', } ); return { id: page.id, - type, + postType, title: page.title.rendered, url: page.link, }; @@ -298,8 +334,29 @@ function NavigationLinkEdit( { className="wp-block-navigation-link__inline-link-input" value={ link } showInitialSuggestions={ true } - withCreateSuggestion={ userCanCreatePages } - createSuggestion={ handleCreatePage } + withCreateSuggestion={ userCanCreate } + createSuggestion={ handleCreate } + createSuggestionButtonText={ ( searchTerm ) => { + let format; + if ( type === 'post' ) { + /* translators: %s: search term. */ + format = __( + 'Create post: %s' + ); + } else { + /* translators: %s: search term. */ + format = __( + 'Create page: %s' + ); + } + return createInterpolateElement( + sprintf( format, searchTerm ), + { mark: } + ); + } } + noDirectEntry={ !! type } + noURLSuggestion={ !! type } + suggestionsQuery={ getSuggestionsQuery( type ) } onChange={ ( { title: newTitle = '', url: newURL = '', @@ -424,11 +481,6 @@ export default compose( [ selectedBlockId, ] )?.length; - const userCanCreatePages = select( 'core' ).canUser( - 'create', - 'pages' - ); - return { isParentOfSelectedBlock, isImmediateParentOfSelectedBlock, @@ -437,7 +489,8 @@ export default compose( [ showSubmenuIcon, textColor: navigationBlockAttributes.textColor, backgroundColor: navigationBlockAttributes.backgroundColor, - userCanCreatePages, + userCanCreatePages: select( 'core' ).canUser( 'create', 'pages' ), + userCanCreatePosts: select( 'core' ).canUser( 'create', 'posts' ), rgbTextColor: getColorObjectByColorSlug( colors, navigationBlockAttributes.textColor, diff --git a/packages/block-library/src/navigation-link/index.js b/packages/block-library/src/navigation-link/index.js index db12c4ad8550ac..55f73f148dc105 100644 --- a/packages/block-library/src/navigation-link/index.js +++ b/packages/block-library/src/navigation-link/index.js @@ -2,7 +2,13 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { mapMarker as icon } from '@wordpress/icons'; +import { + category as categoryIcon, + mapMarker as linkIcon, + page as pageIcon, + postTitle as postIcon, + tag as tagIcon, +} from '@wordpress/icons'; import { InnerBlocks } from '@wordpress/block-editor'; /** @@ -18,16 +24,60 @@ export { metadata, name }; export const settings = { title: __( 'Link' ), - icon, + + icon: linkIcon, + description: __( 'Add a page, link, or another item to your navigation.' ), + + variations: [ + { + name: 'link', + isDefault: true, + title: __( 'Link' ), + description: __( 'A link to a URL.' ), + attributes: {}, + }, + { + name: 'post', + icon: postIcon, + title: __( 'Post Link' ), + description: __( 'A link to a post.' ), + attributes: { type: 'post' }, + }, + { + name: 'page', + icon: pageIcon, + title: __( 'Page Link' ), + description: __( 'A link to a page.' ), + attributes: { type: 'page' }, + }, + { + name: 'category', + icon: categoryIcon, + title: __( 'Category Link' ), + description: __( 'A link to a category.' ), + attributes: { type: 'category' }, + }, + { + name: 'tag', + icon: tagIcon, + title: __( 'Tag Link' ), + description: __( 'A link to a tag.' ), + attributes: { type: 'tag' }, + }, + ], + __experimentalLabel: ( { label } ) => label, + merge( leftAttributes, { label: rightLabel = '' } ) { return { ...leftAttributes, label: leftAttributes.label + rightLabel, }; }, + edit, + save, deprecated: [ diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index 78e8e2d85a368f..ae737bc17c0775 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useRef } from '@wordpress/element'; +import { useRef, useState } from '@wordpress/element'; import { InnerBlocks, InspectorControls, @@ -48,7 +48,13 @@ function Navigation( { // HOOKS // const ref = useRef(); + + const [ isPlaceholderShown, setIsPlaceholderShown ] = useState( + ! hasExistingNavItems + ); + const { selectBlock } = useDispatch( 'core/block-editor' ); + const { TextColor, BackgroundColor, ColorPanel } = __experimentalUseColors( [ { name: 'textColor', property: 'color' }, @@ -85,13 +91,17 @@ function Navigation( { }; } - // If we don't have existing items then show the Placeholder - if ( ! hasExistingNavItems ) { + // + // RENDER + // + + if ( isPlaceholderShown ) { return ( { + setIsPlaceholderShown( false ); updateInnerBlocks( blocks ); if ( selectNavigationBlock ) { selectBlock( clientId ); @@ -107,7 +117,6 @@ function Navigation( { 'is-vertical': attributes.orientation === 'vertical', } ); - // UI State: rendered Block UI return ( <> diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 4f5a5d6492401a..f62a31a084a216 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -28,6 +28,11 @@ $navigation-item-height: 46px; padding: $grid-unit-20; } +// Ensure that an empty block has space around the appender. +.wp-block-navigation__container { + min-height: $navigation-item-height; +} + // Ensure sub-menus stay open and visible when a nested block is selected. .wp-block-navigation__container.is-parent-of-selected-block { visibility: visible; diff --git a/packages/block-library/src/navigation/placeholder.js b/packages/block-library/src/navigation/placeholder.js index b07f96b8a2cb21..ed2aa4b43afea6 100644 --- a/packages/block-library/src/navigation/placeholder.js +++ b/packages/block-library/src/navigation/placeholder.js @@ -245,8 +245,7 @@ function NavigationPlaceholder( { onCreate }, ref ) { const createFromMenu = useCallback( () => { // If an empty menu was selected, create an empty block. if ( ! menuItems.length ) { - const blocks = [ createBlock( 'core/navigation-link' ) ]; - onCreate( blocks ); + onCreate( [] ); return; } @@ -263,8 +262,7 @@ function NavigationPlaceholder( { onCreate }, ref ) { const { key } = selectedCreateOption; switch ( key ) { case CREATE_EMPTY_OPTION_VALUE: { - const blocks = [ createBlock( 'core/navigation-link' ) ]; - onCreate( blocks ); + onCreate( [] ); return; } diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index c575b3297d2a23..eb7a6c163f2f33 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -36,11 +36,7 @@ exports[`Navigation Creating from existing Menus allows a navigation block to be " `; -exports[`Navigation Creating from existing Menus creates an empty navigation block when the selected existing menu is also empty 1`] = ` -" - -" -`; +exports[`Navigation Creating from existing Menus creates an empty navigation block when the selected existing menu is also empty 1`] = `""`; exports[`Navigation Creating from existing Menus does not display option to create from existing menus if there are no menus 1`] = `""`; diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index d49a723e1de74f..7b2a81824de7a6 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -249,6 +249,17 @@ async function createEmptyNavBlock() { await clickCreateButton(); } +async function addLinkBlock() { + // Using 'click' here checks for regressions of https://github.com/WordPress/gutenberg/issues/18329, + // an issue where the block appender requires two clicks. + await page.click( '.wp-block-navigation .block-list-appender' ); + + const [ linkButton ] = await page.$x( + "//*[contains(@class, 'block-editor-inserter__quick-inserter')]//*[text()='Link']" + ); + await linkButton.click(); +} + beforeEach( async () => { await createNewPost(); } ); @@ -364,8 +375,7 @@ describe( 'Navigation', () => { ); // Assert an empty Nav Block is created. - // We expect 1 here because a "placeholder" Nav Item Block is automatically inserted - expect( navBlockItemsLength ).toEqual( 1 ); + expect( navBlockItemsLength ).toEqual( 0 ); // Snapshot should contain the mocked menu items. expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -410,7 +420,9 @@ describe( 'Navigation', () => { await createEmptyNavBlock(); - // Add a link to the default Link block. + await addLinkBlock(); + + // Add a link to the Link block. await updateActiveNavigationLink( { url: 'https://wordpress.org', label: 'WP', @@ -419,16 +431,7 @@ describe( 'Navigation', () => { await showBlockToolbar(); - // Add another block. - // Using 'click' here checks for regressions of https://github.com/WordPress/gutenberg/issues/18329, - // an issue where the block appender requires two clicks. - await page.click( '.wp-block-navigation .block-list-appender' ); - - // Select a Link block. - const [ linkButton ] = await page.$x( - "//*[contains(@class, 'block-editor-inserter__quick-inserter')]//*[text()='Link']" - ); - await linkButton.click(); + await addLinkBlock(); // After adding a new block, search input should be shown immediately. // Verify that Escape would close the popover. @@ -496,6 +499,8 @@ describe( 'Navigation', () => { // Create an empty nav block. await createEmptyNavBlock(); + await addLinkBlock(); + // Wait for URL input to be focused await page.waitForSelector( 'input.block-editor-url-input__input:focus' diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index 55ffe57d9d5159..63e47f7d1589f9 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -34,53 +34,69 @@ function disableInsertingNonNavigationBlocks( settings, name ) { /** * Fetches link suggestions from the API. This function is an exact copy of a function found at: * - * wordpress/editor/src/components/provider/index.js + * packages/editor/src/components/provider/index.js * * It seems like there is no suitable package to import this from. Ideally it would be either part of core-data. * Until we refactor it, just copying the code is the simplest solution. * * @param {string} search * @param {Object} [searchArguments] - * @param {number} [searchArguments.perPage=20] + * @param {number} [searchArguments.isInitialSuggestions] + * @param {number} [searchArguments.type] + * @param {number} [searchArguments.subtype] * @param {Object} [editorSettings] * @param {boolean} [editorSettings.disablePostFormats=false] * @return {Promise} List of suggestions */ const fetchLinkSuggestions = ( search, - { perPage = 20 } = {}, + { isInitialSuggestions, type, subtype } = {}, { disablePostFormats = false } = {} ) => { - const posts = apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - per_page: perPage, - type: 'post', - } ), - } ); + const perPage = isInitialSuggestions ? 3 : 20; - const terms = apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - per_page: perPage, - type: 'term', - } ), - } ); + const queries = []; + + if ( ! type || type === 'post' ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + per_page: perPage, + type: 'post', + subtype, + } ), + } ) + ); + } - let formats; - if ( disablePostFormats ) { - formats = Promise.resolve( [] ); - } else { - formats = apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - per_page: perPage, - type: 'post-format', - } ), - } ); + if ( ! type || type === 'term' ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + per_page: perPage, + type: 'term', + subtype, + } ), + } ) + ); + } + + if ( ! disablePostFormats && ( ! type || type === 'post-format' ) ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + per_page: perPage, + type: 'post-format', + subtype, + } ), + } ) + ); } - return Promise.all( [ posts, terms, formats ] ).then( ( results ) => { + return Promise.all( queries ).then( ( results ) => { return map( flatten( results ).slice( 0, perPage ), ( result ) => ( { id: result.id, url: result.url, diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index a0b8b9b4562860..7880e6f2bff9f5 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -32,53 +32,69 @@ import ConvertToGroupButtons from '../convert-to-group-buttons'; /** * Fetches link suggestions from the API. This function is an exact copy of a function found at: * - * wordpress/editor/src/components/provider/index.js + * packages/edit-navigation/src/index.js * * It seems like there is no suitable package to import this from. Ideally it would be either part of core-data. * Until we refactor it, just copying the code is the simplest solution. * * @param {string} search * @param {Object} [searchArguments] - * @param {number} [searchArguments.perPage=20] + * @param {number} [searchArguments.isInitialSuggestions] + * @param {number} [searchArguments.type] + * @param {number} [searchArguments.subtype] * @param {Object} [editorSettings] * @param {boolean} [editorSettings.disablePostFormats=false] * @return {Promise} List of suggestions */ const fetchLinkSuggestions = ( search, - { perPage = 20 } = {}, + { isInitialSuggestions, type, subtype } = {}, { disablePostFormats = false } = {} ) => { - const posts = apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - per_page: perPage, - type: 'post', - } ), - } ); + const perPage = isInitialSuggestions ? 3 : 20; - const terms = apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - per_page: perPage, - type: 'term', - } ), - } ); + const queries = []; - let formats; - if ( disablePostFormats ) { - formats = Promise.resolve( [] ); - } else { - formats = apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - per_page: perPage, - type: 'post-format', - } ), - } ); + if ( ! type || type === 'post' ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + per_page: perPage, + type: 'post', + subtype, + } ), + } ) + ); + } + + if ( ! type || type === 'term' ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + per_page: perPage, + type: 'term', + subtype, + } ), + } ) + ); + } + + if ( ! disablePostFormats && ( ! type || type === 'post-format' ) ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + per_page: perPage, + type: 'post-format', + subtype, + } ), + } ) + ); } - return Promise.all( [ posts, terms, formats ] ).then( ( results ) => { + return Promise.all( queries ).then( ( results ) => { return map( flatten( results ).slice( 0, perPage ), ( result ) => ( { id: result.id, url: result.url,