Skip to content

Commit

Permalink
Writing Flow: 'Down' key at bottom creates new paragraph (#3973)
Browse files Browse the repository at this point in the history
* Writing Flow: 'Down' key at bottom creates new paragraph

* Refactor by adding appendDefaultBlock action

* WritingFlow: 'down' appends new block after any non-empty block

Fixes the following issue introduced by the parent commits:

1. Add a Quote block.
2. Fill out quote but not citation.
3. Keeping pressing ↓, hoping to move into an empty appendage, but
   nothing happens.

Also correctly account for block-level-only tabbables, e.g. Image.

* BlockListBlock: Expose className constant
  • Loading branch information
mcsf committed Dec 14, 2017
1 parent d94d742 commit 29b757d
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 40 deletions.
6 changes: 6 additions & 0 deletions editor/actions.js
Expand Up @@ -593,3 +593,9 @@ export function convertBlockToReusable( uid ) {
uid,
};
}

export function appendDefaultBlock() {
return {
type: 'APPEND_DEFAULT_BLOCK',
};
}
6 changes: 4 additions & 2 deletions editor/components/block-list/block.js
Expand Up @@ -90,7 +90,7 @@ function getScrollContainer( node ) {
return getScrollContainer( node.parentNode );
}

class BlockListBlock extends Component {
export class BlockListBlock extends Component {
constructor() {
super( ...arguments );

Expand Down Expand Up @@ -409,7 +409,7 @@ class BlockListBlock extends Component {
onMouseDown={ this.onPointerDown }
onKeyDown={ this.onKeyDown }
onFocus={ this.onFocus }
className="editor-block-list__block-edit"
className={ BlockListBlock.className }
tabIndex="0"
aria-label={ blockLabel }
>
Expand Down Expand Up @@ -532,6 +532,8 @@ const mapDispatchToProps = ( dispatch, ownProps ) => ( {
},
} );

BlockListBlock.className = 'editor-block-list__block-edit';

export default compose(
connect( mapStateToProps, mapDispatchToProps ),
withContext( 'editor' )( ( settings ) => {
Expand Down
21 changes: 5 additions & 16 deletions editor/components/default-block-appender/index.js
Expand Up @@ -9,27 +9,16 @@ import 'element-closest';
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { getDefaultBlockName, createBlock } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import './style.scss';
import BlockDropZone from '../block-drop-zone';
import { insertBlock } from '../../actions';
import { appendDefaultBlock } from '../../actions';
import { getBlockCount } from '../../selectors';

export class DefaultBlockAppender extends Component {
constructor( props ) {
super( props );
this.appendDefaultBlock = this.appendDefaultBlock.bind( this );
}

appendDefaultBlock() {
const newBlock = createBlock( getDefaultBlockName() );
this.props.onInsertBlock( newBlock );
}

render() {
const { count } = this.props;
if ( count !== 0 ) {
Expand All @@ -43,9 +32,9 @@ export class DefaultBlockAppender extends Component {
className="editor-default-block-appender__content"
type="text"
readOnly
onFocus={ this.appendDefaultBlock }
onClick={ this.appendDefaultBlock }
onKeyDown={ this.appendDefaultBlock }
onFocus={ this.props.appendDefaultBlock }
onClick={ this.props.appendDefaultBlock }
onKeyDown={ this.props.appendDefaultBlock }
value={ __( 'Write your story' ) }
/>
</div>
Expand All @@ -57,5 +46,5 @@ export default connect(
( state ) => ( {
count: getBlockCount( state ),
} ),
{ onInsertBlock: insertBlock }
{ appendDefaultBlock }
)( DefaultBlockAppender );
26 changes: 11 additions & 15 deletions editor/components/default-block-appender/test/index.js
Expand Up @@ -9,39 +9,35 @@ import { shallow } from 'enzyme';
import { DefaultBlockAppender } from '../';

describe( 'DefaultBlockAppender', () => {
const expectInsertBlockCalled = ( insertBlock ) => {
expect( insertBlock ).toHaveBeenCalledTimes( 1 );
expect( insertBlock ).toHaveBeenCalledWith( expect.objectContaining( {
attributes: {},
isValid: true,
name: 'core/paragraph',
uid: expect.any( String ),
} ) );
const expectAppendDefaultBlockCalled = ( appendDefaultBlock ) => {
expect( appendDefaultBlock ).toHaveBeenCalledTimes( 1 );
expect( appendDefaultBlock ).toHaveBeenCalledWith();
};

describe( 'no block present', () => {
it( 'should match snapshot', () => {
const wrapper = shallow( <DefaultBlockAppender count={ 0 } /> );
const appendDefaultBlock = jest.fn();
const wrapper = shallow( <DefaultBlockAppender count={ 0 } appendDefaultBlock={ appendDefaultBlock } /> );

expect( wrapper ).toMatchSnapshot();
} );

it( 'should append a default block when input clicked', () => {
const insertBlock = jest.fn();
const wrapper = shallow( <DefaultBlockAppender count={ 0 } onInsertBlock={ insertBlock } /> );
const appendDefaultBlock = jest.fn();
const wrapper = shallow( <DefaultBlockAppender count={ 0 } appendDefaultBlock={ appendDefaultBlock } /> );

wrapper.find( 'input.editor-default-block-appender__content' ).simulate( 'click' );

expectInsertBlockCalled( insertBlock );
expectAppendDefaultBlockCalled( appendDefaultBlock );
} );

it( 'should append a default block when input focused', () => {
const insertBlock = jest.fn();
const wrapper = shallow( <DefaultBlockAppender count={ 0 } onInsertBlock={ insertBlock } /> );
const appendDefaultBlock = jest.fn();
const wrapper = shallow( <DefaultBlockAppender count={ 0 } appendDefaultBlock={ appendDefaultBlock } /> );

wrapper.find( 'input.editor-default-block-appender__content' ).simulate( 'focus' );

expectInsertBlockCalled( insertBlock );
expectAppendDefaultBlockCalled( appendDefaultBlock );
} );
} );

Expand Down
56 changes: 49 additions & 7 deletions editor/components/writing-flow/index.js
Expand Up @@ -3,7 +3,7 @@
*/
import { connect } from 'react-redux';
import 'element-closest';
import { find, reverse } from 'lodash';
import { find, last, reverse } from 'lodash';
/**
* WordPress dependencies
*/
Expand All @@ -13,6 +13,7 @@ import { keycodes, focus } from '@wordpress/utils';
/**
* Internal dependencies
*/
import { BlockListBlock } from '../block-list/block';
import {
computeCaretRect,
isHorizontalEdge,
Expand All @@ -27,13 +28,17 @@ import {
getMultiSelectedBlocks,
getSelectedBlock,
} from '../../selectors';
import { multiSelect } from '../../actions';
import { multiSelect, appendDefaultBlock } from '../../actions';

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

function isElementNonEmpty( el ) {
return !! el.innerText.trim();
}

class WritingFlow extends Component {
constructor() {
super( ...arguments );
Expand Down Expand Up @@ -98,6 +103,37 @@ class WritingFlow extends Component {
} );
}

isInLastNonEmptyBlock( target ) {
const tabbables = this.getVisibleTabbables();

// Find last tabbable, compare with target
const lastTabbable = last( tabbables );
if ( ! lastTabbable || ! lastTabbable.contains( target ) ) {
return false;
}

// Find block-level ancestor of said last tabbable
const blockEl = lastTabbable.closest( '.' + BlockListBlock.className );
const blockIndex = tabbables.indexOf( blockEl );

// Unexpected, so we'll leave quietly.
if ( blockIndex === -1 ) {
return false;
}

// Maybe there are no descendants, and the target is the block itself?
if ( lastTabbable === blockEl ) {
return isElementNonEmpty( blockEl );
}

// Otherwise, find the descendants of the ancestor, i.e. the target and
// its siblings, and check them instead.
return tabbables
.slice( blockIndex + 1 )
.some( ( el ) =>
blockEl.contains( el ) && isElementNonEmpty( el ) );
}

expandSelection( blocks, currentStartUid, currentEndUid, delta ) {
const lastIndex = blocks.indexOf( currentEndUid );
const nextIndex = Math.max( 0, Math.min( blocks.length - 1, lastIndex + delta ) );
Expand Down Expand Up @@ -150,6 +186,13 @@ class WritingFlow extends Component {
placeCaretAtHorizontalEdge( closestTabbable, isReverse );
event.preventDefault();
}

if ( isDown && ! isShift && ! hasMultiSelection &&
this.isInLastNonEmptyBlock( target ) &&
isVerticalEdge( target, false, false )
) {
this.props.onBottomReached();
}
}

render() {
Expand Down Expand Up @@ -179,9 +222,8 @@ export default connect(
hasMultiSelection: getMultiSelectedBlocks( state ).length > 1,
selectedBlock: getSelectedBlock( state ),
} ),
( dispatch ) => ( {
onMultiSelect( start, end ) {
dispatch( multiSelect( start, end ) );
},
} )
{
onMultiSelect: multiSelect,
onBottomReached: appendDefaultBlock,
}
)( WritingFlow );
5 changes: 5 additions & 0 deletions editor/effects.js
Expand Up @@ -14,6 +14,7 @@ import {
createBlock,
serialize,
createReusableBlock,
getDefaultBlockName,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';

Expand All @@ -35,6 +36,7 @@ import {
requestMetaBoxUpdates,
updateReusableBlock,
saveReusableBlock,
insertBlock,
} from './actions';
import {
getCurrentPost,
Expand Down Expand Up @@ -388,4 +390,7 @@ export default {
dispatch( saveReusableBlock( reusableBlock.id ) );
dispatch( replaceBlocks( [ oldBlock.uid ], [ newBlock ] ) );
},
APPEND_DEFAULT_BLOCK() {
return insertBlock( createBlock( getDefaultBlockName() ) );
},
};

0 comments on commit 29b757d

Please sign in to comment.