diff --git a/core-blocks/image/edit.js b/core-blocks/image/edit.js index 7792352bc7eae..ba7426e3ba8dc 100644 --- a/core-blocks/image/edit.js +++ b/core-blocks/image/edit.js @@ -204,7 +204,7 @@ class ImageEdit extends Component { /> ) } /> - + ); diff --git a/core-blocks/image/index.js b/core-blocks/image/index.js index 712c95ce1c02f..bb30318c305a4 100644 --- a/core-blocks/image/index.js +++ b/core-blocks/image/index.js @@ -47,6 +47,20 @@ const blockAttributes = { source: 'attribute', selector: 'figure > a', attribute: 'href', + default: null, + }, + target: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'target', + default: null, + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'rel', }, id: { type: 'number', @@ -193,7 +207,7 @@ export const settings = { edit, save( { attributes } ) { - const { url, alt, caption, align, href, width, height, id } = attributes; + const { url, alt, caption, align, href, width, height, id, target, rel } = attributes; const classes = classnames( { [ `align${ align }` ]: align, @@ -212,7 +226,7 @@ export const settings = { return (
- { href ? { image } : image } + { href ? { image } : image } { caption && caption.length > 0 && }
); diff --git a/core-blocks/test/fixtures/core__image.json b/core-blocks/test/fixtures/core__image.json index 2ac6b609a3a8c..4733e1b19a735 100644 --- a/core-blocks/test/fixtures/core__image.json +++ b/core-blocks/test/fixtures/core__image.json @@ -6,9 +6,11 @@ "attributes": { "url": "https://cldup.com/uuUqE_dXzy.jpg", "alt": "", + "href": null, + "target": null, "caption": [] }, "innerBlocks": [], "originalContent": "
\"\"
" } -] +] \ No newline at end of file diff --git a/core-blocks/test/fixtures/core__image__center-caption.json b/core-blocks/test/fixtures/core__image__center-caption.json index 22ccf40cd5655..3c26ff719bcd0 100644 --- a/core-blocks/test/fixtures/core__image__center-caption.json +++ b/core-blocks/test/fixtures/core__image__center-caption.json @@ -6,6 +6,8 @@ "attributes": { "url": "https://cldup.com/YLYhpou2oq.jpg", "alt": "", + "href": null, + "target": null, "caption": [ "Give it a try. Press the \"really wide\" button on the image toolbar." ], @@ -14,4 +16,4 @@ "innerBlocks": [], "originalContent": "
\"\"
Give it a try. Press the "really wide" button on the image toolbar.
" } -] +] \ No newline at end of file diff --git a/core-blocks/test/fixtures/core__image__center-caption.parsed.json b/core-blocks/test/fixtures/core__image__center-caption.parsed.json index abd5611da1948..16b41c3fcec7c 100644 --- a/core-blocks/test/fixtures/core__image__center-caption.parsed.json +++ b/core-blocks/test/fixtures/core__image__center-caption.parsed.json @@ -11,4 +11,4 @@ "attrs": {}, "innerHTML": "\n" } -] +] \ No newline at end of file diff --git a/editor/components/url-input/button.js b/editor/components/url-input/button.js index f42120c8ec6a3..170107ca7166d 100644 --- a/editor/components/url-input/button.js +++ b/editor/components/url-input/button.js @@ -9,39 +9,112 @@ import classnames from 'classnames'; import './style.scss'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { IconButton } from '@wordpress/components'; +import { IconButton, ToggleControl, Popover } from '@wordpress/components'; +import { keycodes } from '@wordpress/utils'; +import { filterURLForDisplay } from '../../utils/url'; +import { prependHTTP } from '@wordpress/url'; + +const { ESCAPE, LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } = keycodes; /** * Internal dependencies */ import UrlInput from './'; +const stopKeyPropagation = ( event ) => event.stopPropagation(); class UrlInputButton extends Component { constructor() { super( ...arguments ); this.toggle = this.toggle.bind( this ); this.submitLink = this.submitLink.bind( this ); + this.dropLink = this.dropLink.bind( this ); + this.toggleLinkSettingsVisibility = this.toggleLinkSettingsVisibility.bind( this ); + this.setLinkTarget = this.setLinkTarget.bind( this ); + this.onChangeLinkValue = this.onChangeLinkValue.bind( this ); + this.onKeyDown = this.onKeyDown.bind( this ); + this.editLink = this.editLink.bind( this ); + this.state = { expanded: false, + isEditing: true, + settingsVisible: false, + opensInNewWindow: this.props.attributes.target === '_blank' ? true : false, + linkValue: this.props.url, }; } + onKeyDown( event ) { + if ( event.keyCode === ESCAPE ) { + this.dropLink(); + this.toggle(); + } + if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + stopKeyPropagation( event ); + } + } + + dropLink() { + this.props.setAttributes( { href: null } ); + this.setState( { linkValue: '', settingsVisible: false } ); + } + + editLink() { + this.setState( { isEditing: ! this.state.isEditing } ); + } + toggle() { this.setState( { expanded: ! this.state.expanded } ); } + onChangeLinkValue( value ) { + this.setState( { linkValue: value } ); + } + submitLink( event ) { event.preventDefault(); - this.toggle(); + const value = prependHTTP( this.state.linkValue ); + this.props.setAttributes( { + href: value, + } ); + this.setState( { isEditing: false, linkValue: value } ); + } + + toggleLinkSettingsVisibility() { + this.setState( ( state ) => ( { settingsVisible: ! state.settingsVisible } ) ); + } + + setLinkTarget( opensInNewWindow ) { + this.setState( { opensInNewWindow } ); + if ( opensInNewWindow ) { + this.props.setAttributes( { + target: '_blank', + rel: 'noreferrer noopener', + } ); + } else { + this.props.setAttributes( { + target: null, + rel: null, + } ); + } } render() { - const { url, onChange } = this.props; - const { expanded } = this.state; + const { url, attributes: { id } } = this.props; + const { expanded, settingsVisible, opensInNewWindow, linkValue, isEditing } = this.state; const buttonLabel = url ? __( 'Edit Link' ) : __( 'Insert Link' ); + const linkSettings = settingsVisible && ( +
+ +
+ ); + return ( -
+
- { expanded && -
-
- - - -
-
+ { + expanded && ( + + { isEditing && ( + // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +
+
+ + + +
+ { linkSettings } +
+ ) } + + { ! isEditing && ( + // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar + /* eslint-disable jsx-a11y/no-static-element-interactions */ + + /* eslint-enable jsx-a11y/no-static-element-interactions */ + ) } +
+ ) } -
+
); } } diff --git a/editor/components/url-input/index.js b/editor/components/url-input/index.js index 21d1e98ef5578..f174140cee74c 100644 --- a/editor/components/url-input/index.js +++ b/editor/components/url-input/index.js @@ -13,6 +13,7 @@ import { __, sprintf, _n } from '@wordpress/i18n'; import { Component, Fragment } from '@wordpress/element'; import { keycodes, decodeEntities } from '@wordpress/utils'; import { Spinner, withInstanceId, withSpokenMessages, Popover } from '@wordpress/components'; +import { prependHTTP } from '@wordpress/url'; const { UP, DOWN, ENTER } = keycodes; @@ -106,7 +107,8 @@ class UrlInput extends Component { } onChange( event ) { - const inputValue = event.target.value; + const inputValue = prependHTTP( event.target.value ); + this.props.onChange( inputValue ); this.updateSuggestions( inputValue ); } diff --git a/editor/components/url-input/style.scss b/editor/components/url-input/style.scss index 3789f52fc5d6b..28db4b5bc0bf7 100644 --- a/editor/components/url-input/style.scss +++ b/editor/components/url-input/style.scss @@ -109,3 +109,10 @@ $input-size: 230px; height: $icon-button-size; } } + +.editor-format-toolbar__link-value { + &:focus { + box-shadow: none; + } +} + diff --git a/editor/components/url-input/test/button.js b/editor/components/url-input/test/button.js index 19bfc72c36b9c..ebb12b3ee18f1 100644 --- a/editor/components/url-input/test/button.js +++ b/editor/components/url-input/test/button.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { shallow, mount } from 'enzyme'; +import { shallow } from 'enzyme'; /** * Internal dependencies @@ -49,19 +49,4 @@ describe( 'UrlInputButton', () => { wrapper.find( '[data-test=\'UrlInput\']' ).simulate( 'change' ); expect( onChangeMock ).toHaveBeenCalledTimes( 2 ); } ); - it( 'should close the form when user clicks Close button', () => { - const wrapper = shallow( ); - clickEditLink( wrapper ); - expect( wrapper.state().expanded ).toBe( true ); - wrapper.find( '.editor-url-input__back' ).simulate( 'click' ); - expect( wrapper.state().expanded ).toBe( false ); - } ); - it( 'should close the form when user submits it', () => { - const wrapper = mount( ); - clickEditLink( wrapper ); - expect( wrapper.state().expanded ).toBe( true ); - wrapper.find( 'form' ).simulate( 'submit' ); - expect( wrapper.state().expanded ).toBe( false ); - wrapper.unmount(); - } ); } );