Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update/unify link interfaces #6392 #7022

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
10e4082
Remove submit button; Add ellipsis button
RockinRonE May 27, 2018
7a99dfe
add toggle settings to state
RockinRonE May 27, 2018
3aaf90a
Add setLinkTarget func
RockinRonE May 27, 2018
14e09dc
Pass props to UrlInputButton
RockinRonE May 27, 2018
efdef98
Add set target attribute
RockinRonE May 27, 2018
2d65e82
Add target attribute
RockinRonE May 27, 2018
66d91f7
Add rel attribute
RockinRonE May 28, 2018
b604f7e
Set rel attribute
RockinRonE May 28, 2018
cd05ad6
Add prependHTTP
RockinRonE May 28, 2018
13025ab
Update blockAttributes with target and rel
RockinRonE May 28, 2018
b1aec98
Add popover and remove commented out code
RockinRonE May 29, 2018
2935e5f
Add onKeyDown functions
RockinRonE May 29, 2018
65541c4
Add keyDown handling
RockinRonE May 29, 2018
a79a991
Add setAttributes func to submit
RockinRonE May 29, 2018
3d9ce25
Add edit link section
RockinRonE May 29, 2018
141bf49
Remove box-shadow on focus
RockinRonE May 29, 2018
0b23bea
Add null default value
RockinRonE May 29, 2018
b538789
Add correct input and edit toggle forms
RockinRonE May 29, 2018
85f9fa3
Destructure props and remove toggle on pressing enter
RockinRonE May 29, 2018
f109c4c
Add toggle state reflected in toggle button
RockinRonE May 29, 2018
45193d2
Add disable lint and remove log statement
RockinRonE May 29, 2018
5d45dfb
Add href and target attrs to tests
RockinRonE May 29, 2018
4bc4620
Remove close button test
RockinRonE May 29, 2018
acb5b5a
Remove close form test
RockinRonE May 30, 2018
0edf482
fix merge conflicts
RockinRonE May 30, 2018
c14e4b3
fix formatting
RockinRonE May 30, 2018
345d3e7
fix formatting
RockinRonE May 30, 2018
ce4d81b
remove unused import
RockinRonE May 30, 2018
ccecb2b
remove whitespace
RockinRonE May 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core-blocks/image/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class ImageEdit extends Component {
/>
) }
/>
<UrlInputButton onChange={ this.onSetHref } url={ href } />
<UrlInputButton onChange={ this.onSetHref } url={ href } { ...this.props } />
</Toolbar>
</BlockControls>
);
Expand Down
18 changes: 16 additions & 2 deletions core-blocks/image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ const blockAttributes = {
source: 'attribute',
selector: 'figure > a',
attribute: 'href',
default: null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why we need the default value?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was because I kept receiving this error when running tests. I wasn't exactly sure what to do so please feel free to suggest and I'll look into it!

screen shot 2018-05-31 at 3 19 40 pm

},
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',
Expand Down Expand Up @@ -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,
Expand All @@ -212,7 +226,7 @@ export const settings = {

return (
<figure className={ classes }>
{ href ? <a href={ href }>{ image }</a> : image }
{ href ? <a href={ href } target={ target } rel={ rel }>{ image }</a> : image }
{ caption && caption.length > 0 && <RichText.Content tagName="figcaption" value={ caption } /> }
</figure>
);
Expand Down
4 changes: 3 additions & 1 deletion core-blocks/test/fixtures/core__image.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"attributes": {
"url": "https://cldup.com/uuUqE_dXzy.jpg",
"alt": "",
"href": null,
"target": null,
"caption": []
},
"innerBlocks": [],
"originalContent": "<figure class=\"wp-block-image\"><img src=\"https://cldup.com/uuUqE_dXzy.jpg\" alt=\"\" /></figure>"
}
]
]
4 changes: 3 additions & 1 deletion core-blocks/test/fixtures/core__image__center-caption.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
],
Expand All @@ -14,4 +16,4 @@
"innerBlocks": [],
"originalContent": "<figure class=\"wp-block-image aligncenter\"><img src=\"https://cldup.com/YLYhpou2oq.jpg\" alt=\"\" /><figcaption>Give it a try. Press the &quot;really wide&quot; button on the image toolbar.</figcaption></figure>"
}
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
"attrs": {},
"innerHTML": "\n"
}
]
]
170 changes: 144 additions & 26 deletions editor/components/url-input/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind putting this into a separate dependencies section labelled "Internal dependencies" ?

See: https://github.com/WordPress/gutenberg/blob/master/docs/reference/coding-guidelines.md#imports

Notably, this would apply for any imports which are made to a relative path within the same top-level folder.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the link and my apologies

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 } );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to track this as part of state?

Or can it be inferred from props in render as:

