diff --git a/packages/block-editor/src/components/link-control/constants.js b/packages/block-editor/src/components/link-control/constants.js index eaf07aea73703..e70ff7b04c747 100644 --- a/packages/block-editor/src/components/link-control/constants.js +++ b/packages/block-editor/src/components/link-control/constants.js @@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n'; // order to handle it as a unique case. export const CREATE_TYPE = '__CREATE__'; export const TEL_TYPE = 'tel'; -export const URL_TYPE = 'URL'; +export const URL_TYPE = 'link'; export const MAILTO_TYPE = 'mailto'; export const INTERNAL_TYPE = 'internal'; diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 1786b516df5c0..6a53fd36cf893 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -1,21 +1,15 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { MenuItem } from '@wordpress/components'; import { createInterpolateElement } from '@wordpress/element'; -import { Icon, plus } from '@wordpress/icons'; +import { plus } from '@wordpress/icons'; export const LinkControlSearchCreate = ( { searchTerm, onClick, itemProps, - isSelected, buttonText, } ) => { if ( ! searchTerm ) { @@ -40,27 +34,15 @@ export const LinkControlSearchCreate = ( { } return ( - + { text } + ); }; 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 250b28596f4f3..976bb4420cb0c 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -1,14 +1,8 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ -import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; -import { Button, TextHighlight } from '@wordpress/components'; +import { MenuItem, TextHighlight } from '@wordpress/components'; import { Icon, globe, @@ -19,6 +13,7 @@ import { file, } from '@wordpress/icons'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; +import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; const ICONS_MAP = { post: postList, @@ -52,50 +47,33 @@ function SearchItemIcon( { isURL, suggestion } ) { export const LinkControlSearchItem = ( { itemProps, suggestion, - isSelected = false, + searchTerm, onClick, isURL = false, - searchTerm = '', shouldShowType = false, } ) => { + const info = isURL + ? __( 'Press ENTER to add this link' ) + : filterURLForDisplay( safeDecodeURI( suggestion?.url ) ); + return ( - + + ); }; 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 9d7ee7ca41abd..71e258c769bf1 100644 --- a/packages/block-editor/src/components/link-control/search-results.js +++ b/packages/block-editor/src/components/link-control/search-results.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { VisuallyHidden } from '@wordpress/components'; +import { VisuallyHidden, MenuGroup } from '@wordpress/components'; /** * External dependencies @@ -72,59 +72,61 @@ export default function LinkControlSearchResults( { className={ resultsListClasses } aria-labelledby={ searchResultsLabelId } > - { suggestions.map( ( suggestion, index ) => { - if ( - shouldShowCreateSuggestion && - CREATE_TYPE === suggestion.type - ) { + + { suggestions.map( ( suggestion, index ) => { + if ( + shouldShowCreateSuggestion && + CREATE_TYPE === suggestion.type + ) { + return ( + + handleSuggestionClick( suggestion ) + } + // Intentionally only using `type` here as + // the constant is enough to uniquely + // identify the single "CREATE" suggestion. + key={ suggestion.type } + itemProps={ buildSuggestionItemProps( + suggestion, + index + ) } + isSelected={ index === selectedSuggestion } + /> + ); + } + + // If we're not handling "Create" suggestions above then + // we don't want them in the main results so exit early. + if ( CREATE_TYPE === suggestion.type ) { + return null; + } + return ( - - handleSuggestionClick( suggestion ) - } - // Intentionally only using `type` here as - // the constant is enough to uniquely - // identify the single "CREATE" suggestion. - key={ suggestion.type } + { + handleSuggestionClick( suggestion ); + } } isSelected={ index === selectedSuggestion } + isURL={ LINK_ENTRY_TYPES.includes( + suggestion.type + ) } + searchTerm={ currentInputValue } + shouldShowType={ shouldShowSuggestionsTypes } + isFrontPage={ suggestion?.isFrontPage } /> ); - } - - // If we're not handling "Create" suggestions above then - // we don't want them in the main results so exit early. - if ( CREATE_TYPE === suggestion.type ) { - return null; - } - - return ( - { - handleSuggestionClick( suggestion ); - } } - isSelected={ index === selectedSuggestion } - isURL={ LINK_ENTRY_TYPES.includes( - suggestion.type - ) } - searchTerm={ currentInputValue } - shouldShowType={ shouldShowSuggestionsTypes } - isFrontPage={ suggestion?.isFrontPage } - /> - ); - } ) } + } ) } + ); diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 2dd57e56c3422..2c4b77f342310 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -41,6 +41,7 @@ $preview-image-height: 140px; // Provides positioning context for reset button. Without this then when an // error notice is displayed the input's reset button is incorrectly positioned. .block-editor-link-control__search-input-wrapper { + margin-bottom: $grid-unit-10; position: relative; } @@ -77,10 +78,11 @@ $preview-image-height: 140px; @include input-control; width: calc(100% - #{$grid-unit-20 * 2}); display: block; - padding: 11px $grid-unit-20; + padding: $grid-unit-10 $grid-unit-20; margin: 0; position: relative; - border: 1px solid $gray-300; + border: 1px solid $gray-600; + height: 40px; border-radius: $radius-block-ui; } } @@ -97,37 +99,9 @@ $preview-image-height: 140px; order: 20; } -.block-editor-link-control__search-results-wrapper { - position: relative; - margin-top: -$grid-unit-20 + 1px; - - &::before, - &::after { - content: ""; - position: absolute; - left: -1px; - right: $grid-unit-20; // avoid overlaying scrollbars - display: block; - pointer-events: none; - z-index: 100; - } - - &::before { - height: $grid-unit-20 * 0.5; - top: 0; - bottom: auto; - } - - &::after { - height: $grid-unit-20; - bottom: 0; - top: auto; - } -} - .block-editor-link-control__search-results { - margin: 0; - padding: $grid-unit-20 * 0.5 $grid-unit-20 $grid-unit-20 * 0.5; + margin-top: -$grid-unit-20; + padding: $grid-unit-10; max-height: 200px; overflow-y: auto; // allow results list to scroll @@ -137,39 +111,28 @@ $preview-image-height: 140px; } .block-editor-link-control__search-item { - position: relative; - display: flex; - align-items: flex-start; // when link text is very long it is important this indicator remains visible and thus should be aligned top. - font-size: $default-font-size; - cursor: pointer; - background: $white; - width: 100%; - border: none; - text-align: left; - padding: $grid-unit-15 $grid-unit-20; - border-radius: 2px; - height: auto; - &:hover, - &:focus { - background-color: $gray-100; + &.components-button.components-menu-item__button { + height: auto; + text-align: left; + } - .block-editor-link-control__search-item-type { - background: $white; - } + .components-menu-item__item { + overflow: hidden; + text-overflow: ellipsis; + // Inline block required to preserve white space + // between `` elements and text nodes. + display: inline-block; } - // The added specificity is needed to override. - &:focus:not(:disabled) { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color) inset; + .components-menu-item__shortcut { + color: $gray-700; + text-transform: capitalize; + white-space: nowrap; // tags shouldn't go over two lines. } - &.is-selected { + &[aria-selected] { background: $gray-100; - - .block-editor-link-control__search-item-type { - background: $white; - } } &.is-current { @@ -209,7 +172,6 @@ $preview-image-height: 140px; .block-editor-link-control__search-item-icon { position: relative; - top: 0.2em; margin-right: $grid-unit-10; max-height: 24px; flex-shrink: 0; @@ -228,18 +190,6 @@ $preview-image-height: 140px; max-height: 32px; } - .block-editor-link-control__search-item-info, - .block-editor-link-control__search-item-title { - overflow: hidden; - text-overflow: ellipsis; - - .components-external-link__icon { - position: absolute; - right: 0; - margin-top: 0; - } - } - .block-editor-link-control__search-item-title { display: block; margin-bottom: 0.2em; @@ -261,28 +211,6 @@ $preview-image-height: 140px; } } - .block-editor-link-control__search-item-info { - display: block; - color: $gray-700; - font-size: 0.9em; - line-height: 1.3; - } - - .block-editor-link-control__search-item-error-notice { - font-style: italic; - font-size: 1.1em; - } - - .block-editor-link-control__search-item-type { - display: block; - padding: 3px 6px; - margin-left: auto; - font-size: 0.9em; - background-color: $gray-100; - border-radius: 2px; - white-space: nowrap; // tags shouldn't go over two lines. - } - .block-editor-link-control__search-item-description { padding-top: 12px; margin: 0; @@ -422,11 +350,6 @@ $preview-image-height: 140px; } } -// Specificity override -.block-editor-link-control__search-results div[role="menu"] > .block-editor-link-control__search-item.block-editor-link-control__search-item { - padding: 10px; -} - .block-editor-link-control__drawer { display: flex; // allow for ordering. order: 30; 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 e5084844946be..8e4cd1634b2e7 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -476,16 +476,16 @@ describe( 'Searching for a link', () => { // The fallback URL suggestion should not be shown when input is not URL-like. expect( searchResultElements[ searchResultElements.length - 1 ] - ).not.toHaveTextContent( 'URL' ); + ).not.toHaveTextContent( 'Press ENTER to add this link' ); } ); it.each( [ - [ 'https://wordpress.org', 'URL' ], - [ 'http://wordpress.org', 'URL' ], - [ 'www.wordpress.org', 'URL' ], - [ 'wordpress.org', 'URL' ], - [ 'ftp://wordpress.org', 'URL' ], + [ 'https://wordpress.org', 'link' ], + [ 'http://wordpress.org', 'link' ], + [ 'www.wordpress.org', 'link' ], + [ 'wordpress.org', 'link' ], + [ 'ftp://wordpress.org', 'link' ], [ 'mailto:hello@wordpress.org', 'mailto' ], [ 'tel:123456789', 'tel' ], [ '#internal', 'internal' ], @@ -582,6 +582,37 @@ describe( 'Searching for a link', () => { expect( mockFetchSearchSuggestions ).not.toHaveBeenCalled(); } ); + it( 'should not display a URL suggestion when input is not likely to be a URL.', async () => { + const searchTerm = 'unlikelytobeaURL'; + const user = userEvent.setup(); + render( ); + + // Search Input UI. + const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); + + // Simulate searching for a term. + await user.type( searchInput, searchTerm ); + + const searchResultElements = within( + await screen.findByRole( 'listbox', { + name: /Search results for.*/, + } ) + ).getAllByRole( 'option' ); + + const lastSearchResultItem = + searchResultElements[ searchResultElements.length - 1 ]; + + // We should see a search result for each of the expect search suggestions. + expect( searchResultElements ).toHaveLength( + fauxEntitySuggestions.length + ); + + // The URL search suggestion should not exist. + expect( lastSearchResultItem ).not.toHaveTextContent( + 'Press ENTER to add this link' + ); + } ); + it( 'should not display a URL suggestion as a default fallback when noURLSuggestion is passed.', async () => { const user = userEvent.setup(); render( ); @@ -630,7 +661,6 @@ describe( 'Manual link entry', () => { expect( searchResultElements ).toBeVisible(); expect( searchResultElements ).toHaveTextContent( searchTerm ); - expect( searchResultElements ).toHaveTextContent( 'URL' ); expect( searchResultElements ).toHaveTextContent( 'Press ENTER to add this link' ); diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 064ed81943de1..384fa4c653e53 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -243,7 +243,7 @@ function InlineLinkUI( { return createInterpolateElement( sprintf( /* translators: %s: search term. */ - __( 'Create Page: %s' ), + __( 'Create page: %s' ), searchTerm ), { mark: } diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js index bdfa4de3131d9..13ea27b60e77e 100644 --- a/test/e2e/specs/editor/blocks/navigation.spec.js +++ b/test/e2e/specs/editor/blocks/navigation.spec.js @@ -1304,7 +1304,9 @@ class LinkControl { await expect( result ).toBeVisible(); return result - .locator( '.block-editor-link-control__search-item-title' ) // this is the only way to get the label text without the URL. + .locator( + '.components-menu-item__info-wrapper .components-menu-item__item' + ) // this is the only way to get the label text without the URL. .innerText(); } }