Skip to content

Commit

Permalink
Writing Flow: Redirect click below editor to last text field (#5541)
Browse files Browse the repository at this point in the history
* Revert "Show the block appender even if the last block is non empty paragraph"

This reverts commit 5bb5310.

* Writing Flow: Redirect click below editor to last text field
  • Loading branch information
aduth authored Mar 14, 2018
1 parent 955f7b7 commit 530a66e
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 26 deletions.
8 changes: 4 additions & 4 deletions edit-post/components/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ function VisualEditor( { hasFixedToolbar, isLargeViewport } ) {
<EditorGlobalKeyboardShortcuts />
<CopyHandler />
<MultiSelectScrollIntoView />
<ObserveTyping>
<WritingFlow>
<WritingFlow>
<ObserveTyping>
<PostTitle />
<BlockList
showContextualToolbar={ ! isLargeViewport || ! hasFixedToolbar }
Expand All @@ -39,8 +39,8 @@ function VisualEditor( { hasFixedToolbar, isLargeViewport } ) {
</Fragment>
) }
/>
</WritingFlow>
</ObserveTyping>
</ObserveTyping>
</WritingFlow>
</BlockSelectionClearer>
);
}
Expand Down
14 changes: 14 additions & 0 deletions edit-post/components/visual-editor/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
}
}

.edit-post-visual-editor .editor-writing-flow__click-redirect {
// Collapse to minimum height of 50px, to fully occupy editor bottom pad.
height: 50px;
width: $visual-editor-max-width;
// Offset for: Visual editor padding, block (default appender) margin.
margin: #{ -1 * $block-spacing } auto -50px;
}

