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

Writing Flow: Redirect click below editor to last text field #5541

Merged
merged 2 commits into from
Mar 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ) => {
cy.get( '.editor-default-block-appender' ).click();
cy.get( '.editor-post-title__input' ).click();
cy.get( lastBlockSelector ).should( 'have.text', firstBlock.text() );
} );

// Using the slash command
// TODO: Test omitted because Cypress doesn't update the selection
// object properly, so the slash inserter is not showing up.
Expand Down