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 3bb72598aabf41..699812332029a5 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 00000000000000..5dfb684bbc9462 --- /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 00000000000000..d7091d02d4a5f1 --- /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 00000000000000..eb8c7da1522a6a --- /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 00000000000000..0477af026a28d1 --- /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 00000000000000..04f6445386f7e4 --- /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 00000000000000..53ebf2e4c3b1e8 --- /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 06b76dc2f95f55..dc727ffd49f46c 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 c8ae882ae1aba8..354b1e07c0b142 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 cc24b81f18b179..b8e2eacc1b01f4 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 2525939403799d..07a4ee43a570e2 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 bc858e2e964a67..e5ced1733ad84b 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 eb8c7da1522a6a..07247b65a084ad 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 0477af026a28d1..fb0f00cccfe319 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 04f6445386f7e4..7ece9e244d9e21 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 ddffcbd39f18c5..40da1336cb01a7 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 e5ced1733ad84b..20c1e6936d87e6 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 07247b65a084ad..61e1ebc18ddccb 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 fb0f00cccfe319..00000000000000 --- 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 00000000000000..29d33e29d5097a --- /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 00000000000000..a081092fa73c71 --- /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 7ece9e244d9e21..00000000000000 --- 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 61e1ebc18ddccb..f244d65d6feab4 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 29d33e29d5097a..fca396d5605adc 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 a081092fa73c71..3ebe07a2513d02 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 fca396d5605adc..17c947f27c82d9 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={