-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Changes from all commits
10e4082
7a99dfe
3aaf90a
14e09dc
efdef98
2d65e82
66d91f7
b604f7e
cd05ad6
13025ab
b1aec98
2935e5f
65541c4
a79a991
3d9ce25
141bf49
0b23bea
b538789
85f9fa3
f109c4c
45193d2
5d45dfb
4bc4620
acb5b5a
0edf482
c14e4b3
345d3e7
ce4d81b
ccecb2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,4 +11,4 @@ | |
"attrs": {}, | ||
"innerHTML": "\n" | ||
} | ||
] | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 |
||
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"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } | ||
|
@@ -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 > | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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!