From 8dbc2bd14ad1642b4a87706adb3457abaf05ae93 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 11:46:00 +0100 Subject: [PATCH 001/113] Initial component file structure --- packages/block-editor/src/components/link/index.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/block-editor/src/components/link/index.js diff --git a/packages/block-editor/src/components/link/index.js b/packages/block-editor/src/components/link/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 From ee008be9d98f16557a42a2a7872da8bd0e04dc5d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 12:20:00 +0100 Subject: [PATCH 002/113] Implement basic icon and toggle mechanic --- packages/block-editor/README.md | 4 ++ packages/block-editor/src/components/index.js | 1 + .../src/components/link-control/index.js | 43 ++++++++++++++ .../test/__snapshots__/index.js.snap | 3 + .../src/components/link-control/test/index.js | 59 +++++++++++++++++++ .../block-editor/src/components/link/index.js | 0 6 files changed, 110 insertions(+) create mode 100644 packages/block-editor/src/components/link-control/index.js create mode 100644 packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap create mode 100644 packages/block-editor/src/components/link-control/test/index.js delete mode 100644 packages/block-editor/src/components/link/index.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index d9c7818065c0b5..a0a7aca8d9d0c9 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -302,6 +302,10 @@ _Related_ - +# **LinkControl** + +Undocumented declaration. + # **MediaPlaceholder** _Related_ diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 6e9bb592378678..b56f5c2376b9a5 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -22,6 +22,7 @@ export { default as __experimentalGradientPickerControl } from './gradient-picke export { default as InnerBlocks } from './inner-blocks'; export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; export { default as InspectorControls } from './inspector-controls'; +export { default as LinkControl } from './link-control'; export { default as MediaPlaceholder } from './media-placeholder'; export { default as MediaUpload } from './media-upload'; export { default as MediaUploadCheck } from './media-upload/check'; diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js new file mode 100644 index 00000000000000..71f503cb709b39 --- /dev/null +++ b/packages/block-editor/src/components/link-control/index.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { + IconButton, +} from '@wordpress/components'; + +import { __ } from '@wordpress/i18n'; + +import { + useCallback, + useState, + Fragment, +} from '@wordpress/element'; + +function LinkControl() { + // State + const [ isOpen, setIsOpen ] = useState( false ); + + // Effects + const openLinkUI = useCallback( () => { + setIsOpen( true ); + } ); + + return ( + + + + { isOpen && ( + +
Link UI is open
+ + ) } +
+ ); +} + +export default LinkControl; diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..5aec2c4e26a5fe --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Basic rendering should render icon button and in closed state by default 1`] = `""`; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js new file mode 100644 index 00000000000000..3c835fca306789 --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -0,0 +1,59 @@ +/** + * External dependencies + */ +import { render, unmountComponentAtNode } from 'react-dom'; +import { act, Simulate } from 'react-dom/test-utils'; + +/** + * Internal dependencies + */ +import LinkControl from '../index'; + +let container = null; +beforeEach( () => { + // setup a DOM element as a render target + container = document.createElement( 'div' ); + document.body.appendChild( container ); +} ); + +afterEach( () => { + // cleanup on exiting + unmountComponentAtNode( container ); + container.remove(); + container = null; +} ); + +describe( 'Basic rendering', () => { + it( 'should render icon button and in closed state by default', () => { + act( () => { + render( + , container + ); + } ); + const openIconButton = container.querySelector( '[aria-label="Insert link"]' ); + const openIcon = openIconButton.querySelector( 'svg' ); + const expectedIconClassName = expect.stringContaining( 'dashicons-insert' ); + + expect( openIconButton ).not.toBeNull(); + expect( openIcon.getAttribute( 'class' ) ).toEqual( expectedIconClassName ); + + expect( container.innerHTML ).toMatchSnapshot(); + } ); + + it( 'should toggle link ui open on icon ui click', () => { + act( () => { + render( + , container + ); + } ); + const openIconButton = container.querySelector( '[aria-label="Insert link"]' ); + + act( () => { + Simulate.click( openIconButton ); + } ); + + expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Link UI is open' ) ); + } ); +} ); diff --git a/packages/block-editor/src/components/link/index.js b/packages/block-editor/src/components/link/index.js deleted file mode 100644 index e69de29bb2d1d6..00000000000000 From e68500315509aa290f2898a0d4657675c272d865 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 14:32:32 +0100 Subject: [PATCH 003/113] Adds basic search input --- .../src/components/link-control/index.js | 30 ++++++++++++++++--- .../src/components/link-control/test/index.js | 19 +++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 71f503cb709b39..f3b8365a191ddf 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -13,15 +13,32 @@ import { Fragment, } from '@wordpress/element'; -function LinkControl() { +/** + * Internal dependencies + */ +import { + URLPopover, +} from '../'; + +import { withInstanceId } from '@wordpress/compose'; + +function LinkControl( { instanceId, defaultOpen = false } ) { // State - const [ isOpen, setIsOpen ] = useState( false ); + const [ isOpen, setIsOpen ] = useState( defaultOpen ); + const [ inputValue, setInputValue ] = useState( '' ); // Effects const openLinkUI = useCallback( () => { setIsOpen( true ); } ); + // Handlers + const onInputChange = ( event ) => { + setInputValue( event.target.value ); + }; + + const inputId = `link-control-search-input-${ instanceId }`; + return ( Link UI is open + +
+ + +
+
) }
); } -export default LinkControl; +export default withInstanceId( LinkControl ); 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 3c835fca306789..5b962878e976b8 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -41,6 +41,23 @@ describe( 'Basic rendering', () => { expect( container.innerHTML ).toMatchSnapshot(); } ); + it( 'should render core link ui interface when open', () => { + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInputLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerText === 'Search or input url' ); + const searchInput = container.querySelector( 'input[type="url"]' ); + + expect( searchInputLabel ).not.toBeNull(); + expect( searchInput ).not.toBeNull(); + } ); + it( 'should toggle link ui open on icon ui click', () => { act( () => { render( @@ -54,6 +71,6 @@ describe( 'Basic rendering', () => { Simulate.click( openIconButton ); } ); - expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Link UI is open' ) ); + expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Search or input url' ) ); } ); } ); From 70049ad0496ee910ab41b495184052c2e9caa421 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 15:22:47 +0100 Subject: [PATCH 004/113] Update input to utilise LinkEditor component autocomplete --- .../src/components/link-control/index.js | 61 +++++++++++++++---- .../src/components/link-control/style.scss | 22 +++++++ .../src/components/link-control/test/index.js | 8 +-- packages/block-editor/src/style.scss | 1 + 4 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/style.scss diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f3b8365a191ddf..bb1433d9fe9f91 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -10,9 +10,19 @@ import { __ } from '@wordpress/i18n'; import { useCallback, useState, + useRef, Fragment, } from '@wordpress/element'; +import { + LEFT, + RIGHT, + UP, + DOWN, + BACKSPACE, + ENTER, +} from '@wordpress/keycodes'; + /** * Internal dependencies */ @@ -20,24 +30,38 @@ import { URLPopover, } from '../'; -import { withInstanceId } from '@wordpress/compose'; - -function LinkControl( { instanceId, defaultOpen = false } ) { +function LinkControl( { defaultOpen = false } ) { // State const [ isOpen, setIsOpen ] = useState( defaultOpen ); const [ inputValue, setInputValue ] = useState( '' ); + // Refs + const autocompleteRef = useRef( null ); + // Effects const openLinkUI = useCallback( () => { setIsOpen( true ); } ); // Handlers - const onInputChange = ( event ) => { - setInputValue( event.target.value ); + const onInputChange = ( value = '' ) => { + setInputValue( value ); }; - const inputId = `link-control-search-input-${ instanceId }`; + const onSubmitLinkChange = ( value ) => { + setInputValue( value ); + }; + + const stopPropagation = ( event ) => { + event.stopPropagation(); + }; + + const stopPropagationRelevantKeys = ( event ) => { + if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. + event.stopPropagation(); + } + }; return ( @@ -51,10 +75,25 @@ function LinkControl( { instanceId, defaultOpen = false } ) { { isOpen && ( -
- - -
+
+
+ + onInputChange() } + /> +
+
) } @@ -62,4 +101,4 @@ function LinkControl( { instanceId, defaultOpen = false } ) { ); } -export default withInstanceId( LinkControl ); +export default LinkControl; diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss new file mode 100644 index 00000000000000..cfd5148a7a1c6c --- /dev/null +++ b/packages/block-editor/src/components/link-control/style.scss @@ -0,0 +1,22 @@ +.link-control__popover-inner { + padding: 20px; +} + +.link-control__search { + position: relative; +} + +.link-control__search-input { + border: 1px solid #000; + padding-right: 36px; // width of reset button + + .components-button { + display: none; + } +} + +.link-control__search-reset { + position: absolute; + top: 0; + right: 0; +} 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 5b962878e976b8..6d3286180b4107 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -51,10 +51,10 @@ describe( 'Basic rendering', () => { } ); // Search Input UI - const searchInputLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerText === 'Search or input url' ); - const searchInput = container.querySelector( 'input[type="url"]' ); + // const searchInputLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerText === 'Search or input url' ); + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - expect( searchInputLabel ).not.toBeNull(); + // expect( searchInputLabel ).not.toBeNull(); expect( searchInput ).not.toBeNull(); } ); @@ -71,6 +71,6 @@ describe( 'Basic rendering', () => { Simulate.click( openIconButton ); } ); - expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Search or input url' ) ); + expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Paste URL or type to search' ) ); } ); } ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 8ebb2e487a4f29..ce48fb4b0a1f9b 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -18,6 +18,7 @@ @import "./components/contrast-checker/style.scss"; @import "./components/default-block-appender/style.scss"; @import "./components/gradient-picker/control.scss"; +@import "./components/link-control/style.scss"; @import "./components/inner-blocks/style.scss"; @import "./components/inserter-with-shortcuts/style.scss"; @import "./components/inserter/style.scss"; From 98e6aa57a24f7c64d7cb2b34127588c9f54118c6 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 15:57:46 +0100 Subject: [PATCH 005/113] Add ability to customise placeholder --- packages/block-editor/src/components/url-input/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 9652df56c9da74..24354055c11403 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -230,7 +230,7 @@ class URLInput extends Component { } render() { - const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder } = this.props; + const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder, placeholder = __( 'Paste URL or type to search' ) } = this.props; const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state; const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`; @@ -251,7 +251,7 @@ class URLInput extends Component { value={ value } onChange={ this.onChange } onInput={ stopEventPropagation } - placeholder={ __( 'Paste URL or type to search' ) } + placeholder={ placeholder } onKeyDown={ this.onKeyDown } role="combobox" aria-expanded={ showSuggestions } From 85a6317e3f98eb35cf90adf878361761155b7a28 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 16:15:50 +0100 Subject: [PATCH 006/113] Update to utilise URLInput directly for greater flexibility --- .../src/components/link-control/index.js | 41 ++++++++++++------- .../src/components/link-control/test/index.js | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index bb1433d9fe9f91..e25f5af7f6cfac 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -28,6 +28,7 @@ import { */ import { URLPopover, + URLInput, } from '../'; function LinkControl( { defaultOpen = false } ) { @@ -77,21 +78,33 @@ function LinkControl( { defaultOpen = false } ) {
- - onInputChange() } - /> + > + + + + onInputChange() } + /> +
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 6d3286180b4107..d2f5c6bf0f39ea 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -71,6 +71,6 @@ describe( 'Basic rendering', () => { Simulate.click( openIconButton ); } ); - expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Paste URL or type to search' ) ); + expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Search or type url' ) ); } ); } ); From 8ec14888f6b40f0dd607080fddbd6756929f798d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Oct 2019 17:07:18 +0100 Subject: [PATCH 007/113] Add example search results and test coverage --- .../src/components/link-control/index.js | 43 ++++++++++++++++++- .../src/components/link-control/style.scss | 30 +++++++++++++ .../src/components/link-control/test/index.js | 42 ++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index e25f5af7f6cfac..790897a8eea912 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -3,6 +3,8 @@ */ import { IconButton, + MenuItem, + NavigableMenu, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -64,6 +66,42 @@ function LinkControl( { defaultOpen = false } ) { } }; + // Render Components + const renderSearchResults = () => ( +
+ + + { + // do things + } } + > + WordPress! + URL + + { + // do things + } } + > + Hello World + Page + + + +
+ ); + + const shouldRenderSearchResults = !! inputValue; + return ( +
@@ -84,6 +124,7 @@ function LinkControl( { defaultOpen = false } ) { > .link-control__search-item.link-control__search-item { + padding: 10px; +} 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 d2f5c6bf0f39ea..64fe67ee3b935b 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -74,3 +74,45 @@ describe( 'Basic rendering', () => { expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Search or type url' ) ); } ); } ); + +describe( 'Searching', () => { + it( 'should render search results for current search term', () => { + act( () => { + render( + , container + ); + } ); + + let searchResultElements; + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + + expect( searchResultElements ).toHaveLength( 0 ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: 'WordPress' } } ); + } ); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + + expect( searchResultElements ).toHaveLength( 2 ); + + // Reset the search term + act( () => { + Simulate.change( searchInput, { target: { value: '' } } ); + } ); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + + expect( searchResultElements ).toHaveLength( 0 ); + } ); +} ); From 140cc907558f7413ba5209dcd42f717efb2f0b6e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 08:58:23 +0100 Subject: [PATCH 008/113] Update class naming convention to match guidelines See https://github.com/WordPress/gutenberg/blob/master/docs/contributors/coding-guidelines.md#css Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r332567521 --- .../src/components/link-control/index.js | 18 +++++++++--------- .../src/components/link-control/style.scss | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 790897a8eea912..a203848f0c5ada 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -68,11 +68,11 @@ function LinkControl( { defaultOpen = false } ) { // Render Components const renderSearchResults = () => ( -
+
WordPress! - URL + URL Hello World - Page + Page @@ -116,15 +116,15 @@ function LinkControl( { defaultOpen = false } ) { -
-
+
+
onInputChange() } /> diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index f4449d027ac882..ab270de295b27f 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -1,12 +1,12 @@ -.link-control__popover-inner { +.block-editor-link-control__popover-inner { padding: 20px; } -.link-control__search { +.block-editor-link-control__search { position: relative; } -.link-control__search-input { +.block-editor-link-control__search-input { border: 1px solid #000; padding-right: 36px; // width of reset button @@ -15,21 +15,21 @@ } } -.link-control__search-reset { +.block-editor-link-control__search-reset { position: absolute; top: 0; right: 0; } -.link-control__search-results { +.block-editor-link-control__search-results { padding: 20px; } -.link-control__search-item { +.block-editor-link-control__search-item { position: relative; - .link-control__search-item-type { + .block-editor-link-control__search-item-type { position: absolute; top: 50%; transform: translateY(-50%); @@ -47,6 +47,6 @@ } // Specificity overide -.link-control__search-results div[role="menu"] > .link-control__search-item.link-control__search-item { +.block-editor-link-control__search-results div[role="menu"] > .block-editor-link-control__search-item.block-editor-link-control__search-item { padding: 10px; } From d64608e89ff0d1ecb5a09c066e7278a9fd9d9649 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 12:21:15 +0100 Subject: [PATCH 009/113] Adds render prop to enable custom suggestions rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously it wasn’t possible to customise the render of the search suggestions. By providing an optional render prop we now have full control over this if required. --- .../src/components/url-input/index.js | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 24354055c11403..4792f5afb5bd23 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { throttle } from 'lodash'; +import { throttle, isFunction } from 'lodash'; import classnames from 'classnames'; import scrollIntoView from 'dom-scroll-into-view'; @@ -21,12 +21,15 @@ import { withSelect } from '@wordpress/data'; const stopEventPropagation = ( event ) => event.stopPropagation(); class URLInput extends Component { - constructor( { autocompleteRef } ) { - super( ...arguments ); + constructor( props ) { + super( props ); this.onChange = this.onChange.bind( this ); this.onKeyDown = this.onKeyDown.bind( this ); - this.autocompleteRef = autocompleteRef || createRef(); + this.selectLink = this.selectLink.bind( this ); + this.handleOnClick = this.handleOnClick.bind( this ); + this.bindSuggestionNode = this.bindSuggestionNode.bind( this ); + this.autocompleteRef = props.autocompleteRef || createRef(); this.inputRef = createRef(); this.updateSuggestions = throttle( this.updateSuggestions.bind( this ), 200 ); @@ -45,6 +48,7 @@ class URLInput extends Component { // when already expanded if ( showSuggestions && selectedSuggestion !== null && ! this.scrollingIntoView ) { this.scrollingIntoView = true; + scrollIntoView( this.suggestionNodes[ selectedSuggestion ], this.autocompleteRef.current, { onlyScrollIfNeeded: true, } ); @@ -230,7 +234,7 @@ class URLInput extends Component { } render() { - const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder, placeholder = __( 'Paste URL or type to search' ) } = this.props; + const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder, placeholder = __( 'Paste URL or type to search' ), renderSuggestions } = this.props; const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state; const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`; @@ -263,7 +267,17 @@ class URLInput extends Component { { ( loading ) && } - { showSuggestions && !! suggestions.length && + { isFunction( renderSuggestions ) && showSuggestions && !! suggestions.length && renderSuggestions( { + suggestions, + suggestionsListboxId, + suggestionOptionIdPrefix, + selectedSuggestion, + bindSuggestionNode: this.bindSuggestionNode, + handleSuggestionClick: this.handleOnClick, + autocompleteRef: this.autocompleteRef, + } ) } + + { ! isFunction( renderSuggestions ) && showSuggestions && !! suggestions.length && { + withSelect( ( select, props ) => { + // If a link suggestions handler is already provided then + // bail + if ( isFunction( props.fetchLinkSuggestions ) ) { + return; + } const { getSettings } = select( 'core/block-editor' ); return { fetchLinkSuggestions: getSettings().__experimentalFetchLinkSuggestions, From 30e0eb672daf98ebd9aadd56ec376154c8a1927a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 12:24:04 +0100 Subject: [PATCH 010/113] Update to utilise URLInput render prop to customise search suggestions render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we relied on our own render of suggestions but this wasn’t hooked up to all the accessibility enhancements afforded by URLInput. By utilising the render prop exposed by URLInput to customise the rendering of suggestions, we can have the best of both worlds. --- .../src/components/link-control/index.js | 93 +++++++++++-------- .../src/components/link-control/style.scss | 34 +++++-- 2 files changed, 80 insertions(+), 47 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index a203848f0c5ada..ceb87d374d1d8e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -3,12 +3,16 @@ */ import { IconButton, - MenuItem, - NavigableMenu, + Icon, } from '@wordpress/components'; - +/** + * External dependencies + */ +import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; +import { uniqueId } from 'lodash'; + import { useCallback, useState, @@ -66,41 +70,50 @@ function LinkControl( { defaultOpen = false } ) { } }; - // Render Components - const renderSearchResults = () => ( -
- - - { - // do things - } } - > - WordPress! - URL - - { - // do things - } } - > - Hello World - Page - - - -
- ); + const fetchFauxSuggestions = async () => ( [ + { + id: uniqueId(), + title: 'WordPress', + type: 'URL', + url: 'make.wordpress.com', + }, + { + id: uniqueId(), + title: 'Hello World', + type: 'Page', + info: '2 days ago', + url: '?p=1234', + }, + ] ); - const shouldRenderSearchResults = !! inputValue; + // Render Components + const renderSearchResults = function renderSearchResults( { suggestions, suggestionsListboxId, suggestionOptionIdPrefix, selectedSuggestion, bindSuggestionNode, handleSuggestionClick } ) { + return ( +
+ { suggestions.map( ( suggestion, index ) => ( + + ) ) } +
+ ); + }; return ( @@ -113,9 +126,7 @@ function LinkControl( { defaultOpen = false } ) { { isOpen && ( - +
@@ -131,6 +142,8 @@ function LinkControl( { defaultOpen = false } ) { onKeyDown={ stopPropagationRelevantKeys } onKeyPress={ stopPropagation } placeholder={ __( 'Search or type url' ) } + renderSuggestions={ renderSearchResults } + fetchLinkSuggestions={ fetchFauxSuggestions } /> input[type="text"] { // specificity overide + border: 1px solid #666; + padding-right: 36px; // width of reset button } } @@ -22,12 +20,34 @@ } .block-editor-link-control__search-results { - padding: 20px; + // padding: 20px; } .block-editor-link-control__search-item { position: relative; + display: flex; + align-items: center; + font-size: $default-font-size; + cursor: pointer; + background: $white; + width: 100%; + border: none; + text-align: left; + padding: 10px; + + span { + display: block; + } + + .dashicon { + margin-right: 1em; + } + + .block-editor-link-control__search-item-info { + color: #999; + font-size: 0.9em; + } .block-editor-link-control__search-item-type { position: absolute; From 53ce51782ceeec748a6e290d5b7c240d00f890ff Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 14:28:53 +0100 Subject: [PATCH 011/113] Update to add post type to the fetchLinkSuggestions responsive mapping This is required to display the type of entitity in the search results for LinkControl --- packages/editor/src/components/provider/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index c386f02097167b..e68f9e9ffa928c 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -40,6 +40,7 @@ const fetchLinkSuggestions = async ( search ) => { id: post.id, url: post.url, title: decodeEntities( post.title ) || __( '(no title)' ), + type: post.subtype || post.type, } ) ); }; From b53c13c86505e4bf500dec4d8254d919ea50ac2e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 14:43:06 +0100 Subject: [PATCH 012/113] Fix to ensure search suggestion interaction states are perceivable --- .../block-editor/src/components/link-control/style.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 801c1dbc0aa4df..e27aa35e377247 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -36,6 +36,15 @@ text-align: left; padding: 10px; + &:hover, + &:focus { + background-color: #e9e9e9; + } + + &.is-selected { + background: #e1e1e1; + } + span { display: block; } From 6d7259aa3a9b8dbe1893d575fa1534710e231bec Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 14:46:35 +0100 Subject: [PATCH 013/113] Update suggestion render prop to provide component props as arguments Previously when using the `renderSuggestions` render prop the user had to know how to put together the correct props on the correct elements in their custom render. By passing the default props for the listing element and the item element we can relieve the user of this burden by allowing them to spread the props onto the appropriate elements in their render without having to know how they are created. --- .../src/components/link-control/index.js | 38 +++++-------------- .../src/components/url-input/index.js | 38 +++++++++++-------- 2 files changed, 32 insertions(+), 44 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index ceb87d374d1d8e..23ea0fc85eb98e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -11,8 +11,6 @@ import { import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { uniqueId } from 'lodash'; - import { useCallback, useState, @@ -37,7 +35,7 @@ import { URLInput, } from '../'; -function LinkControl( { defaultOpen = false } ) { +function LinkControl( { defaultOpen = false, fetchSearchSuggestions } ) { // State const [ isOpen, setIsOpen ] = useState( defaultOpen ); const [ inputValue, setInputValue ] = useState( '' ); @@ -70,40 +68,21 @@ function LinkControl( { defaultOpen = false } ) { } }; - const fetchFauxSuggestions = async () => ( [ - { - id: uniqueId(), - title: 'WordPress', - type: 'URL', - url: 'make.wordpress.com', - }, - { - id: uniqueId(), - title: 'Hello World', - type: 'Page', - info: '2 days ago', - url: '?p=1234', - }, - ] ); - // Render Components - const renderSearchResults = function renderSearchResults( { suggestions, suggestionsListboxId, suggestionOptionIdPrefix, selectedSuggestion, bindSuggestionNode, handleSuggestionClick } ) { + const renderSearchResults = function renderSearchResults( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) { + /* eslint-disable react/jsx-key */ return ( -
+
{ suggestions.map( ( suggestion, index ) => (
); + /* eslint-enable react/jsx-key */ }; return ( @@ -143,7 +123,7 @@ function LinkControl( { defaultOpen = false } ) { onKeyPress={ stopPropagation } placeholder={ __( 'Search or type url' ) } renderSuggestions={ renderSearchResults } - fetchLinkSuggestions={ fetchFauxSuggestions } + fetchLinkSuggestions={ fetchSearchSuggestions } /> { + return { + key: suggestion.id, + role: 'option', + tabIndex: '-1', + id: `${ suggestionOptionIdPrefix }-${ index }`, + ref: this.bindSuggestionNode( index ), + 'aria-selected': index === selectedSuggestion, + }; + }; + + /* eslint-disable jsx-a11y/no-autofocus, react/jsx-key */ return (
{ suggestions.map( ( suggestion, index ) => ( @@ -314,7 +322,7 @@ class URLInput extends Component { }
); - /* eslint-enable jsx-a11y/no-autofocus */ + /* eslint-enable jsx-a11y/no-autofocus, react/jsx-key */ } } From 4f324293f1b1e3cbfbc95c50042e51971da5e0f3 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 16:16:27 +0100 Subject: [PATCH 014/113] Update to match with design visual and provide more accessible markup --- .../src/components/link-control/index.js | 14 ++++----- .../src/components/link-control/style.scss | 29 +++++++++++++++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 23ea0fc85eb98e..5e6806e4ecb815 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -69,17 +69,16 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions } ) { }; // Render Components - const renderSearchResults = function renderSearchResults( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) { + const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) => { /* eslint-disable react/jsx-key */ return ( -
+
    { suggestions.map( ( suggestion, index ) => ( - + { suggestion.type.toLowerCase() || '' } + + ) ) } -
+ ); /* eslint-enable react/jsx-key */ }; diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index e27aa35e377247..5eb4984413edd9 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -7,9 +7,26 @@ } .block-editor-link-control__search .block-editor-link-control__search-input { - &.block-editor-link-control__search-input > input[type="text"] { // specificity overide - border: 1px solid #666; + // Specificity overide + &.block-editor-link-control__search-input > input[type="text"] { + display: block; + padding: 11px $grid-size-large; padding-right: 36px; // width of reset button + position: relative; + z-index: 1; + border: 1px solid #e1e1e1; + border-radius: $radius-round-rectangle; + + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + + @include break-small { + font-size: $default-font-size; + } + + &:focus { + @include input-style__focus(); + } } } @@ -20,7 +37,9 @@ } .block-editor-link-control__search-results { - // padding: 20px; + margin: 0; + padding: $grid-size-large 0; + list-style: none; } @@ -75,6 +94,10 @@ } } +.block-editor-link-control__search-item-title { + font-weight: 500; +} + // Specificity overide .block-editor-link-control__search-results div[role="menu"] > .block-editor-link-control__search-item.block-editor-link-control__search-item { padding: 10px; From 4971e307f896f9b60c8e55b1cafb32672e963b2f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Oct 2019 16:49:58 +0100 Subject: [PATCH 015/113] Adds settings area. Fixes missing reset icon. --- .../src/components/link-control/index.js | 41 +++++++++++-------- .../src/components/link-control/style.scss | 7 ++-- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 5e6806e4ecb815..0630ef1123f779 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -4,12 +4,14 @@ import { IconButton, Icon, + ToggleControl, } from '@wordpress/components'; /** * External dependencies */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; +import { isFunction } from 'lodash'; import { useCallback, @@ -35,7 +37,7 @@ import { URLInput, } from '../'; -function LinkControl( { defaultOpen = false, fetchSearchSuggestions } ) { +function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAdditionalSettings } ) { // State const [ isOpen, setIsOpen ] = useState( defaultOpen ); const [ inputValue, setInputValue ] = useState( '' ); @@ -95,6 +97,15 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions } ) { /* eslint-enable react/jsx-key */ }; + const renderSettings = () => ( + + + { isFunction( renderAdditionalSettings ) && renderAdditionalSettings() } + + ); + return ( +
- - - onInputChange() } - /> + + { inputValue && ( + onInputChange( '' ) } + /> + ) }
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 5eb4984413edd9..d2780694ddacd2 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -11,7 +11,7 @@ &.block-editor-link-control__search-input > input[type="text"] { display: block; padding: 11px $grid-size-large; - padding-right: 36px; // width of reset button + padding-right: 38px; // width of reset button position: relative; z-index: 1; border: 1px solid #e1e1e1; @@ -32,8 +32,9 @@ .block-editor-link-control__search-reset { position: absolute; - top: 0; - right: 0; + top: 4px; // has to be hard coded as form expands with search suggestions + right: 2px; // push away to avoid focus style obscuring input border + z-index: 10; } .block-editor-link-control__search-results { From 4aea19cc55c05585243121dadbd64657d3e8d9e7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Oct 2019 11:54:28 +0100 Subject: [PATCH 016/113] Fix search items to be buttons with correct style and layout --- .../src/components/link-control/index.js | 10 +++---- .../src/components/link-control/style.scss | 27 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 0630ef1123f779..4ae997c0cc1b98 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -74,10 +74,11 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) => { /* eslint-disable react/jsx-key */ return ( -
    +
    { suggestions.map( ( suggestion, index ) => ( -
  • handleSuggestionClick( suggestion ) } className={ classnames( 'block-editor-link-control__search-item', { 'is-selected': index === selectedSuggestion, } ) } @@ -89,10 +90,9 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit { suggestion.info || suggestion.url || '' } { suggestion.type.toLowerCase() || '' } - -
  • + ) ) } -
+
); /* eslint-enable react/jsx-key */ }; diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index d2780694ddacd2..f9ea1019f73298 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -69,8 +69,19 @@ display: block; } - .dashicon { + + .block-editor-link-control__search-item-header { + margin-right: 1em; + } + + .block-editor-link-control__search-item-icon { margin-right: 1em; + min-width: 24px; + } + + .block-editor-link-control__search-item-title { + font-weight: 500; + margin-bottom: 0.2em; } .block-editor-link-control__search-item-info { @@ -79,24 +90,12 @@ } .block-editor-link-control__search-item-type { - position: absolute; - top: 50%; - transform: translateY(-50%); - right: 10px; - display: block; padding: 3px 8px; + margin-left: auto; font-size: 0.9em; background-color: #f3f4f5; border-radius: 2px; } - - .components-menu-item__info-wrapper { - flex: 1; - } -} - -.block-editor-link-control__search-item-title { - font-weight: 500; } // Specificity overide From d554b65ec4b0f9032d76fdac148793771c024339 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Oct 2019 12:18:34 +0100 Subject: [PATCH 017/113] Adds overflow scrolling to search results --- .../src/components/link-control/index.js | 38 ++++++++-------- .../src/components/link-control/style.scss | 45 +++++++++++++++---- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4ae997c0cc1b98..15e46875df96d6 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -74,24 +74,26 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) => { /* eslint-disable react/jsx-key */ return ( -
- { suggestions.map( ( suggestion, index ) => ( - - ) ) } +
+
+ { suggestions.map( ( suggestion, index ) => ( + + ) ) } +
); /* eslint-enable react/jsx-key */ diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index f9ea1019f73298..569f0cbc005ddc 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -1,7 +1,3 @@ -.block-editor-link-control__popover-inner { - padding: 20px; -} - .block-editor-link-control__search { position: relative; } @@ -11,6 +7,7 @@ &.block-editor-link-control__search-input > input[type="text"] { display: block; padding: 11px $grid-size-large; + margin: $grid-size-large; padding-right: 38px; // width of reset button position: relative; z-index: 1; @@ -32,18 +29,48 @@ .block-editor-link-control__search-reset { position: absolute; - top: 4px; // has to be hard coded as form expands with search suggestions - right: 2px; // push away to avoid focus style obscuring input border + top: 19px; // has to be hard coded as form expands with search suggestions + right: 19px; // push away to avoid focus style obscuring input border z-index: 10; } +.block-editor-link-control__search-results-wrapper { + position: relative; + margin-top: -$grid-size-large + 1px; + + &::before, + &::after { + content: ""; + position: absolute; + left: -1px; + right: -2px; + display: block; + pointer-events: none; + z-index: 100; + } + + &::before { + height: $grid-size-large; + top: -1px; + bottom: auto; + background: linear-gradient(to bottom, #fff 0%, transparent 100%); + } + + &::after { + height: 20px; + bottom: -1px; + top: auto; + background: linear-gradient(to bottom, transparent 0%, #fff 100%); + } +} + .block-editor-link-control__search-results { margin: 0; - padding: $grid-size-large 0; - list-style: none; + padding: $grid-size-large/2 $grid-size-large; + max-height: 200px; + overflow-y: scroll; // allow results list to scroll } - .block-editor-link-control__search-item { position: relative; display: flex; From 26c4c1b41a362aeebc759ea565bb1f0efea6e593 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Oct 2019 12:19:49 +0100 Subject: [PATCH 018/113] Fix to stop scroll shadow overlaying scrollbars --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 569f0cbc005ddc..2fdcd5df775d65 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -43,7 +43,7 @@ content: ""; position: absolute; left: -1px; - right: -2px; + right: $grid-size-large; // avoid overlaying scrollbars display: block; pointer-events: none; z-index: 100; From e778a04ecf884320a5aeedbed9519bdf620f1b5f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Oct 2019 12:37:54 +0100 Subject: [PATCH 019/113] Add bespoke settings area and tweak styles --- .../src/components/link-control/index.js | 11 +++++------ .../src/components/link-control/style.scss | 12 +++++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 15e46875df96d6..ffdc3ecbd9cf1e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -99,13 +99,13 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit /* eslint-enable react/jsx-key */ }; - const renderSettings = () => ( - + const LinkControlAdditionalSettings = () => ( +
{ isFunction( renderAdditionalSettings ) && renderAdditionalSettings() } - +
); return ( @@ -119,9 +119,7 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit { isOpen && ( - +
@@ -149,6 +147,7 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit onClick={ () => onInputChange( '' ) } /> ) } + { inputValue && }
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 2fdcd5df775d65..dc9436d5a248d0 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -50,7 +50,7 @@ } &::before { - height: $grid-size-large; + height: $grid-size-large/2; top: -1px; bottom: auto; background: linear-gradient(to bottom, #fff 0%, transparent 100%); @@ -129,3 +129,13 @@ .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__settings { + border-top: 1px solid #e1e1e1; + margin: 0 $grid-size-large; + padding: $grid-size-large $grid-size-large; + + :last-child { + margin-bottom: 0; + } +} From 3ca482edb5d92806d4d8011845e1ba11591e0dca Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Oct 2019 15:27:43 +0100 Subject: [PATCH 020/113] Update to allow URLs to be conditionally handled as a suggestion Previously when a URL was entered it was deemed that no suggestions should or could be found and so the process of fetching suggestions was short circuited. Add additional prop to optionally allow developers to have URL-like values handled as suggestions. --- packages/block-editor/src/components/url-input/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 8900aadbeeca58..9a70ca995fa1b5 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -70,14 +70,14 @@ class URLInput extends Component { } updateSuggestions( value ) { - const { fetchLinkSuggestions } = this.props; + const { fetchLinkSuggestions, handleURLSuggestions } = this.props; if ( ! fetchLinkSuggestions ) { return; } // Show the suggestions after typing at least 2 characters // and also for URLs - if ( value.length < 2 || /^https?:/.test( value ) ) { + if ( value.length < 2 || ( ! handleURLSuggestions && /^https?:/.test( value ) ) ) { this.setState( { showSuggestions: false, selectedSuggestion: null, From aec9f39f7779685008eb0bf1a5820c2f32354e34 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Oct 2019 15:30:07 +0100 Subject: [PATCH 021/113] Updates to conditionally use an entity or url based search results fetcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the current value of the input is a URL then we conditionally pass a different handler for search results to the URLInput component. For URL based values we immediately return a “suggestion” object with values matching those entered by the user. Non URL based values are handled as previously. --- .../src/components/link-control/index.js | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index ffdc3ecbd9cf1e..304c26af0a47da 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -50,11 +50,28 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit setIsOpen( true ); } ); + const getSearchFetcher = useCallback( ( value ) => { + return ( /^https?:/.test( value ) ) ? handleURLSearch : handleEntitySearch; + } ); + // Handlers const onInputChange = ( value = '' ) => { setInputValue( value ); }; + const handleURLSearch = async ( value ) => { + return [ { + id: 1, + title: value, + type: 'URL', + url: value, + } ]; + }; + + const handleEntitySearch = ( value ) => { + return fetchSearchSuggestions( value ); + }; + const onSubmitLinkChange = ( value ) => { setInputValue( value ); }; @@ -85,7 +102,9 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit } ) } > - + { suggestion.type.toLowerCase() === 'url' && ( + + ) } { suggestion.title } { suggestion.info || suggestion.url || '' } @@ -135,7 +154,8 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit onKeyPress={ stopPropagation } placeholder={ __( 'Search or type url' ) } renderSuggestions={ renderSearchResults } - fetchLinkSuggestions={ fetchSearchSuggestions } + fetchLinkSuggestions={ getSearchFetcher( inputValue ) } + handleURLSuggestions={ true } /> { inputValue && ( @@ -144,7 +164,7 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit label={ __( 'Reset' ) } icon="no-alt" className="block-editor-link-control__search-reset" - onClick={ () => onInputChange( '' ) } + onClick={ () => onInputChange( undefined ) } /> ) } { inputValue && } From 70bd877f37761e6806322e6d4a78d46d3fd8c661 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 09:24:53 +0100 Subject: [PATCH 022/113] =?UTF-8?q?Fix=20bug=20whereby=20fetchSearchSugges?= =?UTF-8?q?tions=20wasn=E2=80=99t=20called?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove ambiguity by calling the search handler directly rather than proxying through another function and having to apply it immediately. --- packages/block-editor/src/components/link-control/index.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 304c26af0a47da..afc7f77b022cff 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -51,7 +51,7 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit } ); const getSearchFetcher = useCallback( ( value ) => { - return ( /^https?:/.test( value ) ) ? handleURLSearch : handleEntitySearch; + return ( /^https?:/.test( value ) ) ? handleURLSearch : fetchSearchSuggestions; } ); // Handlers @@ -68,10 +68,6 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit } ]; }; - const handleEntitySearch = ( value ) => { - return fetchSearchSuggestions( value ); - }; - const onSubmitLinkChange = ( value ) => { setInputValue( value ); }; From 58220f84d2de6969e29cf03dd0b06c5f9608553a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 09:42:54 +0100 Subject: [PATCH 023/113] Remove default toggle UI and implement Popover close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LinkControl will be mostly where another element triggers the UI to appear. As a result we don’t want to force a toggle element on the developer. Rather we will expose an API to allow the consuming component to toggle the visibility of the LinkControl --- .../src/components/link-control/index.js | 97 +++++++++---------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index afc7f77b022cff..534c1804f0a28d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -17,7 +17,6 @@ import { useCallback, useState, useRef, - Fragment, } from '@wordpress/element'; import { @@ -46,10 +45,6 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit const autocompleteRef = useRef( null ); // Effects - const openLinkUI = useCallback( () => { - setIsOpen( true ); - } ); - const getSearchFetcher = useCallback( ( value ) => { return ( /^https?:/.test( value ) ) ? handleURLSearch : fetchSearchSuggestions; } ); @@ -59,6 +54,11 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit setInputValue( value ); }; + const closeLinkUI = () => { + setInputValue( '' ); + setIsOpen( false ); + }; + const handleURLSearch = async ( value ) => { return [ { id: 1, @@ -123,54 +123,47 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit
); + if ( ! isOpen ) { + return null; + } + return ( - - - - { isOpen && ( - - -
-
- -
- - - { inputValue && ( - onInputChange( undefined ) } - /> - ) } - { inputValue && } - -
-
-
- - ) } -
+ +
+
+ +
+ + + { inputValue && ( + onInputChange( undefined ) } + /> + ) } + { inputValue && } + +
+
+
); } From 5eeb8193d14629382e643bda42a523780941e047 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 10:00:35 +0100 Subject: [PATCH 024/113] =?UTF-8?q?Adds=20search=20text=20=E2=80=9Chighlig?= =?UTF-8?q?hting=E2=80=9D=20in=20results=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/link-control/index.js | 23 +++++++++++++++++-- .../src/components/link-control/style.scss | 21 ++++++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 534c1804f0a28d..314601f63fbb46 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -11,12 +11,13 @@ import { */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { isFunction } from 'lodash'; +import { isFunction, escapeRegExp } from 'lodash'; import { useCallback, useState, useRef, + Fragment, } from '@wordpress/element'; import { @@ -36,6 +37,22 @@ import { URLInput, } from '../'; +const TextHighlight = ( { text = '', highlight = '' } ) => { + if ( ! highlight.trim() ) { + return { text }; + } + + const regex = new RegExp( `(${ escapeRegExp( highlight ) })`, 'gi' ); + const parts = text.split( regex ); + return ( + + { parts.filter( ( part ) => part ).map( ( part, i ) => ( + regex.test( part ) ? { part } : { part } + ) ) } + + ); +}; + function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAdditionalSettings } ) { // State const [ isOpen, setIsOpen ] = useState( defaultOpen ); @@ -102,7 +119,9 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit ) } - { suggestion.title } + + + { suggestion.info || suggestion.url || '' } { suggestion.type.toLowerCase() || '' } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index dc9436d5a248d0..1ccabd75d78ec5 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -92,12 +92,8 @@ background: #e1e1e1; } - span { - display: block; - } - - .block-editor-link-control__search-item-header { + display: block; margin-right: 1em; } @@ -107,16 +103,29 @@ } .block-editor-link-control__search-item-title { - font-weight: 500; + display: block; margin-bottom: 0.2em; + font-weight: 500; + + mark { + font-weight: 700; + color: #000; + background-color: transparent; + } + + span { + font-weight: normal; + } } .block-editor-link-control__search-item-info { + display: block; color: #999; font-size: 0.9em; } .block-editor-link-control__search-item-type { + display: block; padding: 3px 8px; margin-left: auto; font-size: 0.9em; From d355133e5a9f119a803fefd7e36286a7727b42e7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 10:03:27 +0100 Subject: [PATCH 025/113] Move TextHighlight component to its own file --- .../src/components/link-control/index.js | 19 ++---------- .../components/link-control/text-highlight.js | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/text-highlight.js diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 314601f63fbb46..d9efb92eef43d1 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -11,13 +11,12 @@ import { */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { isFunction, escapeRegExp } from 'lodash'; +import { isFunction } from 'lodash'; import { useCallback, useState, useRef, - Fragment, } from '@wordpress/element'; import { @@ -37,21 +36,7 @@ import { URLInput, } from '../'; -const TextHighlight = ( { text = '', highlight = '' } ) => { - if ( ! highlight.trim() ) { - return { text }; - } - - const regex = new RegExp( `(${ escapeRegExp( highlight ) })`, 'gi' ); - const parts = text.split( regex ); - return ( - - { parts.filter( ( part ) => part ).map( ( part, i ) => ( - regex.test( part ) ? { part } : { part } - ) ) } - - ); -}; +import TextHighlight from './text-highlight'; function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAdditionalSettings } ) { // State diff --git a/packages/block-editor/src/components/link-control/text-highlight.js b/packages/block-editor/src/components/link-control/text-highlight.js new file mode 100644 index 00000000000000..fd743666bc1cc1 --- /dev/null +++ b/packages/block-editor/src/components/link-control/text-highlight.js @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { escapeRegExp } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + Fragment, +} from '@wordpress/element'; + +const TextHighlight = ( { text = '', highlight = '' } ) => { + if ( ! highlight.trim() ) { + return { text }; + } + + const regex = new RegExp( `(${ escapeRegExp( highlight ) })`, 'gi' ); + const parts = text.split( regex ); + return ( + + { parts.filter( ( part ) => part ).map( ( part, i ) => ( + regex.test( part ) ? { part } : { part } + ) ) } + + ); +}; + +export default TextHighlight; From 2044065288b722090962b0c6e91c0b88b1d1f44b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 15:02:20 +0100 Subject: [PATCH 026/113] =?UTF-8?q?Fix=20bug=20where=20update=20to=20value?= =?UTF-8?q?=20prop=20didn=E2=80=99t=20cause=20suggestions=20to=20reset.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/url-input/index.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 9a70ca995fa1b5..5aa28ed6af05ed 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -227,9 +227,20 @@ class URLInput extends Component { this.inputRef.current.focus(); } - static getDerivedStateFromProps( { disableSuggestions }, { showSuggestions } ) { + static getDerivedStateFromProps( { value, disableSuggestions }, { showSuggestions, selectedSuggestion } ) { + let shouldShowSuggestions = showSuggestions; + + if ( ! value.length ) { + shouldShowSuggestions = false; + } + + if ( disableSuggestions === true ) { + shouldShowSuggestions = false; + } + return { - showSuggestions: disableSuggestions === true ? false : showSuggestions, + selectedSuggestion: value.length ? selectedSuggestion : null, + showSuggestions: shouldShowSuggestions, }; } From b256d55f5ceddb0e54d5cbd25c6ce42476c390bf Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 15:09:04 +0100 Subject: [PATCH 027/113] Update to remove internal handling of open/closed state This state is now expected to be handled by the consuming component chosing whether or not to render the component. It has no concept of open or closed. --- .../block-editor/src/components/link-control/index.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index d9efb92eef43d1..9dd1ee5f99ddaf 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -38,9 +38,8 @@ import { import TextHighlight from './text-highlight'; -function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAdditionalSettings } ) { +function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { // State - const [ isOpen, setIsOpen ] = useState( defaultOpen ); const [ inputValue, setInputValue ] = useState( '' ); // Refs @@ -58,7 +57,6 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit const closeLinkUI = () => { setInputValue( '' ); - setIsOpen( false ); }; const handleURLSearch = async ( value ) => { @@ -127,10 +125,6 @@ function LinkControl( { defaultOpen = false, fetchSearchSuggestions, renderAddit
); - if ( ! isOpen ) { - return null; - } - return ( Date: Fri, 11 Oct 2019 15:09:35 +0100 Subject: [PATCH 028/113] Fix React violation by returning only the text for non matches --- .../block-editor/src/components/link-control/text-highlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/text-highlight.js b/packages/block-editor/src/components/link-control/text-highlight.js index fd743666bc1cc1..dc7b35a3d6d2bc 100644 --- a/packages/block-editor/src/components/link-control/text-highlight.js +++ b/packages/block-editor/src/components/link-control/text-highlight.js @@ -12,7 +12,7 @@ import { const TextHighlight = ( { text = '', highlight = '' } ) => { if ( ! highlight.trim() ) { - return { text }; + return text; } const regex = new RegExp( `(${ escapeRegExp( highlight ) })`, 'gi' ); From 2133fb1388c93da4212aaad41072f5f7fc4f3c6b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 15:10:01 +0100 Subject: [PATCH 029/113] Update existing tests to match new implementation --- .../test/__snapshots__/index.js.snap | 2 +- .../link-control/test/fixtures/index.js | 41 +++++++++++++ .../src/components/link-control/test/index.js | 61 +++++++------------ 3 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/test/fixtures/index.js diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap index 5aec2c4e26a5fe..689a1ac7969e29 100644 --- a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render icon button and in closed state by default 1`] = `""`; +exports[`Basic rendering should render with required props 1`] = `"
"`; 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 new file mode 100644 index 00000000000000..fc974749c982b1 --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/fixtures/index.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { uniqueId } from 'lodash'; + +export const fauxEntitySuggestions = [ + { + id: uniqueId(), + title: 'Hello Page', + type: 'Page', + info: '2 days ago', + url: `?p=${ uniqueId() }`, + }, + { + id: uniqueId(), + title: 'Hello Post', + type: 'Post', + info: '19 days ago', + url: `?p=${ uniqueId() }`, + }, + { + id: uniqueId(), + title: 'Hello Another One', + type: 'Page', + info: '19 days ago', + url: `?p=${ uniqueId() }`, + }, + { + id: uniqueId(), + title: 'This is another Post with a much longer title just to be really annoying and to try and break the UI', + type: 'Post', + info: '1 month ago', + url: `?p=${ uniqueId() }`, + }, +]; + +// export const fetchFauxEntitySuggestions = async () => fauxEntitySuggestions; + +export const fetchFauxEntitySuggestions = () => { + return Promise.resolve( fauxEntitySuggestions ); +}; 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 64fe67ee3b935b..cf341f8a3b0aa3 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -8,6 +8,11 @@ import { act, Simulate } from 'react-dom/test-utils'; * Internal dependencies */ import LinkControl from '../index'; +import { fauxEntitySuggestions, fetchFauxEntitySuggestions } from './fixtures'; + +function currentEventLoopEnd() { + return new Promise( ( resolve ) => setImmediate( resolve ) ); +} let container = null; beforeEach( () => { @@ -24,31 +29,13 @@ afterEach( () => { } ); describe( 'Basic rendering', () => { - it( 'should render icon button and in closed state by default', () => { + it( 'should render with required props', () => { act( () => { render( , container ); } ); - const openIconButton = container.querySelector( '[aria-label="Insert link"]' ); - const openIcon = openIconButton.querySelector( 'svg' ); - const expectedIconClassName = expect.stringContaining( 'dashicons-insert' ); - - expect( openIconButton ).not.toBeNull(); - expect( openIcon.getAttribute( 'class' ) ).toEqual( expectedIconClassName ); - - expect( container.innerHTML ).toMatchSnapshot(); - } ); - - it( 'should render core link ui interface when open', () => { - act( () => { - render( - , container - ); - } ); // Search Input UI // const searchInputLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerText === 'Search or input url' ); @@ -56,31 +43,19 @@ describe( 'Basic rendering', () => { // expect( searchInputLabel ).not.toBeNull(); expect( searchInput ).not.toBeNull(); - } ); - it( 'should toggle link ui open on icon ui click', () => { - act( () => { - render( - , container - ); - } ); - const openIconButton = container.querySelector( '[aria-label="Insert link"]' ); - - act( () => { - Simulate.click( openIconButton ); - } ); - - expect( openIconButton.nextSibling.innerHTML ).toEqual( expect.stringMatching( 'Search or type url' ) ); + expect( container.innerHTML ).toMatchSnapshot(); } ); } ); describe( 'Searching', () => { - it( 'should render search results for current search term', () => { + it( 'should render search results for current search term', async ( ) => { + const searchTerm = 'Hello'; + act( () => { render( , container ); } ); @@ -97,21 +72,27 @@ describe( 'Searching', () => { // Simulate searching for a term act( () => { - Simulate.change( searchInput, { target: { value: 'WordPress' } } ); + Simulate.change( searchInput, { target: { value: searchTerm } } ); } ); + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await currentEventLoopEnd(); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); - expect( searchResultElements ).toHaveLength( 2 ); + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); // Reset the search term act( () => { Simulate.change( searchInput, { target: { value: '' } } ); } ); + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await currentEventLoopEnd(); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); expect( searchResultElements ).toHaveLength( 0 ); } ); From 70c5c04c9fe3baa924d9139939d53cc68f37d8eb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 15:23:53 +0100 Subject: [PATCH 030/113] Add link reset test --- .../src/components/link-control/index.js | 18 +++---- .../test/__snapshots__/index.js.snap | 2 +- .../src/components/link-control/test/index.js | 54 +++++++++++++++++-- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 9dd1ee5f99ddaf..13b53237a32fad 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -148,15 +148,15 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { handleURLSuggestions={ true } /> - { inputValue && ( - onInputChange( undefined ) } - /> - ) } + onInputChange( undefined ) } + /> + { inputValue && }
diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap index 689a1ac7969e29..6425c1a512a473 100644 --- a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render with required props 1`] = `"
"`; +exports[`Basic rendering should render with required props 1`] = `"
"`; 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 cf341f8a3b0aa3..871d30b0b46341 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -10,7 +10,7 @@ import { act, Simulate } from 'react-dom/test-utils'; import LinkControl from '../index'; import { fauxEntitySuggestions, fetchFauxEntitySuggestions } from './fixtures'; -function currentEventLoopEnd() { +function eventLoopTick() { return new Promise( ( resolve ) => setImmediate( resolve ) ); } @@ -76,7 +76,7 @@ describe( 'Searching', () => { } ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await currentEventLoopEnd(); + await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); @@ -89,11 +89,59 @@ describe( 'Searching', () => { } ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await currentEventLoopEnd(); + await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); expect( searchResultElements ).toHaveLength( 0 ); } ); + + it( 'should reset input and search results when search term is cleared or reset', async ( ) => { + const searchTerm = 'Hello'; + + act( () => { + render( + , container + ); + } ); + + let searchResultElements; + let searchInput; + + // Search Input UI + searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + + // Check we have definitely rendered some suggestions + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); + + // Grab the reset button now it's available + const resetUI = container.querySelector( '[aria-label="Reset"]' ); + + act( () => { + Simulate.click( resetUI ); + } ); + + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + expect( searchInput.value ).toBe( '' ); + expect( searchResultElements ).toHaveLength( 0 ); + } ); } ); From 4a031eddc56d56306121fb0c1df31dd6b92c1f4f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 15:48:05 +0100 Subject: [PATCH 031/113] Adds test which uncovers major bug in the implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basically this test has revealed that due to the way we’re detecting and handling URL-like values the wrong data fetcher function gets passed to the URLInput component for the first input `change` event. For example if you paste `https://make.wordpress.com` directly into the input then it is determined to be a URL but because the current fetcher function for the current render is still the handler that deals with entity searches the correct results are not displayed. Adding another character to trigger a re-render will cause the UI to update to the expected state, but this is a major bug. --- .../src/components/link-control/test/index.js | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) 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 871d30b0b46341..1f2c04df874519 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -49,7 +49,7 @@ describe( 'Basic rendering', () => { } ); describe( 'Searching', () => { - it( 'should render search results for current search term', async ( ) => { + it( 'should render search suggestions when current input value is not URL-like', async ( ) => { const searchTerm = 'Hello'; act( () => { @@ -97,6 +97,36 @@ describe( 'Searching', () => { expect( searchResultElements ).toHaveLength( 0 ); } ); + it( 'should render a single suggestion result when the current input value is URL-like', async ( ) => { + const searchTerm = 'http://make.wordpress.com'; + + 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: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + const searchResultHTML = searchResultElements[ 0 ].innerHTML; + + expect( searchResultElements ).toHaveLength( 1 ); + expect( searchResultHTML ).toEqual( expect.stringContaining( searchTerm ) ); + } ); + it( 'should reset input and search results when search term is cleared or reset', async ( ) => { const searchTerm = 'Hello'; From 5b5164c06e8d6800410c17825bf0e803d98bbba9 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 11 Oct 2019 15:54:18 +0100 Subject: [PATCH 032/113] Tweak critical test to be more explicit about what is expected --- .../src/components/link-control/test/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 1f2c04df874519..8e925026061fd2 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -121,10 +121,12 @@ describe( 'Searching', () => { // TODO: select these by aria relationship to autocomplete rather than arbitary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); - const searchResultHTML = searchResultElements[ 0 ].innerHTML; + const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; + const expectedResultsLength = 1; - expect( searchResultElements ).toHaveLength( 1 ); - expect( searchResultHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( searchResultElements ).toHaveLength( expectedResultsLength ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'url' ) ); } ); it( 'should reset input and search results when search term is cleared or reset', async ( ) => { From 664daad40273692d0cf0358fc3267f1e29a79af1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 14 Oct 2019 09:35:37 +0100 Subject: [PATCH 033/113] Fix bug to make determining search handler use the latest input value Previously we relied on parent component state to choose which search handler to use for the current input term. However, the state was always 1 tick behind so the previous search handler got used. Updating this to use the real time value of the input passed onChange ensures we select the correct search fetcher when the component re-renders. --- .../src/components/link-control/index.js | 22 +++++++++---------- .../src/components/link-control/test/index.js | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 13b53237a32fad..4a868bc9507c8e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -45,11 +45,6 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { // Refs const autocompleteRef = useRef( null ); - // Effects - const getSearchFetcher = useCallback( ( value ) => { - return ( /^https?:/.test( value ) ) ? handleURLSearch : fetchSearchSuggestions; - } ); - // Handlers const onInputChange = ( value = '' ) => { setInputValue( value ); @@ -59,6 +54,10 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { setInputValue( '' ); }; + const onSubmitLinkChange = ( value ) => { + setInputValue( value ); + }; + const handleURLSearch = async ( value ) => { return [ { id: 1, @@ -68,10 +67,6 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { } ]; }; - const onSubmitLinkChange = ( value ) => { - setInputValue( value ); - }; - const stopPropagation = ( event ) => { event.stopPropagation(); }; @@ -79,10 +74,15 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { const stopPropagationRelevantKeys = ( event ) => { if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - event.stopPropagation(); + stopPropagation( event ); } }; + // Effects + const getSearchHandler = useCallback( ( value ) => { + return ( /^https?:/.test( value ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); + }, [ handleURLSearch, fetchSearchSuggestions ] ); + // Render Components const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) => { /* eslint-disable react/jsx-key */ @@ -144,7 +144,7 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { onKeyPress={ stopPropagation } placeholder={ __( 'Search or type url' ) } renderSuggestions={ renderSearchResults } - fetchLinkSuggestions={ getSearchFetcher( inputValue ) } + fetchLinkSuggestions={ getSearchHandler } handleURLSuggestions={ true } /> 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 8e925026061fd2..a71279c1d62e2b 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -79,7 +79,7 @@ describe( 'Searching', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); @@ -92,7 +92,7 @@ describe( 'Searching', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); expect( searchResultElements ).toHaveLength( 0 ); } ); @@ -120,7 +120,7 @@ describe( 'Searching', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; const expectedResultsLength = 1; @@ -155,7 +155,7 @@ describe( 'Searching', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); // Check we have definitely rendered some suggestions expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); @@ -170,7 +170,7 @@ describe( 'Searching', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] button[role="option"]' ); + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); searchInput = container.querySelector( 'input[aria-label="URL"]' ); expect( searchInput.value ).toBe( '' ); From 9780959970b544667e4bf75484eaad4a1ee70dff Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 14 Oct 2019 11:29:31 +0100 Subject: [PATCH 034/113] Add loading spinner and associated test coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spinner was technically always rendered but it wasn’t visible due to CSS styling. Fix and also cover with tests. --- .../src/components/link-control/index.js | 1 + .../src/components/link-control/style.scss | 15 ++++++ .../test/__snapshots__/index.js.snap | 4 +- .../src/components/link-control/test/index.js | 54 +++++++++++++++++-- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4a868bc9507c8e..4692d592c8525c 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -127,6 +127,7 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { return (
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 1ccabd75d78ec5..3863ddf070ce5e 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -148,3 +148,18 @@ margin-bottom: 0; } } + +.block-editor-link-control .block-editor-link-control__search-input .components-spinner { + display: block; + z-index: 100; + float: none; + + &.components-spinner { // Specificity overide + position: relative; + top: auto; + left: auto; + right: auto; + bottom: auto; + margin: 0 auto $grid-size-large auto; + } +} diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap index 6425c1a512a473..4f743b6d8b0885 100644 --- a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -1,3 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render with required props 1`] = `"
"`; +exports[`Basic rendering should display with required props 1`] = `"
"`; + +exports[`Basic rendering should render with required props 1`] = `"
"`; 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 a71279c1d62e2b..c355281f7e413d 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -29,7 +29,7 @@ afterEach( () => { } ); describe( 'Basic rendering', () => { - it( 'should render with required props', () => { + it( 'should display with required props', () => { act( () => { render( { } ); describe( 'Searching', () => { - it( 'should render search suggestions when current input value is not URL-like', async ( ) => { + it( 'should display loading UI when input is valid but search results have yet to be returned', async () => { + const searchTerm = 'Hello'; + + let resolver; + + const fauxRequest = () => new Promise( ( resolve ) => { + resolver = resolve; + } ); + + 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: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + + let loadingUI = container.querySelector( '.components-spinner' ); + + expect( searchResultElements ).toHaveLength( 0 ); + + expect( loadingUI ).not.toBeNull(); + + act( () => { + resolver( fauxEntitySuggestions ); + } ); + + await eventLoopTick(); + + loadingUI = container.querySelector( '.components-spinner' ); + + expect( loadingUI ).toBeNull(); + } ); + + it( 'should display search suggestions when current input value is not URL-like', async ( ) => { const searchTerm = 'Hello'; act( () => { @@ -97,7 +145,7 @@ describe( 'Searching', () => { expect( searchResultElements ).toHaveLength( 0 ); } ); - it( 'should render a single suggestion result when the current input value is URL-like', async ( ) => { + it( 'should display a single suggestion result when the current input value is URL-like', async ( ) => { const searchTerm = 'http://make.wordpress.com'; act( () => { From 5c3bf353b6faa90b53de7b814021e66d00188702 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 14 Oct 2019 17:36:03 +0100 Subject: [PATCH 035/113] Fix bug where value could be empty --- packages/block-editor/src/components/url-input/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 5aa28ed6af05ed..eb6e43be6d5a49 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -230,7 +230,7 @@ class URLInput extends Component { static getDerivedStateFromProps( { value, disableSuggestions }, { showSuggestions, selectedSuggestion } ) { let shouldShowSuggestions = showSuggestions; - if ( ! value.length ) { + if ( ! value || ! value.length ) { shouldShowSuggestions = false; } @@ -239,7 +239,7 @@ class URLInput extends Component { } return { - selectedSuggestion: value.length ? selectedSuggestion : null, + selectedSuggestion: value && value.length ? selectedSuggestion : null, showSuggestions: shouldShowSuggestions, }; } From c962dc6c8d5122da8a85b0697b4a044381072477 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 12:19:50 +0100 Subject: [PATCH 036/113] Adds basic editing / view state switching --- .../src/components/link-control/index.js | 89 ++++++++++++------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4692d592c8525c..59b56d4fa30241 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -11,7 +11,7 @@ import { */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { isFunction } from 'lodash'; +import { isFunction, partialRight } from 'lodash'; import { useCallback, @@ -38,9 +38,10 @@ import { import TextHighlight from './text-highlight'; -function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { +function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSettings, onLinkChange } ) { // State const [ inputValue, setInputValue ] = useState( '' ); + const [ isEditingLink, setIsEditingLink ] = useState( true ); // Refs const autocompleteRef = useRef( null ); @@ -51,11 +52,26 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { }; const closeLinkUI = () => { + resetInput(); + }; + + const resetInput = useCallback( () => { setInputValue( '' ); + } ); + + const onLinkSelect = ( event, suggestion ) => { + event.preventDefault(); + event.stopPropagation(); + + setIsEditingLink( false ); + + if ( isFunction( onLinkChange ) ) { + onLinkChange( suggestion ); + } }; - const onSubmitLinkChange = ( value ) => { - setInputValue( value ); + const onStartEditing = () => { + setIsEditingLink( true ); }; const handleURLSearch = async ( value ) => { @@ -84,7 +100,7 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { }, [ handleURLSearch, fetchSearchSuggestions ] ); // Render Components - const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, handleSuggestionClick } ) => { + const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion } ) => { /* eslint-disable react/jsx-key */ return (
@@ -92,7 +108,7 @@ function LinkControl( { fetchSearchSuggestions, renderAdditionalSettings } ) { { suggestions.map( ( suggestion, index ) => ( +
+ ) } + + { isEditingLink && ( +
+ + + + + { inputValue && } + + ) }
From d4c0a70ef55e64558369e4e14a1cc9c280c68402 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 12:27:54 +0100 Subject: [PATCH 037/113] Add keydown callback to URLInput --- packages/block-editor/src/components/url-input/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index eb6e43be6d5a49..48ae9a9ac0fb6a 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -211,6 +211,10 @@ class URLInput extends Component { break; } } + + if ( isFunction( this.props.onKeyDown ) ) { + this.props.onKeyDown( event, suggestion ); + } } selectLink( suggestion ) { From 8cff4c232b63f1475d8e9ceb157d6af12b088410 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 12:30:11 +0100 Subject: [PATCH 038/113] Select link on ENTER keydown event --- packages/block-editor/src/components/link-control/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 59b56d4fa30241..99ca8450194efe 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -163,7 +163,12 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet value={ inputValue } onChange={ onInputChange } autocompleteRef={ autocompleteRef } - onKeyDown={ stopPropagationRelevantKeys } + onKeyDown={ ( event, suggestion ) => { + stopPropagationRelevantKeys( event ); + if ( event.keyCode === ENTER ) { + onLinkSelect( event, suggestion ); + } + } } onKeyPress={ stopPropagation } placeholder={ __( 'Search or type url' ) } renderSuggestions={ renderSearchResults } From b4a3f66494965e85bd32d3e9cb70f3e82326c3ce Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 12:42:52 +0100 Subject: [PATCH 039/113] Utilise LinkViewer to render edit state and decode urls for display --- .../src/components/link-control/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 99ca8450194efe..1f0f5ba0835fe9 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -28,6 +28,8 @@ import { ENTER, } from '@wordpress/keycodes'; +import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; + /** * Internal dependencies */ @@ -121,7 +123,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet - { suggestion.info || suggestion.url || '' } + { suggestion.info || filterURLForDisplay( safeDecodeURI( suggestion.url ) ) || '' } { suggestion.type.toLowerCase() || '' } @@ -151,8 +153,12 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet { ! isEditingLink && (
-

The link is { currentLink.title } { currentLink.url }

- +
) } From fbe49e708aef90989c5c674745a8f348f2552bb6 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 12:45:22 +0100 Subject: [PATCH 040/113] Only display link settings when a link is selected --- packages/block-editor/src/components/link-control/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 1f0f5ba0835fe9..66f6e2da3e1312 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -191,9 +191,12 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet onClick={ resetInput } /> - { inputValue && } ) } + + { ! isEditingLink && ( + + ) }
From fa0dd3663adeef11c25cdd9cd809226f2e22ab72 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 15:09:05 +0100 Subject: [PATCH 041/113] Adds current link view styles --- .../src/components/link-control/index.js | 26 ++++++++++++++----- .../src/components/link-control/style.scss | 20 +++++++++++--- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 66f6e2da3e1312..b9a3f94875e915 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -5,7 +5,9 @@ import { IconButton, Icon, ToggleControl, + ExternalLink, } from '@wordpress/components'; + /** * External dependencies */ @@ -152,13 +154,23 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet
{ ! isEditingLink && ( -
- +
+ + + + { currentLink.title } + + { currentLink.info || filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' } + + { currentLink.type.toLowerCase() || '' } +
) } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 3863ddf070ce5e..9ec953976ec375 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -1,5 +1,6 @@ .block-editor-link-control__search { position: relative; + min-width: 340px; } .block-editor-link-control__search .block-editor-link-control__search-input { @@ -30,7 +31,7 @@ .block-editor-link-control__search-reset { position: absolute; top: 19px; // has to be hard coded as form expands with search suggestions - right: 19px; // push away to avoid focus style obscuring input border + right: 25px; // push away to avoid focus style obscuring input border z-index: 10; } @@ -81,15 +82,21 @@ width: 100%; border: none; text-align: left; - padding: 10px; + padding: 10px 15px; + border-radius: 5px; &:hover, &:focus { background-color: #e9e9e9; } - &.is-selected { - background: #e1e1e1; + &.is-current { + background: transparent; + border: 2px solid #ccc; + margin: $grid-size-large; + width: calc(100% - #{$grid-size-large*2}); + cursor: default; + padding-right: 10px; // visually compensate for edit icon padding } .block-editor-link-control__search-item-header { @@ -163,3 +170,8 @@ margin: 0 auto $grid-size-large auto; } } + +.block-editor-link-control__search-item-action { + margin-left: 0.5em; + flex-shrink: 0; +} From f5a7e6558e2c074161b240def55605d42d615964 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 15 Oct 2019 15:21:40 +0100 Subject: [PATCH 042/113] Makes settings toggle controlled by parent component --- packages/block-editor/src/components/link-control/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index b9a3f94875e915..6f70e4a8567efc 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -13,7 +13,7 @@ import { */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { isFunction, partialRight } from 'lodash'; +import { isFunction, partial, partialRight, noop } from 'lodash'; import { useCallback, @@ -42,7 +42,7 @@ import { import TextHighlight from './text-highlight'; -function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSettings, onLinkChange } ) { +function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSettings, onLinkChange, onSettingChange = { noop }, linkSettings } ) { // State const [ inputValue, setInputValue ] = useState( '' ); const [ isEditingLink, setIsEditingLink ] = useState( true ); @@ -140,7 +140,8 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet
+ onChange={ partial( onSettingChange, 'new-tab' ) } + checked={ linkSettings[ 'new-tab' ] } /> { isFunction( renderAdditionalSettings ) && renderAdditionalSettings() }
); From a88826e9db40b9fded04283cbd7f962d1cb597f8 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 11:18:59 +0100 Subject: [PATCH 043/113] Update visuals to match updated design Addresses https://github.com/WordPress/gutenberg/issues/17557#issuecomment-542401433 --- .../src/components/link-control/index.js | 7 +++++-- .../src/components/link-control/style.scss | 16 ++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 6f70e4a8567efc..7a5ea9163c0710 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { + Button, IconButton, Icon, ToggleControl, @@ -170,8 +171,10 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet { currentLink.info || filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' } - { currentLink.type.toLowerCase() || '' } - + +
) } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 9ec953976ec375..c417a0f2457270 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -92,16 +92,16 @@ &.is-current { background: transparent; - border: 2px solid #ccc; - margin: $grid-size-large; - width: calc(100% - #{$grid-size-large*2}); + border: 0; + width: 100%; cursor: default; - padding-right: 10px; // visually compensate for edit icon padding + padding: $grid-size-large; + padding-left: $grid-size-xlarge; } .block-editor-link-control__search-item-header { display: block; - margin-right: 1em; + margin-right: $grid-size-xlarge; } .block-editor-link-control__search-item-icon { @@ -148,8 +148,8 @@ .block-editor-link-control__settings { border-top: 1px solid #e1e1e1; - margin: 0 $grid-size-large; - padding: $grid-size-large $grid-size-large; + margin: 0; + padding: $grid-size-large $grid-size-xlarge; :last-child { margin-bottom: 0; @@ -172,6 +172,6 @@ } .block-editor-link-control__search-item-action { - margin-left: 0.5em; + margin-left: auto; // push to far right hand side flex-shrink: 0; } From ec9e2adcf85ab87e6de1c17838524bc7da83a716 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 11:40:12 +0100 Subject: [PATCH 044/113] Add standardised min width to popover --- packages/block-editor/src/components/link-control/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index c417a0f2457270..11ee3c329d7a66 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -1,11 +1,12 @@ .block-editor-link-control__search { position: relative; - min-width: 340px; + min-width: $modal-min-width; } .block-editor-link-control__search .block-editor-link-control__search-input { // Specificity overide &.block-editor-link-control__search-input > input[type="text"] { + width: calc(100% - #{$grid-size-large*2}); display: block; padding: 11px $grid-size-large; margin: $grid-size-large; @@ -31,7 +32,7 @@ .block-editor-link-control__search-reset { position: absolute; top: 19px; // has to be hard coded as form expands with search suggestions - right: 25px; // push away to avoid focus style obscuring input border + right: 19px; // push away to avoid focus style obscuring input border z-index: 10; } From 11ca79f20afbd2db8f58343ea5c18725086882e6 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 13:59:46 +0100 Subject: [PATCH 045/113] Temporary hack to include Link UI in Playground for testing --- playground/src/index.js | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/playground/src/index.js b/playground/src/index.js index b74e30091c1739..37e563947a89bc 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { uniqueId, random } from 'lodash'; + /** * WordPress dependencies */ @@ -10,6 +15,7 @@ import { BlockList, WritingFlow, ObserveTyping, + LinkControl, } from '@wordpress/block-editor'; import { Popover, @@ -35,6 +41,48 @@ import '@wordpress/format-library/build-style/style.css'; function App() { const [ blocks, updateBlocks ] = useState( [] ); + const [ link, setLink ] = useState( null ); + const [ linkSettings, setLinkSettings ] = useState( { + 'new-tab': false, + } ); + + /* eslint-disable @wordpress/react-no-unsafe-timeout */ + const timeout = ( ms ) => { + return new Promise( ( resolve ) => setTimeout( resolve, ms ) ); + }; + /* eslint-enable @wordpress/react-no-unsafe-timeout */ + + const fetchFauxEntitySuggestions = async () => { + // Simulate network + await timeout( random( 200, 1000 ) ); + + return ( [ + { + id: uniqueId(), + title: 'Hello Page', + type: 'Page', + url: '/hello-page/', + }, + { + id: uniqueId(), + title: 'Hello Post', + type: 'Post', + url: '/hello-post/', + }, + { + id: uniqueId(), + title: 'Hello Another One', + type: 'Page', + url: '/hello-another-one/', + }, + { + id: uniqueId(), + title: 'This is another Post with a much longer title just to be really annoying and to try and break the UI', + type: 'Post', + url: '/this-is-another-post-with-a-much-longer-title-just-to-be-really-annoying-and-to-try-and-break-the-ui/', + }, + ] ); + }; return ( @@ -53,9 +101,25 @@ function App() { + { + setLink( theLink ); + } } + currentLink={ link } + linkSettings={ linkSettings } + onSettingChange={ ( setting, value ) => { + setLinkSettings( { + ...linkSettings, + [ setting ]: value, + } ); + } } + /> +
From 779d440d254a3509e34faca159e1106897c2f414 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 16:29:56 +0100 Subject: [PATCH 046/113] Update to utilise isURL util from @wordpress/url package --- packages/block-editor/src/components/url-input/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 48ae9a9ac0fb6a..7d56fa5eb0c798 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -14,6 +14,7 @@ import { UP, DOWN, ENTER, TAB } from '@wordpress/keycodes'; import { Spinner, withSpokenMessages, Popover } from '@wordpress/components'; import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; +import { isURL } from '@wordpress/url'; // Since URLInput is rendered in the context of other inputs, but should be // considered a separate modal node, prevent keyboard events from propagating @@ -77,7 +78,7 @@ class URLInput extends Component { // Show the suggestions after typing at least 2 characters // and also for URLs - if ( value.length < 2 || ( ! handleURLSuggestions && /^https?:/.test( value ) ) ) { + if ( value.length < 2 || ( ! handleURLSuggestions && isURL( value ) ) ) { this.setState( { showSuggestions: false, selectedSuggestion: null, From 4941bb8016126f89fe75b9fd2b068a1b58462609 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 16:30:17 +0100 Subject: [PATCH 047/113] Update to utilise isURL util from @wordpress/url package --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 7a5ea9163c0710..c1bc3433901220 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -31,7 +31,7 @@ import { ENTER, } from '@wordpress/keycodes'; -import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; +import { safeDecodeURI, filterURLForDisplay, isURL } from '@wordpress/url'; /** * Internal dependencies @@ -101,7 +101,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet // Effects const getSearchHandler = useCallback( ( value ) => { - return ( /^https?:/.test( value ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); + return ( isURL( value ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); }, [ handleURLSearch, fetchSearchSuggestions ] ); // Render Components From 452ec5568590e4bcdf5e50c666110f721cd42897 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 16:34:39 +0100 Subject: [PATCH 048/113] Removes URLPopover dependency Attempts to remove unwanted deps on other components. We now utilise Popover directly and suffer no consequences as we are not making use of any bespoke features provided by URLPopover. --- .../block-editor/src/components/link-control/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index c1bc3433901220..5963980ebb9459 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -7,6 +7,7 @@ import { Icon, ToggleControl, ExternalLink, + Popover, } from '@wordpress/components'; /** @@ -37,7 +38,6 @@ import { safeDecodeURI, filterURLForDisplay, isURL } from '@wordpress/url'; * Internal dependencies */ import { - URLPopover, URLInput, } from '../'; @@ -148,9 +148,11 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet ); return ( -
@@ -215,7 +217,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet ) }
-
+ ); } From 5484a7c6c8a9c172717edd93a5de87afaeeddd2f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 16 Oct 2019 16:41:24 +0100 Subject: [PATCH 049/113] Extract settings drawer to sub component --- .../src/components/link-control/index.js | 18 ++++----------- .../link-control/settings-drawer.js | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/settings-drawer.js diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 5963980ebb9459..9ec8e077bdee8a 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -5,7 +5,6 @@ import { Button, IconButton, Icon, - ToggleControl, ExternalLink, Popover, } from '@wordpress/components'; @@ -15,7 +14,7 @@ import { */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; -import { isFunction, partial, partialRight, noop } from 'lodash'; +import { isFunction, partialRight, noop } from 'lodash'; import { useCallback, @@ -41,9 +40,10 @@ import { URLInput, } from '../'; +import LinkControlSettingsDrawer from './settings-drawer'; import TextHighlight from './text-highlight'; -function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSettings, onLinkChange, onSettingChange = { noop }, linkSettings } ) { +function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSettingChange = { noop }, linkSettings } ) { // State const [ inputValue, setInputValue ] = useState( '' ); const [ isEditingLink, setIsEditingLink ] = useState( true ); @@ -137,16 +137,6 @@ function LinkControl( { currentLink, fetchSearchSuggestions, renderAdditionalSet /* eslint-enable react/jsx-key */ }; - const LinkControlAdditionalSettings = () => ( -
- - { isFunction( renderAdditionalSettings ) && renderAdditionalSettings() } -
- ); - return ( + ) }
diff --git a/packages/block-editor/src/components/link-control/settings-drawer.js b/packages/block-editor/src/components/link-control/settings-drawer.js new file mode 100644 index 00000000000000..be6598484a3f6e --- /dev/null +++ b/packages/block-editor/src/components/link-control/settings-drawer.js @@ -0,0 +1,23 @@ +/** + * External dependencies + */ +import { partial } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + ToggleControl, +} from '@wordpress/components'; + +const LinkControlSettingsDrawer = ( { settings, onSettingChange } ) => ( +
+ +
+); + +export default LinkControlSettingsDrawer; From 37de5d97ecea7740ec052afe6b88226de1328b09 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 17 Oct 2019 11:52:18 +0100 Subject: [PATCH 050/113] Refactor search items into a component --- .../src/components/link-control/index.js | 29 ++++-------- .../components/link-control/search-item.js | 44 +++++++++++++++++++ .../src/components/url-input/index.js | 1 - 3 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/search-item.js diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 9ec8e077bdee8a..047e8e25500979 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -4,7 +4,6 @@ import { Button, IconButton, - Icon, ExternalLink, Popover, } from '@wordpress/components'; @@ -41,7 +40,7 @@ import { } from '../'; import LinkControlSettingsDrawer from './settings-drawer'; -import TextHighlight from './text-highlight'; +import LinkControlSearchItem from './search-item'; function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSettingChange = { noop }, linkSettings } ) { // State @@ -111,25 +110,15 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet
{ suggestions.map( ( suggestion, index ) => ( - + isSelected={ index === selectedSuggestion } + isUrl={ suggestion.type.toLowerCase() === 'url' } + searchTerm={ inputValue } + /> ) ) }
diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js new file mode 100644 index 00000000000000..45a106ad1d7420 --- /dev/null +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import TextHighlight from './text-highlight'; + +/** + * WordPress dependencies + */ +import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; + +import { + Icon, +} from '@wordpress/components'; + +export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = false, onClick, isUrl = false, searchTerm = '' } ) => { + return ( + + ); +}; + +export default LinkControlSearchItem; + diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 7d56fa5eb0c798..0503a96b4940ed 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -264,7 +264,6 @@ class URLInput extends Component { const buildSuggestionItemProps = ( suggestion, index ) => { return { - key: suggestion.id, role: 'option', tabIndex: '-1', id: `${ suggestionOptionIdPrefix }-${ index }`, From ba32148021a16e89d105f089a202e382440bf018 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 17 Oct 2019 12:05:32 +0100 Subject: [PATCH 051/113] Refactor Input and Search to component --- .../src/components/link-control/index.js | 68 +++--------------- .../components/link-control/input-search.js | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+), 59 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/input-search.js diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 047e8e25500979..82e5892b366217 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -3,7 +3,6 @@ */ import { Button, - IconButton, ExternalLink, Popover, } from '@wordpress/components'; @@ -12,44 +11,27 @@ import { * External dependencies */ import classnames from 'classnames'; -import { __ } from '@wordpress/i18n'; import { isFunction, partialRight, noop } from 'lodash'; import { useCallback, useState, - useRef, } from '@wordpress/element'; -import { - LEFT, - RIGHT, - UP, - DOWN, - BACKSPACE, - ENTER, -} from '@wordpress/keycodes'; - import { safeDecodeURI, filterURLForDisplay, isURL } from '@wordpress/url'; /** * Internal dependencies */ -import { - URLInput, -} from '../'; - import LinkControlSettingsDrawer from './settings-drawer'; import LinkControlSearchItem from './search-item'; +import LinkControlInputSearch from './input-search'; function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSettingChange = { noop }, linkSettings } ) { // State const [ inputValue, setInputValue ] = useState( '' ); const [ isEditingLink, setIsEditingLink ] = useState( true ); - // Refs - const autocompleteRef = useRef( null ); - // Handlers const onInputChange = ( value = '' ) => { setInputValue( value ); @@ -87,17 +69,6 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet } ]; }; - const stopPropagation = ( event ) => { - event.stopPropagation(); - }; - - const stopPropagationRelevantKeys = ( event ) => { - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { - // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - stopPropagation( event ); - } - }; - // Effects const getSearchHandler = useCallback( ( value ) => { return ( isURL( value ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); @@ -160,35 +131,14 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet ) } { isEditingLink && ( -
- { - stopPropagationRelevantKeys( event ); - if ( event.keyCode === ENTER ) { - onLinkSelect( event, suggestion ); - } - } } - onKeyPress={ stopPropagation } - placeholder={ __( 'Search or type url' ) } - renderSuggestions={ renderSearchResults } - fetchLinkSuggestions={ getSearchHandler } - handleURLSuggestions={ true } - /> - - - - + ) } { ! isEditingLink && ( diff --git a/packages/block-editor/src/components/link-control/input-search.js b/packages/block-editor/src/components/link-control/input-search.js new file mode 100644 index 00000000000000..aeaeaef6360140 --- /dev/null +++ b/packages/block-editor/src/components/link-control/input-search.js @@ -0,0 +1,69 @@ + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + IconButton, +} from '@wordpress/components'; +import { + LEFT, + RIGHT, + UP, + DOWN, + BACKSPACE, + ENTER, +} from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { + URLInput, +} from '../'; + +const LinkControlInputSearch = ( { value, onChange, onSelect, renderSuggestions, fetchSuggestions, onReset } ) => { + const stopPropagation = ( event ) => { + event.stopPropagation(); + }; + + const stopPropagationRelevantKeys = ( event ) => { + if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. + stopPropagation( event ); + } + }; + + return ( +
+ { + stopPropagationRelevantKeys( event ); + if ( event.keyCode === ENTER ) { + onSelect( event, suggestion ); + } + } } + onKeyPress={ stopPropagation } + placeholder={ __( 'Search or type url' ) } + renderSuggestions={ renderSuggestions } + fetchLinkSuggestions={ fetchSuggestions } + handleURLSuggestions={ true } + /> + + + + + ); +}; + +export default LinkControlInputSearch; From 33467f302a0ff319f6b0a1a99d14348d1b7d0dd2 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 17 Oct 2019 12:21:46 +0100 Subject: [PATCH 052/113] Fix missing selected state on search suggestions --- .../block-editor/src/components/link-control/style.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 11ee3c329d7a66..52b8fee3ff133b 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -91,6 +91,14 @@ background-color: #e9e9e9; } + &.is-selected { + background: #f2f2f2; + + .block-editor-link-control__search-item-type { + background: #fff; + } + } + &.is-current { background: transparent; border: 0; From 553be994b7b61b380247c6e1497c5d4de1d52d9e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 17 Oct 2019 12:23:09 +0100 Subject: [PATCH 053/113] Tweak line height on search suggestion url path --- packages/block-editor/src/components/link-control/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 52b8fee3ff133b..ad850b3ea8c899 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -138,6 +138,7 @@ display: block; color: #999; font-size: 0.9em; + line-height: 1.3; } .block-editor-link-control__search-item-type { From ad46c87591d71528a36f122a8b4f41c0ca30c676 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 18 Oct 2019 17:40:25 +0100 Subject: [PATCH 054/113] =?UTF-8?q?Augment=20test=20for=20URL-like=20by=20?= =?UTF-8?q?testing=20for=20=E2=80=9Cwww.=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/block-editor/src/components/link-control/index.js | 6 +++--- .../block-editor/src/components/link-control/search-item.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 82e5892b366217..ccdc7c8c484e95 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -18,7 +18,7 @@ import { useState, } from '@wordpress/element'; -import { safeDecodeURI, filterURLForDisplay, isURL } from '@wordpress/url'; +import { safeDecodeURI, filterURLForDisplay, isURL, prependHTTP } from '@wordpress/url'; /** * Internal dependencies @@ -65,13 +65,13 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet id: 1, title: value, type: 'URL', - url: value, + url: prependHTTP( value ), } ]; }; // Effects const getSearchHandler = useCallback( ( value ) => { - return ( isURL( value ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); + return ( isURL( value ) || value.includes( 'www.' ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); }, [ handleURLSearch, fetchSearchSuggestions ] ); // Render Components 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 45a106ad1d7420..d84f90613903b4 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -11,7 +11,7 @@ import TextHighlight from './text-highlight'; /** * WordPress dependencies */ -import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; +import { safeDecodeURI } from '@wordpress/url'; import { Icon, @@ -33,7 +33,7 @@ export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = fal - { suggestion.info || filterURLForDisplay( safeDecodeURI( suggestion.url ) ) || '' } + { safeDecodeURI( suggestion.url ) || '' } { suggestion.type.toLowerCase() || '' } From 26c292fbee7d6ade3919958ebd8b6d59cefc8e48 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 18 Oct 2019 18:01:23 +0100 Subject: [PATCH 055/113] Fix to stop url overflows and wrapping on to multiple lines --- .../block-editor/src/components/link-control/index.js | 2 +- .../src/components/link-control/search-item.js | 6 ++++-- .../block-editor/src/components/link-control/style.scss | 8 ++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index ccdc7c8c484e95..671caf3b0dc5fb 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -87,7 +87,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet suggestion={ suggestion } onClick={ partialRight( onLinkSelect, suggestion ) } isSelected={ index === selectedSuggestion } - isUrl={ suggestion.type.toLowerCase() === 'url' } + isURL={ suggestion.type.toLowerCase() === 'url' } searchTerm={ inputValue } /> ) ) } 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 d84f90613903b4..5b9db09c15f02e 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -17,16 +17,18 @@ import { Icon, } from '@wordpress/components'; -export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = false, onClick, isUrl = false, searchTerm = '' } ) => { +export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = false, onClick, isURL = false, searchTerm = '' } ) => { return ( ); }; From 13f5ce19b3efe173427c576791784a27078c0f7d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 21 Oct 2019 13:48:45 +0100 Subject: [PATCH 057/113] Avoid reading out slug/URL for entity results --- .../block-editor/src/components/link-control/search-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a2d6f9a0ad87dd..9fd265aa735cf2 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -35,7 +35,7 @@ export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = fal - { safeDecodeURI( suggestion.url ) || '' } + { safeDecodeURI( suggestion.url ) || '' } { suggestion.type && ( { suggestion.type } From 556088aef8f776f57def0e869e072279e2ea35b6 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 21 Oct 2019 13:51:10 +0100 Subject: [PATCH 058/113] Ensures i18n of change button --- .../src/components/link-control/index.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 671caf3b0dc5fb..f28220329457b0 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -1,3 +1,9 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { isFunction, partialRight, noop } from 'lodash'; + /** * WordPress dependencies */ @@ -6,12 +12,7 @@ import { ExternalLink, Popover, } from '@wordpress/components'; - -/** - * External dependencies - */ -import classnames from 'classnames'; -import { isFunction, partialRight, noop } from 'lodash'; +import { __ } from '@wordpress/i18n'; import { useCallback, @@ -125,7 +126,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet
) } From 1cdd9394a3f74874f56571fab908b51aa4e95150 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 21 Oct 2019 14:11:42 +0100 Subject: [PATCH 059/113] Always offer URL result in search suggestions as default --- .../src/components/link-control/index.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f28220329457b0..89d1b99827854e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -63,16 +63,30 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet const handleURLSearch = async ( value ) => { return [ { - id: 1, + id: '1', title: value, type: 'URL', url: prependHTTP( value ), } ]; }; + const handleEntitySearch = async ( value ) => { + const results = await Promise.all( [ + fetchSearchSuggestions( value ), + handleURLSearch( value ), + ] ); + + const couldBeURL = ! value.includes( ' ' ); + + // 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. + return couldBeURL ? results[ 0 ].concat( results[ 1 ] ) : results[ 0 ]; + }; + // Effects const getSearchHandler = useCallback( ( value ) => { - return ( isURL( value ) || value.includes( 'www.' ) ) ? handleURLSearch( value ) : fetchSearchSuggestions( value ); + return ( isURL( value ) || value.includes( 'www.' ) ) ? handleURLSearch( value ) : handleEntitySearch( value ); }, [ handleURLSearch, fetchSearchSuggestions ] ); // Render Components From 2588b9ed3908e37d3b19b3f80e564bbd84792ad6 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 21 Oct 2019 14:27:52 +0100 Subject: [PATCH 060/113] Fix loading spinner position and dim results during loading Addresses https://github.com/WordPress/gutenberg/pull/17846#issuecomment-543244810 --- .../src/components/link-control/index.js | 7 +++++-- .../src/components/link-control/style.scss | 15 +++++++++++---- .../src/components/url-input/index.js | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 89d1b99827854e..f26619433cdad6 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -90,11 +90,14 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet }, [ handleURLSearch, fetchSearchSuggestions ] ); // Render Components - const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion } ) => { + const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, isLoading } ) => { + const resultsListClasses = classnames( 'block-editor-link-control__search-results', { + 'is-loading': isLoading, + } ); /* eslint-disable react/jsx-key */ return (
-
+
{ suggestions.map( ( suggestion, index ) => ( Date: Mon, 21 Oct 2019 14:35:07 +0100 Subject: [PATCH 061/113] Fix scroll shadows to use valid alpha transparent values in gradient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes broken shadows in Safari which didn’t recognise transparent as a value to transition to in a gradient. --- packages/block-editor/src/components/link-control/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index d0db7b369589c9..69f87d79fdfe3d 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -55,14 +55,14 @@ height: $grid-size-large/2; top: -1px; bottom: auto; - background: linear-gradient(to bottom, #fff 0%, transparent 100%); + background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); } &::after { height: 20px; bottom: -1px; top: auto; - background: linear-gradient(to bottom, transparent 0%, #fff 100%); + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); } } From 98836fde737c7e1495f1dbac8d2d61ace1a66a72 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 23 Oct 2019 10:32:15 +0100 Subject: [PATCH 062/113] Adds instructional text in place of URL for suggestions that are URLs Addresses designer feedback https://github.com/WordPress/gutenberg/issues/17557#issuecomment-545030027 --- .../block-editor/src/components/link-control/index.js | 2 +- .../src/components/link-control/search-item.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f26619433cdad6..83de03b13fb949 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -139,7 +139,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet > { currentLink.title } - { currentLink.info || filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' } + { filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' }
diff --git a/playground/src/index.js b/playground/src/index.js index 37e563947a89bc..87664538571408 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -102,19 +102,18 @@ function App() { { setLink( theLink ); } } - currentLink={ link } - linkSettings={ linkSettings } - onSettingChange={ ( setting, value ) => { + onSettingsChange={ ( setting, value ) => { setLinkSettings( { ...linkSettings, [ setting ]: value, } ); } } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } /> From 811ad11404d994feec20e9f8627adede33e906bd Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 23 Oct 2019 11:04:54 +0100 Subject: [PATCH 064/113] Update line length to improve readability Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r337842799 --- .../src/components/url-input/index.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 61e11faab5245d..9867391d331501 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -250,8 +250,24 @@ class URLInput extends Component { } render() { - const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder, placeholder = __( 'Paste URL or type to search' ), renderSuggestions } = this.props; - const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state; + const { + instanceId, + className, + id, + isFullWidth, + hasBorder, + renderSuggestions, + placeholder = __( 'Paste URL or type to search' ), + value = '', + autoFocus = true, + } = this.props; + + const { + showSuggestions, + suggestions, + selectedSuggestion, + loading, + } = this.state; const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`; const suggestionOptionIdPrefix = `block-editor-url-input-suggestion-${ instanceId }`; From 9cd6d18ba9cf4a43035ded5c9240befbdaf96959 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 23 Oct 2019 11:11:21 +0100 Subject: [PATCH 065/113] Update to avoid need to utilise partialRight util from lodash Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r337882576 --- packages/block-editor/src/components/link-control/index.js | 6 +++--- .../src/components/link-control/input-search.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index e477e176875241..fab27c85ab0b3c 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { isFunction, partialRight, noop } from 'lodash'; +import { isFunction, noop } from 'lodash'; /** * WordPress dependencies @@ -46,7 +46,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet setInputValue( '' ); } ); - const onLinkSelect = ( event, suggestion ) => { + const onLinkSelect = ( suggestion ) => ( event ) => { event.preventDefault(); event.stopPropagation(); @@ -103,7 +103,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet key={ `${ suggestion.id }-${ index }` } itemProps={ buildSuggestionItemProps( suggestion, index ) } suggestion={ suggestion } - onClick={ partialRight( onLinkSelect, suggestion ) } + onClick={ onLinkSelect( suggestion ) } isSelected={ index === selectedSuggestion } isURL={ suggestion.type.toLowerCase() === 'url' } searchTerm={ inputValue } diff --git a/packages/block-editor/src/components/link-control/input-search.js b/packages/block-editor/src/components/link-control/input-search.js index aeaeaef6360140..90c741a209595c 100644 --- a/packages/block-editor/src/components/link-control/input-search.js +++ b/packages/block-editor/src/components/link-control/input-search.js @@ -43,7 +43,7 @@ const LinkControlInputSearch = ( { value, onChange, onSelect, renderSuggestions, onKeyDown={ ( event, suggestion ) => { stopPropagationRelevantKeys( event ); if ( event.keyCode === ENTER ) { - onSelect( event, suggestion ); + onSelect( suggestion )( event ); } } } onKeyPress={ stopPropagation } From bdb62172a9817fea0fb5e0688bfffea929acdcb7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 23 Oct 2019 11:14:39 +0100 Subject: [PATCH 066/113] Updates key to avoid usage of index We cannot assume the suggestion `id` will be unique. This is because at the moment the search results are `Post`s. However in the future we may also need to include `Category` terms and the term IDs could easily clash with the Post IDs as they are in different DB tables. Using the `type` to differentiate the key. Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r337883174 --- packages/block-editor/src/components/link-control/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index fab27c85ab0b3c..96a920b97cac5f 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -94,13 +94,13 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet const resultsListClasses = classnames( 'block-editor-link-control__search-results', { 'is-loading': isLoading, } ); - /* eslint-disable react/jsx-key */ + return (
{ suggestions.map( ( suggestion, index ) => (
); - /* eslint-enable react/jsx-key */ }; return ( From d0a348bf6df430376720c776c5969e22c95cfeec Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 23 Oct 2019 11:15:46 +0100 Subject: [PATCH 067/113] Update to remote isFunction check in favour of direct check Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r337885206 --- packages/block-editor/src/components/url-input/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 9867391d331501..09fdbaa5ec2d6e 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -213,7 +213,7 @@ class URLInput extends Component { } } - if ( isFunction( this.props.onKeyDown ) ) { + if ( this.props.onKeyDown ) { this.props.onKeyDown( event, suggestion ); } } From da212f0ee24a7786b9b8cd8ff4be14e455c46ced Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 23 Oct 2019 14:56:32 +0100 Subject: [PATCH 068/113] Update to handle mailto and tel protocols and internal links --- .../src/components/link-control/index.js | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 96a920b97cac5f..f138dbff9eb2a0 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { isFunction, noop } from 'lodash'; +import { isFunction, noop, startsWith } from 'lodash'; /** * WordPress dependencies @@ -19,7 +19,13 @@ import { useState, } from '@wordpress/element'; -import { safeDecodeURI, filterURLForDisplay, isURL, prependHTTP } from '@wordpress/url'; +import { + safeDecodeURI, + filterURLForDisplay, + isURL, + prependHTTP, + getProtocol, +} from '@wordpress/url'; /** * Internal dependencies @@ -61,19 +67,35 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet setIsEditingLink( true ); }; - const handleURLSearch = async ( value ) => { + const handleDirectEntry = async ( value ) => { + let type = 'URL'; + + const protocol = getProtocol( value ) || ''; + + if ( protocol.includes( 'mailto' ) ) { + type = 'mailto'; + } + + if ( protocol.includes( 'tel' ) ) { + type = 'tel'; + } + + if ( startsWith( value, '#' ) ) { + type = 'internal'; + } + return [ { id: '1', title: value, - type: 'URL', - url: prependHTTP( value ), + url: type === 'URL' ? prependHTTP( value ) : value, + type, } ]; }; const handleEntitySearch = async ( value ) => { const results = await Promise.all( [ fetchSearchSuggestions( value ), - handleURLSearch( value ), + handleDirectEntry( value ), ] ); const couldBeURL = ! value.includes( ' ' ); @@ -86,8 +108,15 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet // Effects const getSearchHandler = useCallback( ( value ) => { - return ( isURL( value ) || value.includes( 'www.' ) ) ? handleURLSearch( value ) : handleEntitySearch( value ); - }, [ handleURLSearch, fetchSearchSuggestions ] ); + const protocol = getProtocol( value ) || ''; + const isMailto = protocol.includes( 'mailto' ); + const isInternal = startsWith( value, '#' ); + const isTel = protocol.includes( 'tel' ); + + const handleManualEntry = isInternal || isMailto || isTel || isURL( value ) || value.includes( 'www.' ); + + return ( handleManualEntry ) ? handleDirectEntry( value ) : handleEntitySearch( value ); + }, [ handleDirectEntry, fetchSearchSuggestions ] ); // Render Components const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, isLoading } ) => { From 19d5e645b7f742100e238866b033cf0c3113ef14 Mon Sep 17 00:00:00 2001 From: retrofox Date: Tue, 22 Oct 2019 16:41:19 -0300 Subject: [PATCH 069/113] url-input: handle onKeyPress type event --- packages/block-editor/src/components/url-input/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 09fdbaa5ec2d6e..e974839635a8c6 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { throttle, isFunction } from 'lodash'; +import { throttle, isFunction, noop } from 'lodash'; import classnames from 'classnames'; import scrollIntoView from 'dom-scroll-into-view'; @@ -305,6 +305,7 @@ class URLInput extends Component { onInput={ stopEventPropagation } placeholder={ placeholder } onKeyDown={ this.onKeyDown } + onKeyPress={ this.props.onKeyPress || noop } role="combobox" aria-expanded={ showSuggestions } aria-autocomplete="list" From 548279ba6332de220ac864700a4352b3b6419523 Mon Sep 17 00:00:00 2001 From: retrofox Date: Tue, 22 Oct 2019 16:52:33 -0300 Subject: [PATCH 070/113] link-control: add className prop --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f138dbff9eb2a0..3de744c13cf4ff 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -34,7 +34,7 @@ import LinkControlSettingsDrawer from './settings-drawer'; import LinkControlSearchItem from './search-item'; import LinkControlInputSearch from './input-search'; -function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSettingsChange = { noop }, currentSettings } ) { +function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkChange, onSettingsChange = { noop }, currentSettings } ) { // State const [ inputValue, setInputValue ] = useState( '' ); const [ isEditingLink, setIsEditingLink ] = useState( true ); @@ -145,7 +145,7 @@ function LinkControl( { currentLink, fetchSearchSuggestions, onLinkChange, onSet return ( Date: Tue, 22 Oct 2019 16:54:40 -0300 Subject: [PATCH 071/113] link-control: add README file --- .../src/components/link-control/README.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/block-editor/src/components/link-control/README.md diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md new file mode 100644 index 00000000000000..0142b969cd7342 --- /dev/null +++ b/packages/block-editor/src/components/link-control/README.md @@ -0,0 +1,53 @@ +# Link Control + +### className + +- Type: `string` +- Required: NO + +### defaultOpen + +- Type: `Boolean` +- Required: No + +### fetchSearchSuggestions + +- Type: `Function` +- Required: YES + +### currentLink + +- Type: +- Required: + +### linkSettings + +- Type: +- Required: + +## Event handlers + +### onClose + +- Type: `Function` +- Required: + +### onKeyDown + +- Type: `Function` +- Required: + +### onKeyPress + +- Type: `Function` +- Required: + +### onLinkChange + +- Type: `Function` +- Required: + +### onSettingChange + +- Type: `Function` +- Required: From fd3a6efcd0523d2154237922521e60d52ab8377a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 09:46:25 +0100 Subject: [PATCH 072/113] Remove unnecessary use of useCallback Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r338363236 --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 3de744c13cf4ff..22490f2176cbe4 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -48,9 +48,9 @@ function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkCh resetInput(); }; - const resetInput = useCallback( () => { + const resetInput = () => { setInputValue( '' ); - } ); + }; const onLinkSelect = ( suggestion ) => ( event ) => { event.preventDefault(); From b314c49c4750883173b36e36bc55d40f5353d218 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 11:31:56 +0100 Subject: [PATCH 073/113] Fix current automated tests --- .../link-control/test/__snapshots__/index.js.snap | 4 +--- .../src/components/link-control/test/index.js | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap index 4f743b6d8b0885..df68c094eabc63 100644 --- a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -1,5 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should display with required props 1`] = `"
"`; - -exports[`Basic rendering should render with required props 1`] = `"
"`; +exports[`Basic rendering should display with required props 1`] = `"
"`; 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 c355281f7e413d..6f38062b5eba2e 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -38,7 +38,6 @@ describe( 'Basic rendering', () => { } ); // Search Input UI - // const searchInputLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerText === 'Search or input url' ); const searchInput = container.querySelector( 'input[aria-label="URL"]' ); // expect( searchInputLabel ).not.toBeNull(); @@ -98,7 +97,7 @@ describe( 'Searching', () => { } ); it( 'should display search suggestions when current input value is not URL-like', async ( ) => { - const searchTerm = 'Hello'; + const searchTerm = 'Hello world'; act( () => { render( @@ -146,7 +145,7 @@ describe( 'Searching', () => { } ); it( 'should display a single suggestion result when the current input value is URL-like', async ( ) => { - const searchTerm = 'http://make.wordpress.com'; + const searchTerm = 'https://make.wordpress.com'; act( () => { render( @@ -174,11 +173,11 @@ describe( 'Searching', () => { expect( searchResultElements ).toHaveLength( expectedResultsLength ); expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'url' ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); it( 'should reset input and search results when search term is cleared or reset', async ( ) => { - const searchTerm = 'Hello'; + const searchTerm = 'Hello world'; act( () => { render( From 5aeb5317a9af5f36d2b2ccbba4c7c90e75f7b6d7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 11:47:13 +0100 Subject: [PATCH 074/113] Improves URL handling test to run for multiple URL value variations --- .../block-editor/src/components/link-control/index.js | 2 +- .../src/components/link-control/test/index.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 22490f2176cbe4..a8dc35d7934b0e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -113,7 +113,7 @@ function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkCh const isInternal = startsWith( value, '#' ); const isTel = protocol.includes( 'tel' ); - const handleManualEntry = isInternal || isMailto || isTel || isURL( value ) || value.includes( 'www.' ); + const handleManualEntry = isInternal || isMailto || isTel || isURL( value ) || ( value && value.includes( 'www.' ) ); return ( handleManualEntry ) ? handleDirectEntry( value ) : handleEntitySearch( value ); }, [ handleDirectEntry, fetchSearchSuggestions ] ); 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 6f38062b5eba2e..e9d670637ad578 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -144,9 +144,11 @@ describe( 'Searching', () => { expect( searchResultElements ).toHaveLength( 0 ); } ); - it( 'should display a single suggestion result when the current input value is URL-like', async ( ) => { - const searchTerm = 'https://make.wordpress.com'; - + it.each( [ + [ 'https://make.wordpress.org' ], // explicit https + [ 'http://make.wordpress.org' ], // explicit http + [ 'www.wordpress.org' ], // usage of "www" + ] )( 'should display a single suggestion result when the current input value is URL-like (eg: %s)', async ( searchTerm ) => { act( () => { render( Date: Thu, 24 Oct 2019 12:01:07 +0100 Subject: [PATCH 075/113] Updates to display the URL type in the search results Previously only true `http` URLs were formatted with the correct type and the instructional text. Fixes so that all types of manual URL entry are correctly shown as such in the search results. Adds test to cover mailto variant of this. --- .../src/components/link-control/index.js | 4 +- .../src/components/link-control/test/index.js | 88 +++++++++++++------ 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index a8dc35d7934b0e..5ecc298ed20ffc 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -124,6 +124,8 @@ function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkCh 'is-loading': isLoading, } ); + const manualLinkEntryTypes = [ 'url', 'mailto', 'tel', 'internal' ]; + return (
@@ -134,7 +136,7 @@ function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkCh suggestion={ suggestion } onClick={ onLinkSelect( suggestion ) } isSelected={ index === selectedSuggestion } - isURL={ suggestion.type.toLowerCase() === 'url' } + isURL={ manualLinkEntryTypes.includes( suggestion.type.toLowerCase() ) } searchTerm={ inputValue } /> ) ) } 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 e9d670637ad578..1204dc716dc680 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -47,7 +47,7 @@ describe( 'Basic rendering', () => { } ); } ); -describe( 'Searching', () => { +describe( 'Searching for a link', () => { it( 'should display loading UI when input is valid but search results have yet to be returned', async () => { const searchTerm = 'Hello'; @@ -144,6 +144,56 @@ describe( 'Searching', () => { expect( searchResultElements ).toHaveLength( 0 ); } ); + it( 'should reset input and search results when search term is cleared or reset', async ( ) => { + const searchTerm = 'Hello world'; + + act( () => { + render( + , container + ); + } ); + + let searchResultElements; + let searchInput; + + // Search Input UI + searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + // Check we have definitely rendered some suggestions + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); + + // Grab the reset button now it's available + const resetUI = container.querySelector( '[aria-label="Reset"]' ); + + act( () => { + Simulate.click( resetUI ); + } ); + + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + expect( searchInput.value ).toBe( '' ); + expect( searchResultElements ).toHaveLength( 0 ); + } ); +} ); + +describe( 'Manual link entry', () => { it.each( [ [ 'https://make.wordpress.org' ], // explicit https [ 'http://make.wordpress.org' ], // explicit http @@ -178,8 +228,9 @@ describe( 'Searching', () => { expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); - it( 'should reset input and search results when search term is cleared or reset', async ( ) => { - const searchTerm = 'Hello world'; + it( 'should recognise "mailto" links and handle as manual entry', async () => { + const searchTerm = 'mailto:example123456@wordpress.org'; + const searchType = 'mailto'; act( () => { render( @@ -189,11 +240,8 @@ describe( 'Searching', () => { ); } ); - let searchResultElements; - let searchInput; - // Search Input UI - searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); // Simulate searching for a term act( () => { @@ -204,25 +252,13 @@ describe( 'Searching', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - - // Check we have definitely rendered some suggestions - expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); - - // Grab the reset button now it's available - const resetUI = container.querySelector( '[aria-label="Reset"]' ); - - act( () => { - Simulate.click( resetUI ); - } ); - - await eventLoopTick(); - - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; + const expectedResultsLength = 1; - expect( searchInput.value ).toBe( '' ); - expect( searchResultElements ).toHaveLength( 0 ); + expect( searchResultElements ).toHaveLength( expectedResultsLength ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchType ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); } ); From 63201c9c86bf22b48921e0cc7ab1b54b609d9fbe Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 12:06:56 +0100 Subject: [PATCH 076/113] Refactor tests to assert against all valid protocol formats and link variants This now includes tel, mailto and internal links. --- .../src/components/link-control/test/index.js | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) 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 1204dc716dc680..cc8410aabdb8fc 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -228,37 +228,40 @@ describe( 'Manual link entry', () => { expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); - it( 'should recognise "mailto" links and handle as manual entry', async () => { - const searchTerm = 'mailto:example123456@wordpress.org'; - const searchType = 'mailto'; - - act( () => { - render( - , container - ); + describe( 'Alternative link protocols and formats', () => { + it.each( [ + [ 'mailto:example123456@wordpress.org', 'mailto' ], + [ 'tel:example123456@wordpress.org', 'tel' ], + [ '#internal-anchor', 'internal' ], + ] )( 'should recognise "%s" as a %s link and handle as manual entry by displaying a single suggestion', async ( searchTerm, searchType ) => { + 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: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; + const expectedResultsLength = 1; + + expect( searchResultElements ).toHaveLength( expectedResultsLength ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchType ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); - - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); - - // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); - - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; - const expectedResultsLength = 1; - - expect( searchResultElements ).toHaveLength( expectedResultsLength ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchType ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); } ); From 49181841cd6237a52669705dfc6e1219e34493f1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 12:17:09 +0100 Subject: [PATCH 077/113] Adds test to cover display of fallback URL search result for search values that are potentially URLS --- .../src/components/link-control/test/index.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 cc8410aabdb8fc..cab1926a4ce712 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -3,6 +3,7 @@ */ import { render, unmountComponentAtNode } from 'react-dom'; import { act, Simulate } from 'react-dom/test-utils'; +import { last } from 'lodash'; /** * Internal dependencies @@ -144,6 +145,41 @@ describe( 'Searching for a link', () => { expect( searchResultElements ).toHaveLength( 0 ); } ); + it.each( [ [ 'couldbeurlorentitysearchterm' ], [ 'ThisCouldAlsoBeAValidURL' ] ] )( 'should always show a URL suggestion as a default fallback when the search term could potentially be a valid url', async ( searchTerm ) => { + 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: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const lastSearchResultItemHTML = last( searchResultElements ).innerHTML; + const additionalDefaultFallbackURLSuggestionLength = 1; + + // We should see a search result for each of the expect search suggestions + // plus 1 additional one for the fallback URL suggestion + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length + additionalDefaultFallbackURLSuggestionLength ); + + // The last item should be a URL search suggestion + expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'URL' ) ); + expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); + } ); + it( 'should reset input and search results when search term is cleared or reset', async ( ) => { const searchTerm = 'Hello world'; @@ -225,6 +261,7 @@ describe( 'Manual link entry', () => { expect( searchResultElements ).toHaveLength( expectedResultsLength ); expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'URL' ) ); expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); From cd29ab51b1412a5818fcc8afe15011cba73454e8 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 12:28:56 +0100 Subject: [PATCH 078/113] =?UTF-8?q?Adds=20tests=20to=20check=20URL=20sugge?= =?UTF-8?q?stions=20don=E2=80=99t=20display=20for=20non-URLs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/link-control/test/index.js | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) 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 cab1926a4ce712..ea04cf8a9fab35 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -3,7 +3,7 @@ */ import { render, unmountComponentAtNode } from 'react-dom'; import { act, Simulate } from 'react-dom/test-utils'; -import { last } from 'lodash'; +import { first, last } from 'lodash'; /** * Internal dependencies @@ -97,8 +97,9 @@ describe( 'Searching for a link', () => { expect( loadingUI ).toBeNull(); } ); - it( 'should display search suggestions when current input value is not URL-like', async ( ) => { + it( 'should display only search suggestions when current input value is not URL-like', async ( ) => { const searchTerm = 'Hello world'; + const firstFauxSuggestion = first( fauxEntitySuggestions ); act( () => { render( @@ -108,16 +109,9 @@ describe( 'Searching for a link', () => { ); } ); - let searchResultElements; - // Search Input UI const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); - - expect( searchResultElements ).toHaveLength( 0 ); - // Simulate searching for a term act( () => { Simulate.change( searchInput, { target: { value: searchTerm } } ); @@ -127,25 +121,39 @@ describe( 'Searching for a link', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchResultItemHTML = first( searchResultElements ).innerHTML; + const lastSearchResultItemHTML = last( searchResultElements ).innerHTML; expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); - // Reset the search term - act( () => { - Simulate.change( searchInput, { target: { value: '' } } ); - } ); + // Sanity check that a search suggestion shows up corresponding to the data + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( firstFauxSuggestion.title ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( firstFauxSuggestion.type ) ); - // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); + // The fallback URL suggestion should not be shown when input is not URL-like + expect( lastSearchResultItemHTML ).not.toEqual( expect.stringContaining( 'URL' ) ); + } ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + // it('should not display any search result when the input is empty or has been cleared or reset', async () => { + // // Reset the search term + // act( () => { + // Simulate.change( searchInput, { target: { value: '' } } ); + // } ); - expect( searchResultElements ).toHaveLength( 0 ); - } ); + // // fetchFauxEntitySuggestions resolves on next "tick" of event loop + // await eventLoopTick(); + + // // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - it.each( [ [ 'couldbeurlorentitysearchterm' ], [ 'ThisCouldAlsoBeAValidURL' ] ] )( 'should always show a URL suggestion as a default fallback when the search term could potentially be a valid url', async ( searchTerm ) => { + // expect( searchResultElements ).toHaveLength( 0 ); + // }) + + it.each( [ + [ 'couldbeurlorentitysearchterm' ], + [ 'ThisCouldAlsoBeAValidURL' ], + ] )( 'should display a URL suggestion as a default fallback for the search term "%s" which could potentially be a valid url.', async ( searchTerm ) => { act( () => { render( Date: Wed, 23 Oct 2019 13:47:26 -0300 Subject: [PATCH 079/113] url-input: remove unneeded `suggestion` const --- packages/block-editor/src/components/url-input/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index e974839635a8c6..d53598ca846525 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -175,8 +175,6 @@ class URLInput extends Component { return; } - const suggestion = this.state.suggestions[ this.state.selectedSuggestion ]; - switch ( event.keyCode ) { case UP: { event.stopPropagation(); @@ -198,7 +196,7 @@ class URLInput extends Component { } case TAB: { if ( this.state.selectedSuggestion !== null ) { - this.selectLink( suggestion ); + this.selectLink( selectedSuggestion ); // Announce a link has been selected when tabbing away from the input field. this.props.speak( __( 'Link selected.' ) ); } @@ -207,14 +205,14 @@ class URLInput extends Component { case ENTER: { if ( this.state.selectedSuggestion !== null ) { event.stopPropagation(); - this.selectLink( suggestion ); + this.selectLink( selectedSuggestion ); } break; } } if ( this.props.onKeyDown ) { - this.props.onKeyDown( event, suggestion ); + this.props.onKeyDown( event, selectedSuggestion ); } } From d8895d5f87116bffa65dd0adac940375048d2d47 Mon Sep 17 00:00:00 2001 From: retrofox Date: Wed, 23 Oct 2019 13:50:52 -0300 Subject: [PATCH 080/113] url-input: always trigger onKeyDown event --- .../block-editor/src/components/url-input/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index d53598ca846525..25b785d04080a3 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -137,6 +137,12 @@ class URLInput extends Component { onKeyDown( event ) { const { showSuggestions, selectedSuggestion, suggestions, loading } = this.state; + + // Trigger `onKeyDown` event, passing the selected suggestion besides the event class. + if ( this.props.onKeyDown ) { + this.props.onKeyDown( event, selectedSuggestion ); + } + // If the suggestions are not shown or loading, we shouldn't handle the arrow keys // We shouldn't preventDefault to allow block arrow keys navigation if ( ! showSuggestions || ! suggestions.length || loading ) { @@ -210,10 +216,6 @@ class URLInput extends Component { break; } } - - if ( this.props.onKeyDown ) { - this.props.onKeyDown( event, selectedSuggestion ); - } } selectLink( suggestion ) { From 2413016071f85a10be30b49dc42f7dad25fe02f5 Mon Sep 17 00:00:00 2001 From: retrofox Date: Wed, 23 Oct 2019 18:09:49 -0300 Subject: [PATCH 081/113] link-control: delegate handling keydown event Instead of this, let's propagate the onKeyDown and onKeyPress events to the parent component --- .../src/components/link-control/index.js | 13 +++++- .../components/link-control/input-search.js | 45 +++++++------------ 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 5ecc298ed20ffc..df4137fc7a9f21 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -34,7 +34,16 @@ import LinkControlSettingsDrawer from './settings-drawer'; import LinkControlSearchItem from './search-item'; import LinkControlInputSearch from './input-search'; -function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkChange, onSettingsChange = { noop }, currentSettings } ) { +function LinkControl( { + currentLink, + className, + fetchSearchSuggestions, + onLinkChange, + onSettingsChange = { noop }, + currentSettings, + onKeyDown = noop, + onKeyPress = noop, +} ) { // State const [ inputValue, setInputValue ] = useState( '' ); const [ isEditingLink, setIsEditingLink ] = useState( true ); @@ -186,6 +195,8 @@ function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkCh renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } onReset={ resetInput } + onKeyDown={ onKeyDown } + onKeyPress={ onKeyPress } /> ) } diff --git a/packages/block-editor/src/components/link-control/input-search.js b/packages/block-editor/src/components/link-control/input-search.js index 90c741a209595c..4c679a9c175eb2 100644 --- a/packages/block-editor/src/components/link-control/input-search.js +++ b/packages/block-editor/src/components/link-control/input-search.js @@ -3,37 +3,24 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - IconButton, -} from '@wordpress/components'; -import { - LEFT, - RIGHT, - UP, - DOWN, - BACKSPACE, - ENTER, -} from '@wordpress/keycodes'; +import { IconButton } from '@wordpress/components'; +import { ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ -import { - URLInput, -} from '../'; - -const LinkControlInputSearch = ( { value, onChange, onSelect, renderSuggestions, fetchSuggestions, onReset } ) => { - const stopPropagation = ( event ) => { - event.stopPropagation(); - }; - - const stopPropagationRelevantKeys = ( event ) => { - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { - // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - stopPropagation( event ); - } - }; - +import { URLInput } from '../'; + +const LinkControlInputSearch = ( { + value, + onChange, + onSelect, + renderSuggestions, + fetchSuggestions, + onReset, + onKeyDown, + onKeyPress, +} ) => { return (
{ - stopPropagationRelevantKeys( event ); if ( event.keyCode === ENTER ) { onSelect( suggestion )( event ); } + onKeyDown( event, suggestion ); } } - onKeyPress={ stopPropagation } + onKeyPress={ onKeyPress } placeholder={ __( 'Search or type url' ) } renderSuggestions={ renderSuggestions } fetchLinkSuggestions={ fetchSuggestions } From e3042c8262356e111888795a0b7aa9c1c80e4e51 Mon Sep 17 00:00:00 2001 From: retrofox Date: Thu, 24 Oct 2019 11:13:46 -0300 Subject: [PATCH 082/113] link-control: add onKeyDown and onKeyPress handlers --- playground/src/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/playground/src/index.js b/playground/src/index.js index 87664538571408..3f469d479cc3c0 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -84,6 +84,19 @@ function App() { ] ); }; + const handleOnKeyDownEvent = ( { keyCode }, suggestion ) => { + console.log( `onKeyDown - Key code: ${ keyCode }` ); + if ( null !== suggestion ) { + console.log( `suggestion: ${ suggestion }` ); + } + event.stopPropagation(); + }; + + const handleOnKeyPressEvent = ( { keyCode } ) => { + console.log( `onKeyPress - Key code: ${ keyCode }` ); + event.stopPropagation(); + }; + return (
@@ -114,6 +127,8 @@ function App() { } ); } } fetchSearchSuggestions={ fetchFauxEntitySuggestions } + onKeyDown={ handleOnKeyDownEvent } + onKeyPress={ handleOnKeyPressEvent } /> From 0a9d558ed97b7f1292147aaa49e9046ef289b158 Mon Sep 17 00:00:00 2001 From: retrofox Date: Thu, 24 Oct 2019 11:24:17 -0300 Subject: [PATCH 083/113] link-control: playground -> close once onClose --- playground/src/index.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/playground/src/index.js b/playground/src/index.js index 3f469d479cc3c0..dbc2957aa03398 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -46,6 +46,8 @@ function App() { 'new-tab': false, } ); + const [ isVisible, setIsVisible ] = useState( true ); + /* eslint-disable @wordpress/react-no-unsafe-timeout */ const timeout = ( ms ) => { return new Promise( ( resolve ) => setTimeout( resolve, ms ) ); @@ -114,22 +116,25 @@ function App() { - { - setLink( theLink ); - } } - onSettingsChange={ ( setting, value ) => { - setLinkSettings( { - ...linkSettings, - [ setting ]: value, - } ); - } } - fetchSearchSuggestions={ fetchFauxEntitySuggestions } - onKeyDown={ handleOnKeyDownEvent } - onKeyPress={ handleOnKeyPressEvent } - /> + { isVisible && + { + setLink( theLink ); + } } + onSettingsChange={ ( setting, value ) => { + setLinkSettings( { + ...linkSettings, + [ setting ]: value, + } ); + } } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + onKeyDown={ handleOnKeyDownEvent } + onKeyPress={ handleOnKeyPressEvent } + onClose={ () => { setIsVisible( false ) } } + /> + } From 89e92fb2184e114b433f7dfe0e9ed639b3c10f8e Mon Sep 17 00:00:00 2001 From: retrofox Date: Thu, 24 Oct 2019 11:36:22 -0300 Subject: [PATCH 084/113] link-control: propagate onClose() event --- packages/block-editor/src/components/link-control/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index df4137fc7a9f21..9757f807bf11c4 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -43,6 +43,7 @@ function LinkControl( { currentSettings, onKeyDown = noop, onKeyPress = noop, + onClose = noop, } ) { // State const [ inputValue, setInputValue ] = useState( '' ); @@ -55,6 +56,7 @@ function LinkControl( { const closeLinkUI = () => { resetInput(); + onClose(); }; const resetInput = () => { From ff57160ad9f688776aa29aa0a068542e4ebc9f26 Mon Sep 17 00:00:00 2001 From: retrofox Date: Thu, 24 Oct 2019 11:40:42 -0300 Subject: [PATCH 085/113] link-control: playground -> hanldling close by ESCAPE key --- playground/src/index.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/playground/src/index.js b/playground/src/index.js index dbc2957aa03398..4e9b253b18b534 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -6,6 +6,7 @@ import { uniqueId, random } from 'lodash'; /** * WordPress dependencies */ +import { ESCAPE } from '@wordpress/keycodes'; import '@wordpress/editor'; // This shouldn't be necessary import { render, useState, Fragment } from '@wordpress/element'; @@ -86,16 +87,22 @@ function App() { ] ); }; - const handleOnKeyDownEvent = ( { keyCode }, suggestion ) => { - console.log( `onKeyDown - Key code: ${ keyCode }` ); + const handleOnKeyDownEvent = ( event, suggestion ) => { + console.log( `onKeyDown - Key code: ${ event.keyCode }` ); if ( null !== suggestion ) { console.log( `suggestion: ${ suggestion }` ); } + + // Do not stop propagation for ESCAPE key + if ( ESCAPE === event.keyCode ) { + return; + } + event.stopPropagation(); }; - const handleOnKeyPressEvent = ( { keyCode } ) => { - console.log( `onKeyPress - Key code: ${ keyCode }` ); + const handleOnKeyPressEvent = ( event) => { + console.log( `onKeyPress - Key code: ${ event.keyCode }` ); event.stopPropagation(); }; From 48e5f44cbe3381e2aa097c8f49717e8df9fe785a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 17:27:32 +0100 Subject: [PATCH 086/113] Fix to only render settings draw if settings are defined --- .../link-control/settings-drawer.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/settings-drawer.js b/packages/block-editor/src/components/link-control/settings-drawer.js index be6598484a3f6e..372426e4e821ff 100644 --- a/packages/block-editor/src/components/link-control/settings-drawer.js +++ b/packages/block-editor/src/components/link-control/settings-drawer.js @@ -11,13 +11,19 @@ import { ToggleControl, } from '@wordpress/components'; -const LinkControlSettingsDrawer = ( { settings, onSettingChange } ) => ( -
- -
-); +const LinkControlSettingsDrawer = ( { settings, onSettingChange } ) => { + if ( ! settings || settings.length ) { + return null; + } + + return ( +
+ +
+ ); +}; export default LinkControlSettingsDrawer; From 4e811db5bd11f52eab601d2029c0d83b0b7d23f2 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 17:27:55 +0100 Subject: [PATCH 087/113] Remove redundant commented out test --- .../src/components/link-control/test/index.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) 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 ea04cf8a9fab35..2f6684f113e77c 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -135,21 +135,6 @@ describe( 'Searching for a link', () => { expect( lastSearchResultItemHTML ).not.toEqual( expect.stringContaining( 'URL' ) ); } ); - // it('should not display any search result when the input is empty or has been cleared or reset', async () => { - // // Reset the search term - // act( () => { - // Simulate.change( searchInput, { target: { value: '' } } ); - // } ); - - // // fetchFauxEntitySuggestions resolves on next "tick" of event loop - // await eventLoopTick(); - - // // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - // searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - - // expect( searchResultElements ).toHaveLength( 0 ); - // }) - it.each( [ [ 'couldbeurlorentitysearchterm' ], [ 'ThisCouldAlsoBeAValidURL' ], @@ -188,7 +173,7 @@ describe( 'Searching for a link', () => { expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); } ); - it( 'should reset input and search results when search term is cleared or reset', async ( ) => { + it( 'should reset the input field and the search results when search term is cleared or reset', async ( ) => { const searchTerm = 'Hello world'; act( () => { From 43c30b26724e7ff7d6927bf894a9befb69fd7716 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 17:29:45 +0100 Subject: [PATCH 088/113] =?UTF-8?q?Update=20to=20render=20with=20a=20?= =?UTF-8?q?=E2=80=9Ccurrent=20link=E2=80=9D=20if=20one=20is=20provided.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously if you passed in a current link the component would still render with a search box as thought nothing was selected. Updates so that if `currentLink` is provided the UI reflects that by showing the “selected” item and no search input. --- .../src/components/link-control/index.js | 94 ++++++++++++------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 9757f807bf11c4..bc36e4da0bf5fd 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -17,6 +17,8 @@ import { __ } from '@wordpress/i18n'; import { useCallback, useState, + useEffect, + Fragment, } from '@wordpress/element'; import { @@ -27,6 +29,8 @@ import { getProtocol, } from '@wordpress/url'; +import { withInstanceId } from '@wordpress/compose'; + /** * Internal dependencies */ @@ -35,49 +39,60 @@ import LinkControlSearchItem from './search-item'; import LinkControlInputSearch from './input-search'; function LinkControl( { + instanceId, currentLink, className, fetchSearchSuggestions, onLinkChange, - onSettingsChange = { noop }, currentSettings, + onSettingsChange = { noop }, onKeyDown = noop, onKeyPress = noop, onClose = noop, } ) { // State const [ inputValue, setInputValue ] = useState( '' ); - const [ isEditingLink, setIsEditingLink ] = useState( true ); + const [ isEditingLink, setIsEditingLink ] = useState( false ); + + // Effects + useEffect( () => { + // If we have a link then stop editing mode + if ( currentLink ) { + setIsEditingLink( false ); + } else { + setIsEditingLink( true ); + } + }, [ currentLink ] ); // Handlers const onInputChange = ( value = '' ) => { setInputValue( value ); }; - const closeLinkUI = () => { - resetInput(); - onClose(); - }; - - const resetInput = () => { - setInputValue( '' ); - }; - const onLinkSelect = ( suggestion ) => ( event ) => { event.preventDefault(); event.stopPropagation(); - setIsEditingLink( false ); - if ( isFunction( onLinkChange ) ) { onLinkChange( suggestion ); } }; - const onStartEditing = () => { + // Utils + + const startEditMode = () => { setIsEditingLink( true ); }; + const closeLinkUI = () => { + resetInput(); + onClose(); + }; + + const resetInput = () => { + setInputValue( '' ); + }; + const handleDirectEntry = async ( value ) => { let type = 'URL'; @@ -166,27 +181,34 @@ function LinkControl( {
- { ! isEditingLink && ( -
- - - - { currentLink.title } - - { filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' } - - - -
+ { ( ! isEditingLink && currentLink ) && ( + +

+ { __( 'Currently selected' ) }: +

+
+ + + + { currentLink.title } + + { filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' } + + + +
+
) } { isEditingLink && ( @@ -211,4 +233,4 @@ function LinkControl( { ); } -export default LinkControl; +export default withInstanceId( LinkControl ); From 158ea3e3dab67b4c38349664222b7a425eea3c66 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 24 Oct 2019 17:30:07 +0100 Subject: [PATCH 089/113] Render playground with currentLink active --- playground/src/index.js | 56 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/playground/src/index.js b/playground/src/index.js index 4e9b253b18b534..fa1b8457610b77 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -40,9 +40,36 @@ import '@wordpress/block-library/build-style/theme.css'; import '@wordpress/format-library/build-style/style.css'; /* eslint-enable no-restricted-syntax */ +const fauxEntitySuggestions = [ + { + id: uniqueId(), + title: 'Hello Page', + type: 'Page', + url: '/hello-page/', + }, + { + id: uniqueId(), + title: 'Hello Post', + type: 'Post', + url: '/hello-post/', + }, + { + id: uniqueId(), + title: 'Hello Another One', + type: 'Page', + url: '/hello-another-one/', + }, + { + id: uniqueId(), + title: 'This is another Post with a much longer title just to be really annoying and to try and break the UI', + type: 'Post', + url: '/this-is-another-post-with-a-much-longer-title-just-to-be-really-annoying-and-to-try-and-break-the-ui/', + }, +]; + function App() { const [ blocks, updateBlocks ] = useState( [] ); - const [ link, setLink ] = useState( null ); + const [ link, setLink ] = useState( fauxEntitySuggestions[ 0 ] ); const [ linkSettings, setLinkSettings ] = useState( { 'new-tab': false, } ); @@ -59,32 +86,7 @@ function App() { // Simulate network await timeout( random( 200, 1000 ) ); - return ( [ - { - id: uniqueId(), - title: 'Hello Page', - type: 'Page', - url: '/hello-page/', - }, - { - id: uniqueId(), - title: 'Hello Post', - type: 'Post', - url: '/hello-post/', - }, - { - id: uniqueId(), - title: 'Hello Another One', - type: 'Page', - url: '/hello-another-one/', - }, - { - id: uniqueId(), - title: 'This is another Post with a much longer title just to be really annoying and to try and break the UI', - type: 'Post', - url: '/this-is-another-post-with-a-much-longer-title-just-to-be-really-annoying-and-to-try-and-break-the-ui/', - }, - ] ); + return fauxEntitySuggestions; }; const handleOnKeyDownEvent = ( event, suggestion ) => { From 4981a7123a2642cbe2c23ea9e5af8dbc9b5cce6b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 10:03:34 +0100 Subject: [PATCH 090/113] Adds test to cover currentLink prop --- .../src/components/link-control/test/index.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 2f6684f113e77c..07094514f2baa7 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -295,3 +295,28 @@ describe( 'Manual link entry', () => { } ); } ); } ); + +describe( 'Selecting links', () => { + it( 'should display a selected link corresponding to the "currentLink" prop when provided', async () => { + const selectedLink = first( fauxEntitySuggestions ); + + act( () => { + render( + , container + ); + } ); + + // TODO: select by aria role or visible text + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.type ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); + } ); +} ); From f1c54a645ebed7f5c69cdecad552bb555847ccf5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 12:05:41 +0100 Subject: [PATCH 091/113] Remove selected state from Playground --- playground/src/index.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/playground/src/index.js b/playground/src/index.js index fa1b8457610b77..32b7dc4c55448a 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -69,7 +69,7 @@ const fauxEntitySuggestions = [ function App() { const [ blocks, updateBlocks ] = useState( [] ); - const [ link, setLink ] = useState( fauxEntitySuggestions[ 0 ] ); + const [ link, setLink ] = useState(); const [ linkSettings, setLinkSettings ] = useState( { 'new-tab': false, } ); @@ -90,9 +90,7 @@ function App() { }; const handleOnKeyDownEvent = ( event, suggestion ) => { - console.log( `onKeyDown - Key code: ${ event.keyCode }` ); if ( null !== suggestion ) { - console.log( `suggestion: ${ suggestion }` ); } // Do not stop propagation for ESCAPE key @@ -103,8 +101,7 @@ function App() { event.stopPropagation(); }; - const handleOnKeyPressEvent = ( event) => { - console.log( `onKeyPress - Key code: ${ event.keyCode }` ); + const handleOnKeyPressEvent = ( event ) => { event.stopPropagation(); }; @@ -141,7 +138,9 @@ function App() { fetchSearchSuggestions={ fetchFauxEntitySuggestions } onKeyDown={ handleOnKeyDownEvent } onKeyPress={ handleOnKeyPressEvent } - onClose={ () => { setIsVisible( false ) } } + onClose={ () => { + setIsVisible( false ); + } } /> } From 797fd6c921bafb770fe8bc3211babefb0993ae50 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 12:06:01 +0100 Subject: [PATCH 092/113] Adds tests to cover selecting and changing links --- .../src/components/link-control/index.js | 7 +- .../src/components/link-control/test/index.js | 113 +++++++++++++++++- 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index bc36e4da0bf5fd..4fa4c603881d76 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -62,7 +62,7 @@ function LinkControl( { } else { setIsEditingLink( true ); } - }, [ currentLink ] ); + }, [ currentLink, setIsEditingLink ] ); // Handlers const onInputChange = ( value = '' ) => { @@ -79,9 +79,10 @@ function LinkControl( { }; // Utils - const startEditMode = () => { - setIsEditingLink( true ); + if ( isFunction( onLinkChange ) ) { + onLinkChange(); + } }; const closeLinkUI = () => { 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 07094514f2baa7..0ff1234622061d 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -4,7 +4,10 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { act, Simulate } from 'react-dom/test-utils'; import { first, last } from 'lodash'; - +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; /** * Internal dependencies */ @@ -16,6 +19,7 @@ function eventLoopTick() { } let container = null; + beforeEach( () => { // setup a DOM element as a render target container = document.createElement( 'div' ); @@ -297,23 +301,124 @@ describe( 'Manual link entry', () => { } ); describe( 'Selecting links', () => { - it( 'should display a selected link corresponding to the "currentLink" prop when provided', async () => { + it( 'should display a selected link corresponding to the provided "currentLink" prop', () => { const selectedLink = first( fauxEntitySuggestions ); + const LinkControlConsumer = () => { + const [ link ] = useState( selectedLink ); + + return ( + + ); + }; + act( () => { render( + , container + ); + } ); + + // TODO: select by aria role or visible text + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.type ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); + } ); + + it( 'should remove currently selected link and (re)display search UI when "Change" button is clicked', () => { + const selectedLink = first( fauxEntitySuggestions ); + + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( selectedLink ); + + return ( setLink( suggestion ) } fetchSearchSuggestions={ fetchFauxEntitySuggestions } - />, container + /> + ); + }; + + act( () => { + render( + , container ); } ); // TODO: select by aria role or visible text + let currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + + const currentLinkBtn = currentLink.querySelector( 'button' ); + + // Simulate searching for a term + act( () => { + Simulate.click( currentLinkBtn ); + } ); + + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + + // We should be back to showing the search input + expect( searchInput ).not.toBeNull(); + expect( currentLink ).toBeNull(); + } ); + + it( 'should display the current link UI when a search suggestion is clicked', async ( ) => { + const searchTerm = 'Hello world'; + const selectedLink = first( fauxEntitySuggestions ); + + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + /> + ); + }; + + 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: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + const firstSearchSuggestion = first( searchResultElements ); + + // Simulate selecting the first of the search suggestions + act( () => { + Simulate.click( firstSearchSuggestion ); + } ); + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); const currentLinkHTML = currentLink.innerHTML; const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + // Check that this suggestion is now shown as selected expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.type ) ); expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); From e5e44e6769305668dfeb0b67268bd05005637a76 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 12:19:45 +0100 Subject: [PATCH 093/113] Remove async function in place of direct Promise usage and add test coverage --- .../src/components/link-control/index.js | 16 +++++++++------- .../src/components/link-control/test/index.js | 14 +++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4fa4c603881d76..354b5bc1133dba 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -94,7 +94,7 @@ function LinkControl( { setInputValue( '' ); }; - const handleDirectEntry = async ( value ) => { + const handleDirectEntry = ( value ) => { let type = 'URL'; const protocol = getProtocol( value ) || ''; @@ -111,12 +111,14 @@ function LinkControl( { type = 'internal'; } - return [ { - id: '1', - title: value, - url: type === 'URL' ? prependHTTP( value ) : value, - type, - } ]; + return Promise.resolve( + [ { + id: '1', + title: value, + url: type === 'URL' ? prependHTTP( value ) : value, + type, + } ] + ); }; const handleEntitySearch = async ( value ) => { 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 0ff1234622061d..ee8851042e8e57 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -371,10 +371,15 @@ describe( 'Selecting links', () => { expect( currentLink ).toBeNull(); } ); - it( 'should display the current link UI when a search suggestion is clicked', async ( ) => { - const searchTerm = 'Hello world'; - const selectedLink = first( fauxEntitySuggestions ); - + it.each( [ + [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search + [ 'url', 'https://www.wordpress.org', { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + } ], // url + ] )( 'should display a current selected link UI when a %s search suggestion for input "%s" is clicked', async ( type, searchTerm, selectedLink ) => { const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); @@ -420,7 +425,6 @@ describe( 'Selecting links', () => { // Check that this suggestion is now shown as selected expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.type ) ); expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); expect( currentLinkAnchor ).not.toBeNull(); } ); From 5ba4b65f2190d47eddbe10f9378714301a4dbca9 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 14:38:47 +0100 Subject: [PATCH 094/113] Add test to cover keyboard handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: this uncovered a bug whereby keyboard handling of “selecting” the link you want to use is broken. This needs to be fixed. --- .../src/components/link-control/test/index.js | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) 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 ee8851042e8e57..c63865f1d39240 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -3,11 +3,12 @@ */ import { render, unmountComponentAtNode } from 'react-dom'; import { act, Simulate } from 'react-dom/test-utils'; -import { first, last } from 'lodash'; +import { first, last, nth } from 'lodash'; /** * WordPress dependencies */ import { useState } from '@wordpress/element'; +import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ @@ -428,4 +429,87 @@ describe( 'Selecting links', () => { expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); expect( currentLinkAnchor ).not.toBeNull(); } ); + + it( 'should display a current selected link UI when a search suggestion is selected using the keyboard', async ( ) => { + const searchTerm = 'Hello world'; + // const selectedLink = first( fauxEntitySuggestions ); + + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + /> + ); + }; + + 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: searchTerm } } ); + } ); + + //fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // Step down into the search results, highlighting the first result item + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchSuggestion = first( searchResultElements ); + const secondSearchSuggestion = nth( searchResultElements, 1 ); + + let selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + + // Check we can go down again using the down arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); + + selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( secondSearchSuggestion ); + + // Check we can go back up via up arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: UP } ); + } ); + + selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + + // We should be back to highlighting the first search result again + expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + + // Commit the selected item as the current link + act( () => { + Simulate.keyDown( searchInput, { keyCode: ENTER } ); + } ); + + // // Check that the suggestion selected via is now shown as selected + // const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + // const currentLinkHTML = currentLink.innerHTML; + // const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + + // expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + // expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + // expect( currentLinkAnchor ).not.toBeNull(); + } ); } ); From d5abad24bd45dffa046e4d8644c9e66274d2e3f5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 15:25:45 +0100 Subject: [PATCH 095/113] Remove unecessary dep from effect --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 354b5bc1133dba..4fecca396a0341 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -62,7 +62,7 @@ function LinkControl( { } else { setIsEditingLink( true ); } - }, [ currentLink, setIsEditingLink ] ); + }, [ currentLink ] ); // Handlers const onInputChange = ( value = '' ) => { From 5c1ec2238f33254307061e44c814993555253106 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 15:34:57 +0100 Subject: [PATCH 096/113] Fix URLInput to pass the actual suggestion object not the index If the full object is not provided then consuming components have no way of accessing the details of the selected suggestion thereby rendering it useless. --- packages/block-editor/src/components/url-input/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 25b785d04080a3..8bec30764769b1 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -140,7 +140,7 @@ class URLInput extends Component { // Trigger `onKeyDown` event, passing the selected suggestion besides the event class. if ( this.props.onKeyDown ) { - this.props.onKeyDown( event, selectedSuggestion ); + this.props.onKeyDown( event, suggestions[ selectedSuggestion ] ); } // If the suggestions are not shown or loading, we shouldn't handle the arrow keys From 4d5b4550015a4a97fd1a4bdd66c7b970c5177f58 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 15:36:06 +0100 Subject: [PATCH 097/113] Fix keyboard handling so hitting `ENTER` will select an item as the current link Builds on previous commit. --- .../src/components/link-control/input-search.js | 14 +++++++------- .../src/components/link-control/test/index.js | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/link-control/input-search.js b/packages/block-editor/src/components/link-control/input-search.js index 4c679a9c175eb2..d2210c581f157e 100644 --- a/packages/block-editor/src/components/link-control/input-search.js +++ b/packages/block-editor/src/components/link-control/input-search.js @@ -13,13 +13,13 @@ import { URLInput } from '../'; const LinkControlInputSearch = ( { value, - onChange, - onSelect, - renderSuggestions, - fetchSuggestions, - onReset, - onKeyDown, - onKeyPress, + onChange, + onSelect, + renderSuggestions, + fetchSuggestions, + onReset, + onKeyDown, + onKeyPress, } ) => { return ( 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 c63865f1d39240..03c6f9c4709bc5 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -432,7 +432,7 @@ describe( 'Selecting links', () => { it( 'should display a current selected link UI when a search suggestion is selected using the keyboard', async ( ) => { const searchTerm = 'Hello world'; - // const selectedLink = first( fauxEntitySuggestions ); + const selectedLink = first( fauxEntitySuggestions ); const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); @@ -503,13 +503,13 @@ describe( 'Selecting links', () => { Simulate.keyDown( searchInput, { keyCode: ENTER } ); } ); - // // Check that the suggestion selected via is now shown as selected - // const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); - // const currentLinkHTML = currentLink.innerHTML; - // const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + // Check that the suggestion selected via is now shown as selected + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); - // expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - // expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); - // expect( currentLinkAnchor ).not.toBeNull(); + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); } ); } ); From b8a01bd6bf3ace4b9b01163b0b68539dd669a22a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 15:42:25 +0100 Subject: [PATCH 098/113] Updates keyboard interaction test to include URL entry --- .../src/components/link-control/test/index.js | 232 +++++++++--------- 1 file changed, 122 insertions(+), 110 deletions(-) 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 03c6f9c4709bc5..b2d73a8b37690e 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -372,144 +372,156 @@ describe( 'Selecting links', () => { expect( currentLink ).toBeNull(); } ); - it.each( [ - [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search - [ 'url', 'https://www.wordpress.org', { - id: '1', - title: 'https://www.wordpress.org', - url: 'https://www.wordpress.org', - type: 'URL', - } ], // url - ] )( 'should display a current selected link UI when a %s search suggestion for input "%s" is clicked', async ( type, searchTerm, selectedLink ) => { - const LinkControlConsumer = () => { - const [ link, setLink ] = useState( null ); - - return ( - setLink( suggestion ) } - fetchSearchSuggestions={ fetchFauxEntitySuggestions } - /> - ); - }; + describe( 'Selection using mouse click', () => { + it.each( [ + [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search + [ 'url', 'https://www.wordpress.org', { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + } ], // url + ] )( 'should display a current selected link UI when a %s search suggestion for input "%s" is clicked', async ( type, searchTerm, selectedLink ) => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + /> + ); + }; - act( () => { - render( - , container - ); - } ); + act( () => { + render( + , container + ); + } ); - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); - // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchSuggestion = first( searchResultElements ); + const firstSearchSuggestion = first( searchResultElements ); - // Simulate selecting the first of the search suggestions - act( () => { - Simulate.click( firstSearchSuggestion ); - } ); + // Simulate selecting the first of the search suggestions + act( () => { + Simulate.click( firstSearchSuggestion ); + } ); - const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); - const currentLinkHTML = currentLink.innerHTML; - const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); - // Check that this suggestion is now shown as selected - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); - expect( currentLinkAnchor ).not.toBeNull(); + // Check that this suggestion is now shown as selected + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); + } ); } ); - it( 'should display a current selected link UI when a search suggestion is selected using the keyboard', async ( ) => { - const searchTerm = 'Hello world'; - const selectedLink = first( fauxEntitySuggestions ); + describe( 'Selection using keyboard', () => { + it.each( [ + [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search + [ 'url', 'https://www.wordpress.org', { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + } ], // url + ] )( 'should display a current selected link UI when an %s search suggestion for %s is selected using the keyboard', async ( type, searchTerm, selectedLink ) => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + /> + ); + }; - const LinkControlConsumer = () => { - const [ link, setLink ] = useState( null ); + act( () => { + render( + , container + ); + } ); - return ( - setLink( suggestion ) } - fetchSearchSuggestions={ fetchFauxEntitySuggestions } - /> - ); - }; + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - act( () => { - render( - , container - ); - } ); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + //fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); + // Step down into the search results, highlighting the first result item + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); - //fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchSuggestion = first( searchResultElements ); + const secondSearchSuggestion = nth( searchResultElements, 1 ); - // Step down into the search results, highlighting the first result item - act( () => { - Simulate.keyDown( searchInput, { keyCode: DOWN } ); - } ); + let selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchSuggestion = first( searchResultElements ); - const secondSearchSuggestion = nth( searchResultElements, 1 ); + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); - let selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + // Only entity searches contain more than 1 suggestion + if ( type === 'entity' ) { + // Check we can go down again using the down arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); - // We should have highlighted the first item using the keyboard - expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); - // Check we can go down again using the down arrow - act( () => { - Simulate.keyDown( searchInput, { keyCode: DOWN } ); - } ); + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( secondSearchSuggestion ); - selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + // Check we can go back up via up arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: UP } ); + } ); - // We should have highlighted the first item using the keyboard - expect( selectedSearchResultElement ).toEqual( secondSearchSuggestion ); + selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); - // Check we can go back up via up arrow - act( () => { - Simulate.keyDown( searchInput, { keyCode: UP } ); - } ); + // We should be back to highlighting the first search result again + expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + } - selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + // Commit the selected item as the current link + act( () => { + Simulate.keyDown( searchInput, { keyCode: ENTER } ); + } ); - // We should be back to highlighting the first search result again - expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + // Check that the suggestion selected via is now shown as selected + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); - // Commit the selected item as the current link - act( () => { - Simulate.keyDown( searchInput, { keyCode: ENTER } ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); } ); - - // Check that the suggestion selected via is now shown as selected - const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); - const currentLinkHTML = currentLink.innerHTML; - const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); - - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); - expect( currentLinkAnchor ).not.toBeNull(); } ); } ); From 7df9aa558da236188178bdd16097a0405f192e62 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 15:46:40 +0100 Subject: [PATCH 099/113] Minor: reword test description --- .../block-editor/src/components/link-control/test/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b2d73a8b37690e..d6dc506c7687b8 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -381,7 +381,7 @@ describe( 'Selecting links', () => { url: 'https://www.wordpress.org', type: 'URL', } ], // url - ] )( 'should display a current selected link UI when a %s search suggestion for input "%s" is clicked', async ( type, searchTerm, selectedLink ) => { + ] )( 'should display a current selected link UI when a %s suggestion for the search "%s" is clicked', async ( type, searchTerm, selectedLink ) => { const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); @@ -441,7 +441,7 @@ describe( 'Selecting links', () => { url: 'https://www.wordpress.org', type: 'URL', } ], // url - ] )( 'should display a current selected link UI when an %s search suggestion for %s is selected using the keyboard', async ( type, searchTerm, selectedLink ) => { + ] )( 'should display a current selected link UI when an %s suggestion for the search "%s" is selected using the keyboard', async ( type, searchTerm, selectedLink ) => { const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); From b6edb7323233cd171517eb623ddaf6f57755e7ca Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Oct 2019 15:51:38 +0100 Subject: [PATCH 100/113] Fix missing key prop regression Previously `buildSuggestionItemProps` was including a key. However the implementation of `LinkControl` changed so that this was not required. However we forgot to reinstate on `URLInput`. This update ensures a key prop is set on the default output. Note that disabling of the autofocus linting was already in place: https://github.com/WordPress/gutenberg/blob/04e142e9cbd06a45c4ea297ec573d389955c13be/packages/block-editor/src/components/url-input/index.js#L239 Addresses https://github.com/WordPress/gutenberg/pull/17846#discussion_r337841961 --- packages/block-editor/src/components/url-input/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 8bec30764769b1..ea228147d5b796 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -342,6 +342,7 @@ class URLInput extends Component { { suggestions.map( ( suggestion, index ) => (
From 7d35791e08a49aae0435d03a274b07d50f0c28b0 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 29 Oct 2019 15:50:25 -0700 Subject: [PATCH 111/113] Rename InputSearch to SearchInput Props @talldan I really hope those changes I had to make in `search-input.js` don't break anything. --- .../src/components/link-control/index.js | 6 ++-- .../{input-search.js => search-input.js} | 28 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) rename packages/block-editor/src/components/link-control/{input-search.js => search-input.js} (71%) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 0e5f43aa3ef0d9..76736303f2aec7 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -37,7 +37,7 @@ import { withSelect } from '@wordpress/data'; */ import LinkControlSettingsDrawer from './settings-drawer'; import LinkControlSearchItem from './search-item'; -import LinkControlInputSearch from './input-search'; +import LinkControlSearchInput from './search-input'; function LinkControl( { className, @@ -68,7 +68,7 @@ function LinkControl( { // Handlers /** - * onChange LinkControlInputSearch event handler + * onChange LinkControlSearchInput event handler * * @param {string} value Current value returned by the search. */ @@ -213,7 +213,7 @@ function LinkControl( { ) } { isEditingLink && ( - { - const selectItemHandler = ( value, suggestion ) => { - onChange( value ); + const selectItemHandler = ( selection, suggestion ) => { + onChange( selection ); if ( suggestion ) { onSelect( suggestion ); } }; - const stopFormEventsPropagation = event => { + const stopFormEventsPropagation = ( event ) => { event.preventDefault(); event.stopPropagation(); }; return ( - { - if ( event.keyCode === ENTER ) { - return; - } - onKeyDown( event ); - } } - onKeyPress={ onKeyPress } - > + { + if ( event.keyCode === ENTER ) { + return; + } + onKeyDown( event ); + } } + onKeyPress={ onKeyPress } placeholder={ __( 'Search or type url' ) } __experimentalRenderSuggestions={ renderSuggestions } __experimentalFetchLinkSuggestions={ fetchSuggestions } @@ -68,4 +66,4 @@ const LinkControlInputSearch = ( { ); }; -export default LinkControlInputSearch; +export default LinkControlSearchInput; From e7585b8ccd62b56a0738d6a219a2ad690b57886b Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Wed, 30 Oct 2019 11:13:48 +0800 Subject: [PATCH 112/113] Remove disabling of jsx-key lint rule --- packages/block-editor/src/components/url-input/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 29693e729ea666..60e6b0c19c5432 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -290,7 +290,7 @@ class URLInput extends Component { }; }; - /* eslint-disable jsx-a11y/no-autofocus, react/jsx-key */ + /* eslint-disable jsx-a11y/no-autofocus */ return (
); - /* eslint-enable jsx-a11y/no-autofocus, react/jsx-key */ + /* eslint-enable jsx-a11y/no-autofocus */ } } From 8450bb12c1af82ab9df07b9e2731cb6caf1e70b2 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Wed, 30 Oct 2019 11:23:38 +0800 Subject: [PATCH 113/113] Change fake id value to something that will not clash with post ids --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 76736303f2aec7..afe72df4f2e93d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -111,7 +111,7 @@ function LinkControl( { return Promise.resolve( [ { - id: '1', + id: '-1', title: value, url: type === 'URL' ? prependHTTP( value ) : value, type,