const opensInNewWindow = ( this.props.attributes.target === '_blank' );

The hope being that we could consolidate to one source of truth (attributes), rather than tracking in two places, which is more prone to falling out of sync.

Edit: I see after the fact that this is also how we populate the initial state in the constructor.

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 && (
<div className="editor-format-toolbar__link-modal-line editor-format-toolbar__link-settings">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coding guidelines for CSS naming is written such that this is not a valid class name to assign to this component. It's reflective of the fact that components are meant to compose one another, and shouldn't be inheriting behaviors, since it can lead to accidental breakage if a change to the format toolbar inadvertently affects this component. Isolating components to silo their behavior / silos helps prevent this.

What I'd like to see instead, and what seemed to be hinted by the idea of "unifying" in the original pull request, is that instead of duplicating what already exists elsewhere, we ought to consider creating a new, separate component which contains the shared behavior (of link management), then use that both here and in other existing usage.

<ToggleControl
label={ __( 'Open in new window' ) }
checked={ opensInNewWindow }
onChange={ this.setLinkTarget }
/>
</div>
);

return (
<div className="editor-url-input__button">
<div className="editor-url-input__button" >
<IconButton
icon="admin-links"
label={ buttonLabel }
Expand All @@ -50,28 +123,73 @@ class UrlInputButton extends Component {
'is-active': url,
} ) }
/>
{ expanded &&
<form
className="editor-url-input__button-modal"
onSubmit={ this.submitLink }
>
<div className="editor-url-input__button-modal-line">
<IconButton
className="editor-url-input__back"
icon="arrow-left-alt"
label={ __( 'Close' ) }
onClick={ this.toggle }
/>
<UrlInput value={ url || '' } onChange={ onChange } data-test="UrlInput" />
<IconButton
icon="editor-break"
label={ __( 'Submit' ) }
type="submit"
/>
</div>
</form>
{
expanded && (
<Popover
position="bottom center"
focusOnMount={ true }
key={ id }
>
{ isEditing && (
// Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
<form
className="editor-url-input__button-modal"
onSubmit={ this.submitLink }
onKeyPress={ stopKeyPropagation }
onKeyDown={ this.onKeyDown }
>
<div className="editor-url-input__button-modal-line">
<UrlInput value={ linkValue } onKeyPress={ stopKeyPropagation } onChange={ this.onChangeLinkValue } data-test="UrlInput" />
<IconButton
icon="editor-break"
label={ __( 'Submit' ) }
type="submit"
/>
<IconButton
className="editor-format-toolbar__link-settings-toggle"
icon="ellipsis"
label={ __( 'Link Settings' ) }
onClick={ this.toggleLinkSettingsVisibility }
aria-expanded={ settingsVisible }
/>
</div>
{ linkSettings }
</form>
) }

{ ! isEditing && (
// Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar
/* eslint-disable jsx-a11y/no-static-element-interactions */
<div
className="editor-format-toolbar__link-modal"
onKeyPress={ stopKeyPropagation }
>
<div className="editor-format-toolbar__link-modal-line">
<a
className="editor-format-toolbar__link-value"
href={ url }
target="_blank"
>
{ url && filterURLForDisplay( decodeURI( url ) ) }
</a>
<IconButton icon="edit" label={ __( 'Edit' ) } onClick={ this.editLink } />
<IconButton
className="editor-format-toolbar__link-settings-toggle"
icon="ellipsis"
label={ __( 'Link Settings' ) }
onClick={ this.toggleLinkSettingsVisibility }
aria-expanded={ settingsVisible }
/>
</div>
{ linkSettings }
</div>
/* eslint-enable jsx-a11y/no-static-element-interactions */
) }
</Popover>
)
}
</div>
</div >
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change, while not explicitly part of the coding standards, does not seem necessary and is in contrast with style convention of JSX used elsewhere in the codebase.

);
}
}
Expand Down
4 changes: 3 additions & 1 deletion editor/components/url-input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 );
}
Expand Down
7 changes: 7 additions & 0 deletions editor/components/url-input/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,10 @@ $input-size: 230px;
height: $icon-button-size;
}
}

.editor-format-toolbar__link-value {
&:focus {
box-shadow: none;
}
}

17 changes: 1 addition & 16 deletions editor/components/url-input/test/button.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { shallow, mount } from 'enzyme';
import { shallow } from 'enzyme';

/**
* Internal dependencies
Expand Down Expand Up @@ -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( <UrlInputButton /> );
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( <UrlInputButton /> );
clickEditLink( wrapper );
expect( wrapper.state().expanded ).toBe( true );
wrapper.find( 'form' ).simulate( 'submit' );
expect( wrapper.state().expanded ).toBe( false );
wrapper.unmount();
} );
} );