.edit-post-visual-editor .editor-block-list__block {
margin-left: auto;
margin-right: auto;
Expand Down Expand Up @@ -89,6 +97,12 @@
margin-right: auto;
position: relative;

&[data-root-uid=""] .editor-default-block-appender__content:hover {
// Outline on root-level default block appender is redundant with the
// WritingFlow click redirector.
outline: 1px solid transparent;
}

@include break-small() {
padding: 0 $block-mover-padding-visible; // don't subtract 1px border because it's a border not an outline

Expand Down
21 changes: 16 additions & 5 deletions editor/components/default-block-appender/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
* External dependencies
*/
import { connect } from 'react-redux';
import { get } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { compose } from '@wordpress/element';
import { isUnmodifiedDefaultBlock } from '@wordpress/blocks';
import { getDefaultBlockName } from '@wordpress/blocks';
import { withContext } from '@wordpress/components';

/**
Expand All @@ -21,15 +22,25 @@ import { getBlock, getBlockCount } from '../../store/selectors';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import Inserter from '../inserter';

export function DefaultBlockAppender( { isLocked, isVisible, onAppend, showPrompt, placeholder, layout, rootUID } ) {
export function DefaultBlockAppender( {
isLocked,
isVisible,
onAppend,
showPrompt,
placeholder,
layout,
rootUID,
} ) {
if ( isLocked || ! isVisible ) {
return null;
}

const value = placeholder || __( 'Write your story' );

return (
<div className="editor-default-block-appender">
<div
data-root-uid={ rootUID || '' }
className="editor-default-block-appender">
<BlockDropZone />
<input
className="editor-default-block-appender__content"
Expand All @@ -50,10 +61,10 @@ export default compose(
( state, ownProps ) => {
const isEmpty = ! getBlockCount( state, ownProps.rootUID );
const lastBlock = getBlock( state, ownProps.lastBlockUID );
const isLastBlockEmptyDefault = lastBlock && isUnmodifiedDefaultBlock( lastBlock );
const isLastBlockDefault = get( lastBlock, 'name' ) === getDefaultBlockName();

return {
isVisible: isEmpty || ! isLastBlockEmptyDefault,
isVisible: isEmpty || ! isLastBlockDefault,
showPrompt: isEmpty,
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`DefaultBlockAppender should append a default block when input focused 1`] = `
<div
className="editor-default-block-appender"
data-root-uid=""
>
<Connect(WrappedComponent) />
<input
Expand Down Expand Up @@ -42,6 +43,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1
exports[`DefaultBlockAppender should match snapshot 1`] = `
<div
className="editor-default-block-appender"
data-root-uid=""
>
<Connect(WrappedComponent) />
<input
Expand All @@ -63,6 +65,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = `
exports[`DefaultBlockAppender should optionally show without prompt 1`] = `
<div
className="editor-default-block-appender"
data-root-uid=""
>
<Connect(WrappedComponent) />
<input
Expand Down
70 changes: 61 additions & 9 deletions editor/components/writing-flow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { connect } from 'react-redux';
import { find, reverse, get } from 'lodash';
import { overEvery, find, findLast, reverse, get } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -22,6 +22,7 @@ import {
/**
* Internal dependencies
*/
import './style.scss';
import {
getPreviousBlockUid,
getNextBlockUid,
Expand All @@ -38,18 +39,39 @@ import {
isInSameBlock,
} from '../../utils/dom';

/**
* Browser dependencies
*/

const { DOMRect } = window;

/**
* Module Constants
*/
const { UP, DOWN, LEFT, RIGHT } = keycodes;

/**
* Given an element, returns true if the element is a tabbable text field, or
* false otherwise.
*
* @param {Element} element Element to test.
*
* @return {boolean} Whether element is a tabbable text field.
*/
const isTabbableTextField = overEvery( [
isTextField,
focus.tabbable.isTabbableIndex,
] );

class WritingFlow extends Component {
constructor() {
super( ...arguments );

this.onKeyDown = this.onKeyDown.bind( this );
this.bindContainer = this.bindContainer.bind( this );
this.clearVerticalRect = this.clearVerticalRect.bind( this );
this.focusLastTextField = this.focusLastTextField.bind( this );

this.verticalRect = null;
}

Expand Down Expand Up @@ -194,28 +216,58 @@ class WritingFlow extends Component {
this.moveSelection( isReverse );
} else if ( isVertical && isVerticalEdge( target, isReverse, isShift ) ) {
const closestTabbable = this.getClosestTabbable( target, isReverse );
placeCaretAtVerticalEdge( closestTabbable, isReverse, this.verticalRect );
event.preventDefault();
if ( closestTabbable ) {
placeCaretAtVerticalEdge( closestTabbable, isReverse, this.verticalRect );
event.preventDefault();
}
} else if ( isHorizontal && isHorizontalEdge( target, isReverse, isShift ) ) {
const closestTabbable = this.getClosestTabbable( target, isReverse );
placeCaretAtHorizontalEdge( closestTabbable, isReverse );
event.preventDefault();
}
}

/**
* Shifts focus to the last tabbable text field — if one exists — at the
* given mouse event's X coordinate.
*
* @param {MouseEvent} event Mouse event to align caret X offset.
*/
focusLastTextField( event ) {
const focusableNodes = focus.focusable.find( this.container );
const target = findLast( focusableNodes, isTabbableTextField );
if ( ! target ) {
return;
}

// Emulate a rect at which caret should be placed using mouse event.
const rect = target.getBoundingClientRect();
const targetRect = new DOMRect( event.clientX, rect.top, 0, rect.height );

placeCaretAtVerticalEdge( target, false, targetRect );
}

render() {
const { children } = this.props;

// Disable reason: Wrapper itself is non-interactive, but must capture
// bubbling events from children to determine focus transition intents.
/* eslint-disable jsx-a11y/no-static-element-interactions */
return (
<div
ref={ this.bindContainer }
onKeyDown={ this.onKeyDown }
onMouseDown={ this.clearVerticalRect }
>
{ children }
<div className="editor-writing-flow">
<div
ref={ this.bindContainer }
onKeyDown={ this.onKeyDown }
onMouseDown={ this.clearVerticalRect }
>
{ children }
</div>
<div
aria-hidden
tabIndex={ -1 }
onClick={ this.focusLastTextField }
className="editor-writing-flow__click-redirect"
/>
</div>
);
/* eslint-disable jsx-a11y/no-static-element-interactions */
Expand Down
10 changes: 10 additions & 0 deletions editor/components/writing-flow/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.editor-writing-flow {
height: 100%;
display: flex;
flex-direction: column;
}

.editor-writing-flow__click-redirect {
flex-basis: 100%;
cursor: text;
}
12 changes: 4 additions & 8 deletions test/e2e/integration/002-adding-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@ describe( 'Adding blocks', () => {
} );

it( 'Should insert content using the placeholder and the regular inserter', () => {
const lastBlockSelector = '.editor-block-list__block-edit:last';
// Default block appender is provisional
cy.get( '.editor-default-block-appender' ).click();
cy.get( '.editor-post-title__input' ).click();
cy.get( '[data-type="core/paragraph"]' ).should( 'have.length', 0 );

// Using the placeholder
cy.get( '.editor-default-block-appender' ).click();
cy.focused().type( 'Paragraph block' );

// Default block appender is provisional
cy.get( lastBlockSelector ).then( ( firstBlock ) => {