diff --git a/blocks/rich-text/format-toolbar/index.js b/blocks/rich-text/format-toolbar/index.js
index edc1c54a61b749..b53cf3cd3336c0 100644
--- a/blocks/rich-text/format-toolbar/index.js
+++ b/blocks/rich-text/format-toolbar/index.js
@@ -11,9 +11,10 @@ import { keycodes } from '@wordpress/utils';
*/
import './style.scss';
import UrlInput from '../../url-input';
+import { LINK_PLACEHOLDER_VALUE } from '../';
import { filterURLForDisplay } from '../../../editor/utils/url';
-const { ESCAPE, LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } = keycodes;
+const { ESCAPE } = keycodes;
const FORMATTING_CONTROLS = [
{
@@ -32,8 +33,6 @@ const FORMATTING_CONTROLS = [
format: 'strikethrough',
},
{
- icon: 'admin-links',
- title: __( 'Link' ),
format: 'link',
},
];
@@ -49,7 +48,6 @@ class FormatToolbar extends Component {
super( ...arguments );
this.state = {
- isAddingLink: false,
isEditingLink: false,
newLinkValue: '',
};
@@ -63,21 +61,20 @@ class FormatToolbar extends Component {
}
onKeyDown( event ) {
+ event.stopPropagation();
+
if ( event.keyCode === ESCAPE ) {
- if ( this.state.isEditingLink ) {
- event.stopPropagation();
+ this.setState( { isEditingLink: false, newLinkValue: '' } );
+
+ if ( this.props.formats.link.value === LINK_PLACEHOLDER_VALUE ) {
this.dropLink();
}
}
- if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) {
- stopKeyPropagation( event );
- }
}
componentWillReceiveProps( nextProps ) {
if ( this.props.selectedNodeId !== nextProps.selectedNodeId ) {
this.setState( {
- isAddingLink: false,
isEditingLink: false,
newLinkValue: '',
} );
@@ -97,25 +94,28 @@ class FormatToolbar extends Component {
}
addLink() {
- this.setState( { isEditingLink: false, isAddingLink: true, newLinkValue: '' } );
+ this.props.onChange( { link: { value: LINK_PLACEHOLDER_VALUE } } );
}
dropLink() {
this.props.onChange( { link: undefined } );
- this.setState( { isEditingLink: false, isAddingLink: false, newLinkValue: '' } );
}
editLink( event ) {
event.preventDefault();
- this.setState( { isEditingLink: false, isAddingLink: true, newLinkValue: this.props.formats.link.value } );
+ this.setState( { isEditingLink: true, newLinkValue: this.props.formats.link.value } );
}
submitLink( event ) {
event.preventDefault();
- this.props.onChange( { link: { value: this.state.newLinkValue } } );
- if ( this.state.isAddingLink ) {
+
+ this.setState( { isEditingLink: false, newLinkValue: '' } );
+
+ if ( this.props.formats.link.value === LINK_PLACEHOLDER_VALUE ) {
this.props.speak( __( 'Link added.' ), 'assertive' );
}
+
+ this.props.onChange( { link: { value: this.state.newLinkValue } } );
}
isFormatActive( format ) {
@@ -124,69 +124,79 @@ class FormatToolbar extends Component {
render() {
const { formats, focusPosition, enabledControls = DEFAULT_CONTROLS, customControls = [] } = this.props;
- const { isAddingLink, isEditingLink, newLinkValue } = this.state;
- const linkStyle = focusPosition ?
- { position: 'absolute', ...focusPosition } :
- null;
+ const { isEditingLink, newLinkValue } = this.state;
const toolbarControls = FORMATTING_CONTROLS.concat( customControls )
- .filter( control => enabledControls.indexOf( control.format ) !== -1 )
+ .filter( control => enabledControls.includes( control.format ) )
.map( ( control ) => {
- const isLink = control.format === 'link';
+ const isActive = this.isFormatActive( control.format );
+
+ if ( control.format === 'link' ) {
+ return {
+ ...control,
+ icon: isActive ? 'editor-unlink' : 'admin-links', // TODO: proper unlink icon
+ title: isActive ? __( 'Unlink' ) : __( 'Link' ),
+ isActive,
+ onClick: isActive ? this.dropLink : this.addLink,
+ };
+ }
+
return {
...control,
- onClick: isLink ? this.addLink : this.toggleFormat( control.format ),
- isActive: this.isFormatActive( control.format ) || ( isLink && isAddingLink ),
+ isActive,
+ onClick: this.toggleFormat( control.format ),
};
} );
+ const hasLinkUI = !! formats.link;
+ const hasEditLinkUI = hasLinkUI && ( isEditingLink || formats.link.value === LINK_PLACEHOLDER_VALUE );
+ const hasViewLinkUI = hasLinkUI && ! isEditingLink && formats.link.value !== LINK_PLACEHOLDER_VALUE;
+
return (
- { ( isAddingLink || isEditingLink ) &&
- // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar
- /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
-
-
-
- /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */
- }
-
- { !! formats.link && ! isAddingLink && ! isEditingLink &&
- // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar
- /* eslint-disable jsx-a11y/no-static-element-interactions */
+ { hasLinkUI &&
-
- /* eslint-enable jsx-a11y/no-static-element-interactions */
}
);
diff --git a/blocks/rich-text/format-toolbar/style.scss b/blocks/rich-text/format-toolbar/style.scss
index 609255d2e3ffc8..7f761c080a66c9 100644
--- a/blocks/rich-text/format-toolbar/style.scss
+++ b/blocks/rich-text/format-toolbar/style.scss
@@ -3,11 +3,11 @@
}
.blocks-format-toolbar__link-modal {
- position: absolute;
+ position: relative;
+ left: -50%;
box-shadow: 0px 3px 20px rgba( 18, 24, 30, .1 ), 0px 1px 3px rgba( 18, 24, 30, .1 );
border: 1px solid #e0e5e9;
background: #fff;
- width: 300px;
display: flex;
flex-direction: column;
font-family: $default-font;
@@ -37,8 +37,4 @@
position: relative;
white-space: nowrap;
min-width: 0;
-
- &:after {
- @include long-content-fade( $size: 40% );
- }
}
diff --git a/blocks/rich-text/index.js b/blocks/rich-text/index.js
index eeeebff9581f13..8d7a783142a9e5 100644
--- a/blocks/rich-text/index.js
+++ b/blocks/rich-text/index.js
@@ -72,6 +72,12 @@ export function getFormatProperties( formatName, parents ) {
const DEFAULT_FORMATS = [ 'bold', 'italic', 'strikethrough', 'link' ];
+/**
+ * When inserting a new link, we insert an tag with this placeholder href
+ * so that there is a visual indication of which text will be made into a link.
+ */
+export const LINK_PLACEHOLDER_VALUE = '_wp_link_placeholder';
+
export default class RichText extends Component {
constructor( props ) {
super( ...arguments );
@@ -428,11 +434,10 @@ export default class RichText extends Component {
const container = findRelativeParent( this.editor.getBody() );
const containerPosition = container.getBoundingClientRect();
const toolbarOffset = { top: 10, left: 0 };
- const linkModalWidth = 298;
return {
top: position.top - containerPosition.top + ( position.height ) + toolbarOffset.top,
- left: position.left - containerPosition.left - ( linkModalWidth / 2 ) + ( position.width / 2 ) + toolbarOffset.left,
+ left: position.left - containerPosition.left + ( position.width / 2 ) + toolbarOffset.left,
};
}
@@ -656,22 +661,41 @@ export default class RichText extends Component {
);
}
- onNodeChange( { parents } ) {
+ removePlaceholderLinks() {
+ this.editor.$( 'a' ).each( ( index, element ) => {
+ if ( element.getAttribute( 'href' ) === LINK_PLACEHOLDER_VALUE ) {
+ this.editor.dom.remove( element, true );
+ }
+ } );
+ }
+
+ getFormats( parents ) {
+ return this.editor.formatter.matchAll( this.props.formattingControls ).reduce( ( formats, format ) => {
+ formats[ format ] = {
+ isActive: true,
+ ...getFormatProperties( format, parents ),
+ };
+ return formats;
+ }, {} );
+ }
+
+ onNodeChange( event ) {
if ( document.activeElement !== this.editor.getBody() ) {
return;
}
- const formatNames = this.props.formattingControls;
- const formats = this.editor.formatter.matchAll( formatNames ).reduce( ( accFormats, activeFormat ) => {
- accFormats[ activeFormat ] = {
- isActive: true,
- ...getFormatProperties( activeFormat, parents ),
- };
- return accFormats;
- }, {} );
+ const formats = this.getFormats( event.parents );
+
+ // Remove all placeholder links if selection moves away from a placeholder link
+ if ( ! formats.link || formats.link.value !== LINK_PLACEHOLDER_VALUE ) {
+ this.removePlaceholderLinks();
+ }
- const focusPosition = this.getFocusPosition();
- this.setState( { formats, focusPosition, selectedNodeId: this.state.selectedNodeId + 1 } );
+ this.setState( {
+ formats,
+ focusPosition: this.getFocusPosition(),
+ selectedNodeId: this.state.selectedNodeId + 1,
+ } );
}
updateContent() {
diff --git a/blocks/url-input/index.js b/blocks/url-input/index.js
index a98d0df14025aa..755707fae0b2c2 100644
--- a/blocks/url-input/index.js
+++ b/blocks/url-input/index.js
@@ -177,11 +177,9 @@ class UrlInput extends Component {
render() {
const { value, instanceId } = this.props;
const { showSuggestions, posts, selectedSuggestion, loading } = this.state;
- /* eslint-disable jsx-a11y/no-autofocus */
return (
);
- /* eslint-enable jsx-a11y/no-autofocus */
}
}