Skip to content

Commit

Permalink
WIP: Add inline image resizing UI (#13737)
Browse files Browse the repository at this point in the history
* Basic functionality

* Only update width on submit

* Fix styles

* Push a little polish

* Select objects when clicked

* Fix getDerivedStateFromProps

* Reselect image after update

* Move PositionedAtSelection

* Export PositionAtSelection in same way we export other @wordpress/components
  • Loading branch information
ellatrix authored and youknowriad committed Mar 6, 2019
1 parent ac0e970 commit dbb4b1d
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 9 deletions.
1 change: 0 additions & 1 deletion lib/packages-dependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@
'wp-escape-html' => array(),
'wp-format-library' => array(
'wp-components',
'wp-dom',
'wp-editor',
'wp-element',
'wp-i18n',
Expand Down
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export { default as PanelHeader } from './panel/header';
export { default as PanelRow } from './panel/row';
export { default as Placeholder } from './placeholder';
export { default as Popover } from './popover';
export { default as PositionedAtSelection } from './positioned-at-selection';
export { default as QueryControls } from './query-controls';
export { default as RadioControl } from './radio-control';
export { default as RangeControl } from './range-control';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function getCurrentCaretPositionStyle() {
*
* @type {WPComponent}
*/
class PositionedAtSelection extends Component {
export default class PositionedAtSelection extends Component {
constructor() {
super( ...arguments );

Expand All @@ -63,5 +63,3 @@ class PositionedAtSelection extends Component {
);
}
}

export default PositionedAtSelection;
4 changes: 4 additions & 0 deletions packages/editor/src/components/rich-text/editable.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ export default class Editable extends Component {
onCompositionEnd,
onFocus,
onBlur,
onMouseDown,
onTouchStart,
} = this.props;

ariaProps.role = 'textbox';
Expand All @@ -188,6 +190,8 @@ export default class Editable extends Component {
onBlur,
onKeyDown,
onCompositionEnd,
onMouseDown,
onTouchStart,
} );
}
}
29 changes: 29 additions & 0 deletions packages/editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class RichText extends Component {
this.setRef = this.setRef.bind( this );
this.valueToEditableHTML = this.valueToEditableHTML.bind( this );
this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this );
this.onPointerDown = this.onPointerDown.bind( this );

this.formatToValue = memize( this.formatToValue.bind( this ), { size: 1 } );

Expand Down Expand Up @@ -758,6 +759,32 @@ export class RichText extends Component {
this.onSplit( before, after, ...blocks );
}

/**
* Select object when they are clicked. The browser will not set any
* selection when clicking e.g. an image.
*
* @param {SyntheticEvent} event Synthetic mousedown or touchstart event.
*/
onPointerDown( event ) {
const { target } = event;

// If the child element has no text content, it must be an object.
if ( target === this.editableRef || target.textContent ) {
return;
}

const { parentNode } = target;
const index = Array.from( parentNode.childNodes ).indexOf( target );
const range = target.ownerDocument.createRange();
const selection = getSelection();

range.setStart( target.parentNode, index );
range.setEnd( target.parentNode, index + 1 );

selection.removeAllRanges();
selection.addRange( range );
}

componentDidUpdate( prevProps ) {
const { tagName, value, isSelected } = this.props;

Expand Down Expand Up @@ -994,6 +1021,8 @@ export class RichText extends Component {
onKeyDown={ this.onKeyDown }
onFocus={ this.onFocus }
onBlur={ this.onBlur }
onMouseDown={ this.onPointerDown }
onTouchStart={ this.onPointerDown }
multilineTag={ this.multilineTag }
multilineWrapperTags={ this.multilineWrapperTags }
setRef={ this.setRef }
Expand Down
1 change: 0 additions & 1 deletion packages/format-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"dependencies": {
"@babel/runtime": "^7.3.1",
"@wordpress/components": "file:../components",
"@wordpress/dom": "file:../dom",
"@wordpress/editor": "file:../editor",
"@wordpress/element": "file:../element",
"@wordpress/i18n": "file:../i18n",
Expand Down
88 changes: 86 additions & 2 deletions packages/format-library/src/image/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
/**
* WordPress dependencies
*/
import { Path, SVG } from '@wordpress/components';
import { Path, SVG, TextControl, Popover, IconButton, PositionedAtSelection } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { insertObject } from '@wordpress/rich-text';
import { MediaUpload, RichTextInserterItem, MediaUploadCheck } from '@wordpress/editor';
import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes';

const ALLOWED_MEDIA_TYPES = [ 'image' ];

const name = 'core/image';

const stopKeyPropagation = ( event ) => event.stopPropagation();

export const image = {
name,
title: __( 'Image' ),
Expand All @@ -27,13 +30,46 @@ export const image = {
edit: class ImageEdit extends Component {
constructor() {
super( ...arguments );
this.onChange = this.onChange.bind( this );
this.onKeyDown = this.onKeyDown.bind( this );
this.openModal = this.openModal.bind( this );
this.closeModal = this.closeModal.bind( this );
this.state = {
modal: false,
};
}

static getDerivedStateFromProps( props, state ) {
const { activeAttributes: { style } } = props;

if ( style === state.previousStyle ) {
return null;
}

if ( ! style ) {
return {
width: undefined,
previousStyle: style,
};
}

return {
width: style.replace( /\D/g, '' ),
previousStyle: style,
};
}

onChange( width ) {
this.setState( { width } );
}

onKeyDown( event ) {
if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) {
// Stop the key event from propagating up to ObserveTyping.startTypingInTextField.
event.stopPropagation();
}
}

openModal() {
this.setState( { modal: true } );
}
Expand All @@ -43,7 +79,11 @@ export const image = {
}

render() {
const { value, onChange } = this.props;
const { value, onChange, isActive, activeAttributes } = this.props;
const { style } = activeAttributes;
// Rerender PositionedAtSelection when the selection changes or when
// the width changes.
const key = value.start + style;

return (
<MediaUploadCheck>
Expand Down Expand Up @@ -73,6 +113,50 @@ export const image = {
return null;
} }
/> }
{ isActive && <PositionedAtSelection key={ key }>
<Popover
position="bottom center"
focusOnMount={ false }
>
{ // 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-format-toolbar__image-container-content"
onKeyPress={ stopKeyPropagation }
onKeyDown={ this.onKeyDown }
onSubmit={ ( event ) => {
const newFormats = value.formats.slice( 0 );

newFormats[ value.start ] = [ {
type: name,
object: true,
attributes: {
...activeAttributes,
style: `width: ${ this.state.width }px;`,
},
} ];

onChange( {
...value,
formats: newFormats,
} );

event.preventDefault();
} }
>
<TextControl
className="editor-format-toolbar__image-container-value"
type="number"
label={ __( 'Width' ) }
value={ this.state.width }
min={ 1 }
onChange={ this.onChange }
/>
<IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" />
</form>
{ /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ }
</Popover>
</PositionedAtSelection> }
</MediaUploadCheck>
);
}
Expand Down
21 changes: 21 additions & 0 deletions packages/format-library/src/image/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.editor-format-toolbar__image-container-content {
display: flex;

.components-icon-button {
height: $icon-button-size + $grid-size + $grid-size;
align-self: flex-end;
}
}

.editor-format-toolbar__image-container-value {
margin: $grid-size - $border-width;
flex-grow: 1;
flex-shrink: 1;
white-space: nowrap;
min-width: 150px;
max-width: 500px;

&.components-base-control .components-base-control__field {
margin-bottom: 0;
}
}
2 changes: 1 addition & 1 deletion packages/format-library/src/link/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IconButton,
ToggleControl,
withSpokenMessages,
PositionedAtSelection,
} from '@wordpress/components';
import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes';
import { prependHTTP, safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
Expand All @@ -29,7 +30,6 @@ import { URLInput, URLPopover } from '@wordpress/editor';
/**
* Internal dependencies
*/
import PositionedAtSelection from './positioned-at-selection';
import { isValidHref } from './utils';

const stopKeyPropagation = ( event ) => event.stopPropagation();
Expand Down
1 change: 1 addition & 0 deletions packages/format-library/src/style.scss
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@import "./image/style.scss";
@import "./link/style.scss";

0 comments on commit dbb4b1d

Please sign in to comment.