From ed1061adf46a73c6196a58a39aae434adca1cfff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 22 Jul 2020 16:15:13 +0200 Subject: [PATCH 1/5] Squash Only use the experimental toolbar link editing on the navigation screen Re-add missing block-toolbar-link-control Prioritize inputProps over toolbarItemProps Pass ref directly to InputControl instead of going through LinkControlSearchInput Show spinner when creating new page Create snackbar notice when error occured in useCreatePage Revert LinkControlSearchInput change Restore is-vertically-retracted to search-input.js Restore renderControl to searchInput include rel in link Decide suggestions popover visibility based on the settings dropdown state Improve keyboard navigation Formatted Restore changes from master Make the "create new page" button work Rename computeNiceURL to computeDisplayURL Move and to the same toolbar group Fix overriding url in updateCurrentLink Save link block attributes on each change, not just once Done is clicked Remove setCurrentLink from the context Hide the suggestions dropdown on blur Hide suggestions on escape --- packages/block-editor/README.md | 4 + .../compute-display-url.js | 23 +++ .../block-toolbar-link-control/context.js | 7 + .../block-toolbar-link-control/index.js | 91 +++++++++ .../link-input-toolbar-item.js | 177 ++++++++++++++++++ .../settings-toolbar-item.js | 91 +++++++++ .../block-toolbar-link-control/style.scss | 56 ++++++ packages/block-editor/src/components/index.js | 1 + .../components/link-control/search-input.js | 28 ++- .../components/link-control/search-results.js | 8 +- .../src/components/link-control/style.scss | 4 +- .../src/components/url-input/index.js | 6 +- packages/block-editor/src/style.scss | 1 + .../block-library/src/navigation-link/edit.js | 22 ++- .../components/src/dropdown-menu/index.js | 2 + packages/edit-navigation/src/index.js | 1 + 16 files changed, 511 insertions(+), 11 deletions(-) create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/context.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/index.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/style.scss diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 3bb72598aabf4..699812332029a 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -210,6 +210,10 @@ _Returns_ Undocumented declaration. +# **BlockToolbarLinkControl** + +Undocumented declaration. + # **BlockVerticalAlignmentToolbar** _Related_ diff --git a/packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js b/packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js new file mode 100644 index 0000000000000..5dfb684bbc946 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js @@ -0,0 +1,23 @@ +export default function computeDisplayUrl( url ) { + if ( ! url ) { + return ''; + } + + let urlData; + try { + urlData = new URL( url ); + } catch ( e ) { + return url; + } + let displayUrl = ''; + + const siteHost = document.location.host; + if ( urlData.host && urlData.host !== siteHost ) { + displayUrl += urlData.host; + } + displayUrl += urlData.pathname; + if ( urlData.search ) { + displayUrl += urlData.search; + } + return displayUrl; +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/context.js b/packages/block-editor/src/components/block-toolbar-link-control/context.js new file mode 100644 index 0000000000000..d7091d02d4a5f --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/context.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +const ToolbarLinkControlContext = createContext( {} ); +export default ToolbarLinkControlContext; diff --git a/packages/block-editor/src/components/block-toolbar-link-control/index.js b/packages/block-editor/src/components/block-toolbar-link-control/index.js new file mode 100644 index 0000000000000..eb8c7da1522a6 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/index.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useCallback, useMemo, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BlockControls from '../block-controls'; +import SettingsToolbarItem from './settings-toolbar-item'; +import LinkInputToolbarItem from './link-input-toolbar-item'; +import computeDisplayUrl from './compute-display-url'; +import ToolbarLinkControlContext from './context'; + +export default function ToolbarLinkControl( { + initialLink, + createSuggestion, + close, + onChange, +} ) { + const [ currentLink, setCurrentLink ] = useState( { + ...initialLink, + url: computeDisplayUrl( initialLink.url ), + } ); + + const [ preferredDropdown, setPreferredDropdown ] = useState( + 'suggestions' + ); + + const updateCurrentLink = useCallback( + ( data ) => { + const newLink = { + ...currentLink, + ...data, + }; + if ( currentLink.id && ! data.id ) { + delete newLink.id; + } + if ( 'url' in data ) { + newLink.url = computeDisplayUrl( data.url ); + } else { + newLink.url = currentLink.url; + } + if ( data.title && ! currentLink.label ) { + newLink.label = data.title; + } + setCurrentLink( newLink ); + onChange( newLink ); + }, + [ currentLink ] + ); + + const contextValue = useMemo( + () => ( { + createSuggestion, + currentLink, + updateCurrentLink, + preferredDropdown, + setPreferredDropdown, + } ), + [ + createSuggestion, + currentLink, + updateCurrentLink, + preferredDropdown, + setPreferredDropdown, + ] + ); + + return ( + + + + + + + + + Done + + + + + ); +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js b/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js new file mode 100644 index 0000000000000..0477af026a28d --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js @@ -0,0 +1,177 @@ +/** + * External dependencies + */ +import { unstable_CompositeItemWidget as ToolbarWidget } from 'reakit/Composite'; + +/** + * WordPress dependencies + */ +import { + __experimentalToolbarItem as ToolbarItem, + Popover, + __experimentalToolbarContext as ToolbarContext, + __experimentalInputControl as InputControl, + Icon, + Spinner, +} from '@wordpress/components'; +import { link as linkIcon } from '@wordpress/icons'; +import { useContext, useEffect, useState, useRef } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import LinkControlSearchResults from '../link-control/search-results'; +import LinkControlSearchInput from '../link-control/search-input'; +import useCreatePage from '../link-control/use-create-page'; +import ToolbarLinkControlContext from './context'; + +export default function LinkInputToolbarItem() { + const toolbar = useContext( ToolbarContext ); + + const [ editUrl, setEditUrl ] = useState( '' ); + return ( + + { ( htmlProps ) => ( +
+ +
+ ) } +
+ ); +} + +const ToolbarLinkEditorControl = function ( props ) { + const { + createSuggestion, + currentLink, + updateCurrentLink, + preferredDropdown, + setPreferredDropdown, + } = useContext( ToolbarLinkControlContext ); + + const { createPage, isCreatingPage, errorMessage } = useCreatePage( + createSuggestion + ); + + const { createErrorNotice } = useDispatch( 'core/notices' ); + useEffect( () => { + if ( errorMessage ) { + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } + }, [ errorMessage ] ); + + const searchInputRef = useRef(); + + return ( + { + setPreferredDropdown( 'suggestions' ); + updateCurrentLink( { url } ); + } } + onSelect={ ( link ) => { + updateCurrentLink( link ); + } } + showInitialSuggestions={ false } + allowDirectEntry + showSuggestions={ preferredDropdown === 'suggestions' } + withCreateSuggestion + renderControl={ ( + controlProps, + inputProps, + { isLoading, suggestionsVisible } + ) => { + return ( + { + inputProps.onKeyDown( event ); + // LinkControlSearchInput renders a form which is normally submitted with an Enter key. + // In this context however, Reakit calls .preventDefault() on the enter keydown event + // so we need to select focused suggestion manually. + if ( event.key === 'Enter' ) { + // The flow of this keyDown event is quite complex, some of the consumers here are URLInput, InputControl, + // LinkControlSearchInput, ToolbarLinkEditorControl, Reakit, some of them talk to each other and keep their + // own local state. + // + // setTimeout seems to be the easiest way to achieve the intended outcome of selecting the suggestion selected + // at the time of pressing the enter key. It is quite unintuitive so let's explore some cleaner solutions in the + // longer run. + setTimeout( () => { + if ( searchInputRef?.current ) { + searchInputRef.current.selectFocusedSuggestion(); + } + } ); + } + + // When escape is pressed, either: + // * Hide suggestions if they're visible + // * Stop editing if suggestions are not visible + if ( + event.key === 'Escape' && + suggestionsVisible + ) { + setPreferredDropdown( null ); + } else { + props.onKeyDown( event ); + } + } } + onChange={ ( value, { event } ) => { + inputProps.onChange( event ); + props.onChange( event ); + } } + onFocus={ ( event ) => { + inputProps.onFocus( event ); + props.onFocus( event ); + } } + onBlur={ ( event ) => { + setPreferredDropdown( null ); + if ( 'onBlur' in inputProps ) { + inputProps.onBlur( event ); + } + if ( 'onBlur' in props ) { + props.onBlur( event ); + } + } } + prefix={ +
+ +
+ } + suffix={ +
+ { ( isCreatingPage || isLoading ) && ( + + ) } +
+ } + /> + ); + } } + /> + ); +}; + +const renderSuggestions = ( suggestionsProps ) => ( + + + +); diff --git a/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js b/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js new file mode 100644 index 0000000000000..04f6445386f7e --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { + DropdownMenu, + MenuGroup, + MenuItem, + __experimentalToolbarItem as ToolbarItem, +} from '@wordpress/components'; +import { + chevronDown as arrowDownIcon, + check as checkIcon, +} from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToolbarLinkControlContext from './context'; + +export default function SettingsToolbarItem() { + const { currentLink, updateCurrentLink, setPreferredDropdown } = useContext( + ToolbarLinkControlContext + ); + const { opensInNewTab, rel } = currentLink; + return ( + + { ( toolbarItemProps ) => ( + { + if ( isOpen ) { + setPreferredDropdown( 'settings' ); + } + } } + toggleProps={ { + ...toolbarItemProps, + name: 'link-options', + title: __( 'Link options' ), + } } + > + { ( { onClose } ) => ( + <> + + { + updateCurrentLink( { + opensInNewTab: ! opensInNewTab, + } ); + } } + > + { __( 'Open in new tab' ) } + + { + updateCurrentLink( { + rel: + currentLink.rel === 'nofollow' + ? '' + : 'nofollow', + } ); + } } + > + { __( 'Add nofollow attribute' ) } + + + + { + updateCurrentLink( { + url: '', + } ); + onClose(); + } } + > + { __( 'Remove link' ) } + + + + ) } + + ) } + + ); +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/style.scss b/packages/block-editor/src/components/block-toolbar-link-control/style.scss new file mode 100644 index 0000000000000..53ebf2e4c3b1e --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/style.scss @@ -0,0 +1,56 @@ + +// Specificity hack +.block-editor-block-toolbar.block-editor-block-toolbar { + + [data-experimental-toolbar-item]:focus .components-input-control__container { + //box-shadow: inset 0 0 0 2px var(--wp-admin-theme-color); + outline: var(--wp-admin-theme-color) auto 2px; + } + + .toolbar-link-control__input-group { + flex-grow: 1; + margin: 0; + height: 45px; + min-height: 0; + } + + .toolbar-link-control__input-wrapper { + display: flex; + + .components-base-control { + display: flex; + height: 100%; + } + + .components-base-control__field { + display: flex; + margin: 0; + } + + .components-input-control__input { + min-width: 280px; + padding-left: 0; + } + } + + .toolbar-link-control__input-control { + height: 45px; + border: 1px solid #ccc; + flex-grow: 1; + align-self: center; + font-size: 13px; + } + + .toolbar-link-control__affix-wrapper { + width: $grid-unit-50; + text-align: center; + + .components-spinner { + margin: 0; + } + } +} + +.block-editor-link-control__search-results-wrapper.is-toolbar-results { + margin-top: 0; +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 06b76dc2f95f5..dc727ffd49f46 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -23,6 +23,7 @@ export { default as __experimentalBlockNavigationEditor } from './block-navigati export { default as __experimentalBlockNavigationTree } from './block-navigation/tree'; export { default as __experimentalBlockVariationPicker } from './block-variation-picker'; export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar'; +export { default as BlockToolbarLinkControl } from './block-toolbar-link-control'; export { default as ButtonBlockerAppender } from './button-block-appender'; export { default as ColorPalette } from './color-palette'; export { default as ColorPaletteControl } from './color-palette/control'; diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index c8ae882ae1aba..354b1e07c0b14 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -7,7 +7,12 @@ import { noop, omit } from 'lodash'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; -import { forwardRef, useState } from '@wordpress/element'; +import { + forwardRef, + useRef, + useState, + useImperativeHandle, +} from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -33,8 +38,12 @@ const LinkControlSearchInput = forwardRef( onSelect = noop, showSuggestions = true, renderSuggestions = ( props ) => ( - + ), + renderControl = null, fetchSuggestions = null, allowDirectEntry = true, showInitialSuggestions = false, @@ -117,6 +126,13 @@ const LinkControlSearchInput = forwardRef( } }; + const urlInputRef = useRef(); + useImperativeHandle( ref, () => ( { + selectFocusedSuggestion: () => { + onSuggestionSelected( focusedSuggestion || { url: value } ); + }, + } ) ); + return (
{ children } diff --git a/packages/block-editor/src/components/link-control/search-results.js b/packages/block-editor/src/components/link-control/search-results.js index cc24b81f18b17..b8e2eacc1b01f 100644 --- a/packages/block-editor/src/components/link-control/search-results.js +++ b/packages/block-editor/src/components/link-control/search-results.js @@ -20,6 +20,7 @@ import { CREATE_TYPE } from './constants'; export default function LinkControlSearchResults( { instanceId, withCreateSuggestion, + className, currentInputValue, handleSuggestionClick, suggestionsListProps, @@ -75,8 +76,13 @@ export default function LinkControlSearchResults( { ); + const wrapperClass = classnames( + 'block-editor-link-control__search-results-wrapper', + className + ); + return ( -
+
{ searchResultsLabel }
{ - if ( isLinkOpen && url ) { + if ( isLinkOpen && url && ! isExperimentalNavScreen ) { // Does this look like a URL and have something TLD-ish? if ( isURL( prependHTTP( label ) ) && @@ -264,6 +268,14 @@ function NavigationLinkEdit( { return ( + { isExperimentalNavScreen && isLinkOpen && ( + setIsLinkOpen( false ) } + onChange={ ( args ) => setAttributes( args ) } + /> + ) } - { isLinkOpen && ( + { ! isExperimentalNavScreen && isLinkOpen && ( setIsLinkOpen( false ) } @@ -486,6 +498,11 @@ export default compose( [ getBlockParentsByBlockName( clientId, 'core/navigation' ) ); const navigationBlockAttributes = getBlockAttributes( rootBlock ); + const isExperimentalNavScreen = get( + getSettings(), + '__experimentalNavigationScreen', + false + ); const colors = get( getSettings(), 'colors', [] ); const hasDescendants = !! getClientIdsOfDescendants( [ clientId ] ) .length; @@ -502,6 +519,7 @@ export default compose( [ ] )?.length; return { + isExperimentalNavScreen, isParentOfSelectedBlock, isImmediateParentOfSelectedBlock, hasDescendants, diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 2525939403799..07a4ee43a570e 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -40,6 +40,7 @@ function DropdownMenu( { icon = 'menu', label, popoverProps, + onToggle: dropdownOnToggle, toggleProps, menuProps, disableOpenOnArrowDown = false, @@ -86,6 +87,7 @@ function DropdownMenu( { { const openOnArrowDown = ( event ) => { if ( disableOpenOnArrowDown ) { diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index bc858e2e964a6..e5ced1733ad84 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -125,6 +125,7 @@ export function initialize( id, settings ) { fetchLinkSuggestions, settings ); + settings.__experimentalNavigationScreen = true; render( , From 78f08620a53c3efbe8c8d0dbfc148126317aed6f Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 8 Sep 2020 18:07:52 +0800 Subject: [PATCH 2/5] Integrate LinkControl changes --- .../src/components/block-toolbar-link-control/index.js | 6 +++--- .../block-toolbar-link-control/link-input-toolbar-item.js | 2 +- .../block-toolbar-link-control/settings-toolbar-item.js | 2 +- packages/block-library/src/navigation-link/edit.js | 4 +--- packages/edit-navigation/src/index.js | 1 + 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/block-toolbar-link-control/index.js b/packages/block-editor/src/components/block-toolbar-link-control/index.js index eb8c7da1522a6..07247b65a084a 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/index.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/index.js @@ -8,7 +8,7 @@ import { useCallback, useMemo, useState } from '@wordpress/element'; /** * Internal dependencies */ -import BlockControls from '../block-controls'; +import BlockToolbarInlineEdit from '../block-toolbar-inline-edit'; import SettingsToolbarItem from './settings-toolbar-item'; import LinkInputToolbarItem from './link-input-toolbar-item'; import computeDisplayUrl from './compute-display-url'; @@ -70,7 +70,7 @@ export default function ToolbarLinkControl( { ); return ( - + @@ -86,6 +86,6 @@ export default function ToolbarLinkControl( { - + ); } diff --git a/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js b/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js index 0477af026a28d..fb0f00cccfe31 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js @@ -7,7 +7,7 @@ import { unstable_CompositeItemWidget as ToolbarWidget } from 'reakit/Composite' * WordPress dependencies */ import { - __experimentalToolbarItem as ToolbarItem, + ToolbarItem, Popover, __experimentalToolbarContext as ToolbarContext, __experimentalInputControl as InputControl, diff --git a/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js b/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js index 04f6445386f7e..7ece9e244d9e2 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js @@ -5,7 +5,7 @@ import { DropdownMenu, MenuGroup, MenuItem, - __experimentalToolbarItem as ToolbarItem, + ToolbarItem, } from '@wordpress/components'; import { chevronDown as arrowDownIcon, diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ddffcbd39f18c..40da1336cb01a 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -152,8 +152,6 @@ function NavigationLinkEdit( { } = attributes; const link = { url, - rel, - label, opensInNewTab, }; const { saveEntityRecord } = useDispatch( 'core' ); @@ -271,7 +269,7 @@ function NavigationLinkEdit( { { isExperimentalNavScreen && isLinkOpen && ( setIsLinkOpen( false ) } onChange={ ( args ) => setAttributes( args ) } /> diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index e5ced1733ad84..20c1e6936d87e 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -125,6 +125,7 @@ export function initialize( id, settings ) { fetchLinkSuggestions, settings ); + settings.__experimentalNavigationScreen = true; render( From 63b01f2954c4abe26d3005f75f940d615fc65dc4 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Wed, 30 Sep 2020 16:59:50 +0800 Subject: [PATCH 3/5] WIP remove toolbar semantics from inline link control implementation --- .../block-toolbar-link-control/index.js | 22 +-- .../link-input-toolbar-item.js | 177 ------------------ .../search-input.js | 143 ++++++++++++++ .../settings-menu.js | 80 ++++++++ .../settings-toolbar-item.js | 91 --------- 5 files changed, 229 insertions(+), 284 deletions(-) delete mode 100644 packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/search-input.js create mode 100644 packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js delete mode 100644 packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js diff --git a/packages/block-editor/src/components/block-toolbar-link-control/index.js b/packages/block-editor/src/components/block-toolbar-link-control/index.js index 07247b65a084a..61e1ebc18ddcc 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/index.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useCallback, useMemo, useState } from '@wordpress/element'; @@ -9,8 +9,8 @@ import { useCallback, useMemo, useState } from '@wordpress/element'; * Internal dependencies */ import BlockToolbarInlineEdit from '../block-toolbar-inline-edit'; -import SettingsToolbarItem from './settings-toolbar-item'; -import LinkInputToolbarItem from './link-input-toolbar-item'; +import SearchInput from './search-input'; +import SettingsMenu from './settings-menu'; import computeDisplayUrl from './compute-display-url'; import ToolbarLinkControlContext from './context'; @@ -72,19 +72,9 @@ export default function ToolbarLinkControl( { return ( - - - - - - - Done - - + + + ); diff --git a/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js b/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js deleted file mode 100644 index fb0f00cccfe31..0000000000000 --- a/packages/block-editor/src/components/block-toolbar-link-control/link-input-toolbar-item.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * External dependencies - */ -import { unstable_CompositeItemWidget as ToolbarWidget } from 'reakit/Composite'; - -/** - * WordPress dependencies - */ -import { - ToolbarItem, - Popover, - __experimentalToolbarContext as ToolbarContext, - __experimentalInputControl as InputControl, - Icon, - Spinner, -} from '@wordpress/components'; -import { link as linkIcon } from '@wordpress/icons'; -import { useContext, useEffect, useState, useRef } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import LinkControlSearchResults from '../link-control/search-results'; -import LinkControlSearchInput from '../link-control/search-input'; -import useCreatePage from '../link-control/use-create-page'; -import ToolbarLinkControlContext from './context'; - -export default function LinkInputToolbarItem() { - const toolbar = useContext( ToolbarContext ); - - const [ editUrl, setEditUrl ] = useState( '' ); - return ( - - { ( htmlProps ) => ( -
- -
- ) } -
- ); -} - -const ToolbarLinkEditorControl = function ( props ) { - const { - createSuggestion, - currentLink, - updateCurrentLink, - preferredDropdown, - setPreferredDropdown, - } = useContext( ToolbarLinkControlContext ); - - const { createPage, isCreatingPage, errorMessage } = useCreatePage( - createSuggestion - ); - - const { createErrorNotice } = useDispatch( 'core/notices' ); - useEffect( () => { - if ( errorMessage ) { - createErrorNotice( errorMessage, { type: 'snackbar' } ); - } - }, [ errorMessage ] ); - - const searchInputRef = useRef(); - - return ( - { - setPreferredDropdown( 'suggestions' ); - updateCurrentLink( { url } ); - } } - onSelect={ ( link ) => { - updateCurrentLink( link ); - } } - showInitialSuggestions={ false } - allowDirectEntry - showSuggestions={ preferredDropdown === 'suggestions' } - withCreateSuggestion - renderControl={ ( - controlProps, - inputProps, - { isLoading, suggestionsVisible } - ) => { - return ( - { - inputProps.onKeyDown( event ); - // LinkControlSearchInput renders a form which is normally submitted with an Enter key. - // In this context however, Reakit calls .preventDefault() on the enter keydown event - // so we need to select focused suggestion manually. - if ( event.key === 'Enter' ) { - // The flow of this keyDown event is quite complex, some of the consumers here are URLInput, InputControl, - // LinkControlSearchInput, ToolbarLinkEditorControl, Reakit, some of them talk to each other and keep their - // own local state. - // - // setTimeout seems to be the easiest way to achieve the intended outcome of selecting the suggestion selected - // at the time of pressing the enter key. It is quite unintuitive so let's explore some cleaner solutions in the - // longer run. - setTimeout( () => { - if ( searchInputRef?.current ) { - searchInputRef.current.selectFocusedSuggestion(); - } - } ); - } - - // When escape is pressed, either: - // * Hide suggestions if they're visible - // * Stop editing if suggestions are not visible - if ( - event.key === 'Escape' && - suggestionsVisible - ) { - setPreferredDropdown( null ); - } else { - props.onKeyDown( event ); - } - } } - onChange={ ( value, { event } ) => { - inputProps.onChange( event ); - props.onChange( event ); - } } - onFocus={ ( event ) => { - inputProps.onFocus( event ); - props.onFocus( event ); - } } - onBlur={ ( event ) => { - setPreferredDropdown( null ); - if ( 'onBlur' in inputProps ) { - inputProps.onBlur( event ); - } - if ( 'onBlur' in props ) { - props.onBlur( event ); - } - } } - prefix={ -
- -
- } - suffix={ -
- { ( isCreatingPage || isLoading ) && ( - - ) } -
- } - /> - ); - } } - /> - ); -}; - -const renderSuggestions = ( suggestionsProps ) => ( - - - -); diff --git a/packages/block-editor/src/components/block-toolbar-link-control/search-input.js b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js new file mode 100644 index 0000000000000..29d33e29d5097 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js @@ -0,0 +1,143 @@ +/** + * WordPress dependencies + */ +import { + Popover, + __experimentalInputControl as InputControl, + Icon, + Spinner, +} from '@wordpress/components'; +import { link as linkIcon } from '@wordpress/icons'; +import { useContext, useEffect, useRef } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import LinkControlSearchResults from '../link-control/search-results'; +import LinkControlSearchInput from '../link-control/search-input'; +import useCreatePage from '../link-control/use-create-page'; +import ToolbarLinkControlContext from './context'; + +export default function SearchInput( props ) { + const { + createSuggestion, + currentLink, + updateCurrentLink, + preferredDropdown, + setPreferredDropdown, + } = useContext( ToolbarLinkControlContext ); + + const { createPage, isCreatingPage, errorMessage } = useCreatePage( + createSuggestion + ); + + const { createErrorNotice } = useDispatch( 'core/notices' ); + useEffect( () => { + if ( errorMessage ) { + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } + }, [ errorMessage ] ); + + const searchInputRef = useRef(); + + return ( + { + setPreferredDropdown( 'suggestions' ); + updateCurrentLink( { url } ); + } } + onSelect={ ( link ) => { + updateCurrentLink( link ); + } } + showInitialSuggestions={ false } + allowDirectEntry + showSuggestions={ preferredDropdown === 'suggestions' } + withCreateSuggestion + renderControl={ ( controlProps, inputProps, { isLoading } ) => { + return ( + { + // inputProps.onKeyDown( event ); + // // LinkControlSearchInput renders a form which is normally submitted with an Enter key. + // // In this context however, Reakit calls .preventDefault() on the enter keydown event + // // so we need to select focused suggestion manually. + // if ( event.key === 'Enter' ) { + // // The flow of this keyDown event is quite complex, some of the consumers here are URLInput, InputControl, + // // LinkControlSearchInput, ToolbarLinkEditorControl, Reakit, some of them talk to each other and keep their + // // own local state. + // // + // // setTimeout seems to be the easiest way to achieve the intended outcome of selecting the suggestion selected + // // at the time of pressing the enter key. It is quite unintuitive so let's explore some cleaner solutions in the + // // longer run. + // setTimeout( () => { + // if ( searchInputRef?.current ) { + // searchInputRef.current.selectFocusedSuggestion(); + // } + // } ); + // } + + // // When escape is pressed, either: + // // * Hide suggestions if they're visible + // // * Stop editing if suggestions are not visible + // if ( + // event.key === 'Escape' && + // suggestionsVisible + // ) { + // setPreferredDropdown( null ); + // } else { + // props.onKeyDown( event ); + // } + // } } + onChange={ ( value, { event } ) => { + inputProps.onChange( event ); + // props.onChange( event ); + } } + // onFocus={ ( event ) => { + // inputProps.onFocus( event ); + // props.onFocus( event ); + // } } + // onBlur={ ( event ) => { + // setPreferredDropdown( null ); + // if ( 'onBlur' in inputProps ) { + // inputProps.onBlur( event ); + // } + // if ( 'onBlur' in props ) { + // props.onBlur( event ); + // } + // } } + prefix={ +
+ +
+ } + suffix={ +
+ { ( isCreatingPage || isLoading ) && ( + + ) } +
+ } + /> + ); + } } + /> + ); +} + +const renderSuggestions = ( suggestionsProps ) => ( + + + +); diff --git a/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js b/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js new file mode 100644 index 0000000000000..a081092fa73c7 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; +import { + chevronDown as arrowDownIcon, + check as checkIcon, +} from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToolbarLinkControlContext from './context'; + +export default function SettingsMenu() { + const { currentLink, updateCurrentLink, setPreferredDropdown } = useContext( + ToolbarLinkControlContext + ); + const { opensInNewTab, rel } = currentLink; + return ( + { + if ( isOpen ) { + setPreferredDropdown( 'settings' ); + } + } } + toggleProps={ { + label: __( 'Link options' ), + } } + > + { ( { onClose } ) => ( + <> + + { + updateCurrentLink( { + opensInNewTab: ! opensInNewTab, + } ); + } } + > + { __( 'Open in new tab' ) } + + { + updateCurrentLink( { + rel: + currentLink.rel === 'nofollow' + ? '' + : 'nofollow', + } ); + } } + > + { __( 'Add nofollow attribute' ) } + + + + { + updateCurrentLink( { + url: '', + } ); + onClose(); + } } + > + { __( 'Remove link' ) } + + + + ) } + + ); +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js b/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js deleted file mode 100644 index 7ece9e244d9e2..0000000000000 --- a/packages/block-editor/src/components/block-toolbar-link-control/settings-toolbar-item.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * WordPress dependencies - */ -import { - DropdownMenu, - MenuGroup, - MenuItem, - ToolbarItem, -} from '@wordpress/components'; -import { - chevronDown as arrowDownIcon, - check as checkIcon, -} from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; -import { useContext } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import ToolbarLinkControlContext from './context'; - -export default function SettingsToolbarItem() { - const { currentLink, updateCurrentLink, setPreferredDropdown } = useContext( - ToolbarLinkControlContext - ); - const { opensInNewTab, rel } = currentLink; - return ( - - { ( toolbarItemProps ) => ( - { - if ( isOpen ) { - setPreferredDropdown( 'settings' ); - } - } } - toggleProps={ { - ...toolbarItemProps, - name: 'link-options', - title: __( 'Link options' ), - } } - > - { ( { onClose } ) => ( - <> - - { - updateCurrentLink( { - opensInNewTab: ! opensInNewTab, - } ); - } } - > - { __( 'Open in new tab' ) } - - { - updateCurrentLink( { - rel: - currentLink.rel === 'nofollow' - ? '' - : 'nofollow', - } ); - } } - > - { __( 'Add nofollow attribute' ) } - - - - { - updateCurrentLink( { - url: '', - } ); - onClose(); - } } - > - { __( 'Remove link' ) } - - - - ) } - - ) } - - ); -} From f4f180b6880ca5565bbaac15a40185991ad1ff13 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 2 Oct 2020 14:58:46 +0800 Subject: [PATCH 4/5] Remove preferredDropdown code --- .../block-toolbar-link-control/index.js | 14 +--- .../search-input.js | 74 +++---------------- .../settings-menu.js | 7 +- 3 files changed, 12 insertions(+), 83 deletions(-) diff --git a/packages/block-editor/src/components/block-toolbar-link-control/index.js b/packages/block-editor/src/components/block-toolbar-link-control/index.js index 61e1ebc18ddcc..f244d65d6feab 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/index.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/index.js @@ -25,10 +25,6 @@ export default function ToolbarLinkControl( { url: computeDisplayUrl( initialLink.url ), } ); - const [ preferredDropdown, setPreferredDropdown ] = useState( - 'suggestions' - ); - const updateCurrentLink = useCallback( ( data ) => { const newLink = { @@ -57,16 +53,8 @@ export default function ToolbarLinkControl( { createSuggestion, currentLink, updateCurrentLink, - preferredDropdown, - setPreferredDropdown, } ), - [ - createSuggestion, - currentLink, - updateCurrentLink, - preferredDropdown, - setPreferredDropdown, - ] + [ createSuggestion, currentLink, updateCurrentLink ] ); return ( diff --git a/packages/block-editor/src/components/block-toolbar-link-control/search-input.js b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js index 29d33e29d5097..fca396d5605ad 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/search-input.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js @@ -19,14 +19,16 @@ import LinkControlSearchInput from '../link-control/search-input'; import useCreatePage from '../link-control/use-create-page'; import ToolbarLinkControlContext from './context'; -export default function SearchInput( props ) { - const { - createSuggestion, - currentLink, - updateCurrentLink, - preferredDropdown, - setPreferredDropdown, - } = useContext( ToolbarLinkControlContext ); +const renderSuggestions = ( suggestionsProps ) => ( + + + +); + +export default function SearchInput() { + const { createSuggestion, currentLink, updateCurrentLink } = useContext( + ToolbarLinkControlContext + ); const { createPage, isCreatingPage, errorMessage } = useCreatePage( createSuggestion @@ -50,7 +52,6 @@ export default function SearchInput( props ) { value={ currentLink.url } onCreateSuggestion={ createPage } onChange={ ( url ) => { - setPreferredDropdown( 'suggestions' ); updateCurrentLink( { url } ); } } onSelect={ ( link ) => { @@ -58,65 +59,16 @@ export default function SearchInput( props ) { } } showInitialSuggestions={ false } allowDirectEntry - showSuggestions={ preferredDropdown === 'suggestions' } withCreateSuggestion renderControl={ ( controlProps, inputProps, { isLoading } ) => { return ( { - // inputProps.onKeyDown( event ); - // // LinkControlSearchInput renders a form which is normally submitted with an Enter key. - // // In this context however, Reakit calls .preventDefault() on the enter keydown event - // // so we need to select focused suggestion manually. - // if ( event.key === 'Enter' ) { - // // The flow of this keyDown event is quite complex, some of the consumers here are URLInput, InputControl, - // // LinkControlSearchInput, ToolbarLinkEditorControl, Reakit, some of them talk to each other and keep their - // // own local state. - // // - // // setTimeout seems to be the easiest way to achieve the intended outcome of selecting the suggestion selected - // // at the time of pressing the enter key. It is quite unintuitive so let's explore some cleaner solutions in the - // // longer run. - // setTimeout( () => { - // if ( searchInputRef?.current ) { - // searchInputRef.current.selectFocusedSuggestion(); - // } - // } ); - // } - - // // When escape is pressed, either: - // // * Hide suggestions if they're visible - // // * Stop editing if suggestions are not visible - // if ( - // event.key === 'Escape' && - // suggestionsVisible - // ) { - // setPreferredDropdown( null ); - // } else { - // props.onKeyDown( event ); - // } - // } } onChange={ ( value, { event } ) => { inputProps.onChange( event ); - // props.onChange( event ); } } - // onFocus={ ( event ) => { - // inputProps.onFocus( event ); - // props.onFocus( event ); - // } } - // onBlur={ ( event ) => { - // setPreferredDropdown( null ); - // if ( 'onBlur' in inputProps ) { - // inputProps.onBlur( event ); - // } - // if ( 'onBlur' in props ) { - // props.onBlur( event ); - // } - // } } prefix={
@@ -135,9 +87,3 @@ export default function SearchInput( props ) { /> ); } - -const renderSuggestions = ( suggestionsProps ) => ( - - - -); diff --git a/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js b/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js index a081092fa73c7..3ebe07a2513d0 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js @@ -15,7 +15,7 @@ import { useContext } from '@wordpress/element'; import ToolbarLinkControlContext from './context'; export default function SettingsMenu() { - const { currentLink, updateCurrentLink, setPreferredDropdown } = useContext( + const { currentLink, updateCurrentLink } = useContext( ToolbarLinkControlContext ); const { opensInNewTab, rel } = currentLink; @@ -25,11 +25,6 @@ export default function SettingsMenu() { className="link-option" contentClassName="link-options__popover" icon={ arrowDownIcon } - onToggle={ ( isOpen ) => { - if ( isOpen ) { - setPreferredDropdown( 'settings' ); - } - } } toggleProps={ { label: __( 'Link options' ), } } From 30687dc14cf5dfc24eb6b988633791ee0f056b5d Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 2 Oct 2020 15:54:48 +0800 Subject: [PATCH 5/5] Hide suggestions when input is blurred --- .../block-toolbar-link-control/search-input.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-toolbar-link-control/search-input.js b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js index fca396d5605ad..17c947f27c82d 100644 --- a/packages/block-editor/src/components/block-toolbar-link-control/search-input.js +++ b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js @@ -8,7 +8,7 @@ import { Spinner, } from '@wordpress/components'; import { link as linkIcon } from '@wordpress/icons'; -import { useContext, useEffect, useRef } from '@wordpress/element'; +import { useContext, useEffect, useRef, useState } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; /** @@ -26,6 +26,7 @@ const renderSuggestions = ( suggestionsProps ) => ( ); export default function SearchInput() { + const [ hasFocus, setHasFocus ] = useState( false ); const { createSuggestion, currentLink, updateCurrentLink } = useContext( ToolbarLinkControlContext ); @@ -49,6 +50,7 @@ export default function SearchInput() { currentLink={ currentLink } placeholder="Start typing" renderSuggestions={ renderSuggestions } + showSuggestions={ hasFocus } value={ currentLink.url } onCreateSuggestion={ createPage } onChange={ ( url ) => { @@ -69,6 +71,12 @@ export default function SearchInput() { onChange={ ( value, { event } ) => { inputProps.onChange( event ); } } + onFocus={ () => { + setHasFocus( true ); + } } + onBlur={ () => { + setHasFocus( false ); + } } prefix={