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

Add "Open in new window" option to links #2628

Merged
merged 9 commits into from
Sep 5, 2017
33 changes: 30 additions & 3 deletions blocks/editable/format-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ const DEFAULT_CONTROLS = [ 'bold', 'italic', 'strikethrough', 'link' ];
class FormatToolbar extends Component {
constructor() {
super( ...arguments );

this.state = {
isAddingLink: false,
isEditingLink: false,
settingsVisible: false,
opensInNewWindow: false,
newLinkValue: '',
};

Expand All @@ -52,6 +53,8 @@ class FormatToolbar extends Component {
this.submitLink = this.submitLink.bind( this );
this.onKeyDown = this.onKeyDown.bind( this );
this.onChangeLinkValue = this.onChangeLinkValue.bind( this );
this.toggleLinkSettingsVisibility = this.toggleLinkSettingsVisibility.bind( this );
this.setLinkTarget = this.setLinkTarget.bind( this );
}

componentDidMount() {
Expand All @@ -76,6 +79,8 @@ class FormatToolbar extends Component {
this.setState( {
isAddingLink: false,
isEditingLink: false,
settingsVisible: false,
opensInNewWindow: !! nextProps.formats.link && !! nextProps.formats.link.target,
newLinkValue: '',
} );
}
Expand All @@ -93,6 +98,17 @@ class FormatToolbar extends Component {
};
}

toggleLinkSettingsVisibility() {
const settingsVisible = ! this.state.settingsVisible;
this.setState( { settingsVisible } );
Copy link
Contributor

Choose a reason for hiding this comment

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

there's an implicit coding standard stating that if the next state depends on the previous, we should use the callback notation (I think we were trying to add an eslint for this in an old PR)

this.setState( ( state ) => ( { settingsVisible: ! state.settingsVisible } ) );

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed!

}

setLinkTarget( event ) {
const opensInNewWindow = event.target.checked;
this.setState( { opensInNewWindow } );
this.props.onChange( { link: { value: this.props.formats.link.value, target: opensInNewWindow ? '_blank' : undefined } } );
}

addLink() {
this.setState( { isEditingLink: false, isAddingLink: true, newLinkValue: '' } );
}
Expand All @@ -109,15 +125,15 @@ class FormatToolbar extends Component {

submitLink( event ) {
event.preventDefault();
this.props.onChange( { link: { value: this.state.newLinkValue } } );
this.props.onChange( { link: { value: this.state.newLinkValue, target: this.state.opensInNewWindow ? '_blank' : undefined } } );
if ( this.state.isAddingLink ) {
this.props.speak( __( 'Link inserted.' ), 'assertive' );
}
}

render() {
const { formats, focusPosition, enabledControls = DEFAULT_CONTROLS } = this.props;
const { isAddingLink, isEditingLink, newLinkValue } = this.state;
const { isAddingLink, isEditingLink, newLinkValue, settingsVisible, opensInNewWindow } = this.state;
const linkStyle = focusPosition
? { position: 'absolute', ...focusPosition }
: null;
Expand All @@ -130,6 +146,13 @@ class FormatToolbar extends Component {
isActive: !! formats[ control.format ],
} ) );

// TODO: make this not look hideous
const linkSettings = settingsVisible && (
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you considered using the Popover component?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, mainly because I didn't know what it was 😄 Looks like a good way to have this visibility state managed for us, although I don't think it would help with my awful design skills?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've switched this to use a popover, but it still looks really bad. I'm not sure what markup we need to have this render like the wireframes, and there's nowhere that we're actually using a PopoverContext for me to see how to get this rendering nicely, either. Also, there are focus issues once you toggle the checkbox (click the settings icon, toggle the checkbox, then you have to click the settings icon twice to close the Popover, as I think the first click takes the focus away from the Popover, which feels bad.)

<div>
{ __( 'Open in new window' ) }: <input type="checkbox" checked={ opensInNewWindow } label={ __( 'Open in new window' ) } onChange={ this.setLinkTarget } />
</div>
);

if ( enabledControls.indexOf( 'link' ) !== -1 ) {
toolbarControls.push( {
icon: 'admin-links',
Expand All @@ -151,6 +174,8 @@ class FormatToolbar extends Component {
<UrlInput value={ newLinkValue } onChange={ this.onChangeLinkValue } />
<IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" />
<IconButton icon="editor-unlink" label={ __( 'Remove link' ) } onClick={ this.dropLink } />
<IconButton icon="admin-generic" onClick={ this.toggleLinkSettingsVisibility } />
{ linkSettings }
</form>
}

Expand All @@ -165,6 +190,8 @@ class FormatToolbar extends Component {
</a>
<IconButton icon="edit" label={ __( 'Edit' ) } onClick={ this.editLink } />
<IconButton icon="editor-unlink" label={ __( 'Remove link' ) } onClick={ this.dropLink } />
<IconButton icon="admin-generic" onClick={ this.toggleLinkSettingsVisibility } />
{ linkSettings }
</div>
}
</div>
Expand Down
4 changes: 2 additions & 2 deletions blocks/editable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ export default class Editable extends Component {
const formats = {};
const link = find( parents, ( node ) => node.nodeName.toLowerCase() === 'a' );
if ( link ) {
formats.link = { value: link.getAttribute( 'href' ) || '', node: link };
formats.link = { value: link.getAttribute( 'href' ) || '', target: link.getAttribute( 'target' ) || '', node: link };
}
const activeFormats = this.editor.formatter.matchAll( [ 'bold', 'italic', 'strikethrough' ] );
activeFormats.forEach( ( activeFormat ) => formats[ activeFormat ] = true );
Expand Down Expand Up @@ -542,7 +542,7 @@ export default class Editable extends Component {
if ( ! anchor ) {
this.removeFormat( 'link' );
}
this.applyFormat( 'link', { href: formatValue.value }, anchor );
this.applyFormat( 'link', { href: formatValue.value, target: formatValue.target }, anchor );
} else {
this.editor.execCommand( 'Unlink' );
}
Expand Down