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

Replace the side inserter by an inserter with shortcuts on empty paragraphs #4953

Merged
merged 8 commits into from
Feb 13, 2018
14 changes: 10 additions & 4 deletions blocks/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class RichText extends Component {
this.getSettings = this.getSettings.bind( this );
this.onSetup = this.onSetup.bind( this );
this.onChange = this.onChange.bind( this );
this.throttledOnChange = throttle( this.onChange.bind( this ), 500 );
this.throttledOnChange = throttle( this.onChange.bind( this, false ), 500, { leading: true } );
this.onNewBlock = this.onNewBlock.bind( this );
this.onNodeChange = this.onNodeChange.bind( this );
this.onKeyDown = this.onKeyDown.bind( this );
Expand Down Expand Up @@ -371,12 +371,15 @@ export class RichText extends Component {

/**
* Handles any case where the content of the tinyMCE instance has changed.
*
* @param {boolean} checkIfDirty Check whether the editor is dirty before calling onChange.
*/
onChange() {
if ( ! this.editor.isDirty() ) {
onChange( checkIfDirty = true ) {
if ( checkIfDirty && ! this.editor.isDirty() ) {
return;
}
this.savedContent = this.state.empty ? [] : this.getContent();
const isEmpty = tinymce.DOM.isEmpty( this.editor.getBody() );
this.savedContent = isEmpty ? [] : this.getContent();
this.props.onChange( this.savedContent );
this.editor.save();
}
Expand Down Expand Up @@ -506,6 +509,8 @@ export class RichText extends Component {
return;
}

this.onChange( false );

const forward = event.keyCode === DELETE;

if ( this.props.onMerge ) {
Expand Down Expand Up @@ -718,6 +723,7 @@ export class RichText extends Component {
this.updateContent();
}
}

componentWillReceiveProps( nextProps ) {
if ( 'development' === process.env.NODE_ENV ) {
if ( ! isEqual( this.props.formatters, nextProps.formatters ) ) {
Expand Down
62 changes: 36 additions & 26 deletions editor/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getBlockType,
getSaveElement,
isReusableBlock,
isUnmodifiedDefaultBlock,
} from '@wordpress/blocks';
import { withFilters, withContext, withAPIData } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
Expand All @@ -25,7 +26,6 @@ import { __, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import BlockMover from '../block-mover';
import VisualEditorInserter from '../inserter';
import BlockDropZone from '../block-drop-zone';
import BlockSettingsMenu from '../block-settings-menu';
import InvalidBlockWarning from './invalid-block-warning';
Expand All @@ -37,6 +37,7 @@ import BlockMultiControls from './multi-controls';
import BlockMobileToolbar from './block-mobile-toolbar';
import BlockInsertionPoint from './insertion-point';
import IgnoreNestedEvents from './ignore-nested-events';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import { createInnerBlockList } from './utils';
import {
clearSelectedBlock,
Expand Down Expand Up @@ -254,9 +255,9 @@ export class BlockListBlock extends Component {
* @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter
*/
maybeHover() {
const { isHovered, isSelected, isMultiSelected, onHover } = this.props;
const { isHovered, isMultiSelected, onHover } = this.props;

if ( isHovered || isSelected || isMultiSelected || this.hadTouchStart ) {
if ( isHovered || isMultiSelected || this.hadTouchStart ) {
return;
}

Expand Down Expand Up @@ -458,6 +459,10 @@ export class BlockListBlock extends Component {
rootUID,
layout,
renderBlockMenu,
isHovered,
isSelected,
isMultiSelected,
isFirstMultiSelected,
} = this.props;
const { name: blockName, isValid } = block;
const blockType = getBlockType( blockName );
Expand All @@ -466,16 +471,21 @@ export class BlockListBlock extends Component {
// The block as rendered in the editor is composed of general block UI
// (mover, toolbar, wrapper) and the display of the block content.

// Generate the wrapper class names handling the different states of the block.
const { isHovered, isSelected, isMultiSelected, isFirstMultiSelected } = this.props;

// If the block is selected and we're typing we hide the sidebar
// unless the selection is not collapsed.
const showUI = isSelected && ( ! this.props.isTyping || ! this.state.isSelectionCollapsed );
// If the block is selected and we're typing the block should not appear as selected unless the selection is not collapsed.
// Empty paragraph blocks should always show up as unselected.
const isEmptyDefaultBlock = isUnmodifiedDefaultBlock( block );
const showSideInserter = ( isSelected || isHovered ) && isEmptyDefaultBlock;
const shouldAppearSelected = ! showSideInserter && isSelected && ( ! this.props.isTyping || ! this.state.isSelectionCollapsed );
const shouldShowMovers = shouldAppearSelected || isHovered;
const shouldShowSettingsMenu = shouldShowMovers;
const shouldShowContextualToolbar = shouldAppearSelected && isValid && showContextualToolbar;
const shouldShowMobileToolbar = shouldAppearSelected;
const { error } = this.state;

// Generate the wrapper class names handling the different states of the block.
const wrapperClassName = classnames( 'editor-block-list__block', {
'has-warning': ! isValid || !! error,
'is-selected': showUI,
'is-selected': shouldAppearSelected,
'is-multi-selected': isMultiSelected,
'is-hovered': isHovered,
'is-reusable': isReusableBlock( blockType ),
Expand Down Expand Up @@ -521,14 +531,7 @@ export class BlockListBlock extends Component {
rootUID={ rootUID }
layout={ layout }
/>
{ ( showUI || isHovered ) && (
<VisualEditorInserter
onToggle={ this.selectOnOpen }
rootUID={ rootUID }
layout={ layout }
/>
) }
{ ( showUI || isHovered ) && (
{ shouldShowMovers && (
<BlockMover
uids={ [ block.uid ] }
rootUID={ rootUID }
Expand All @@ -537,13 +540,13 @@ export class BlockListBlock extends Component {
isLast={ isLast }
/>
) }
{ ( showUI || isHovered ) && (
{ shouldShowSettingsMenu && (
<BlockSettingsMenu
uids={ [ block.uid ] }
renderBlockMenu={ renderBlockMenu }
/>
) }
{ showUI && isValid && showContextualToolbar && <BlockContextualToolbar /> }
{ shouldShowContextualToolbar && <BlockContextualToolbar /> }
{ isFirstMultiSelected && <BlockMultiControls rootUID={ rootUID } /> }
<IgnoreNestedEvents
ref={ this.bindBlockNode }
Expand Down Expand Up @@ -585,14 +588,21 @@ export class BlockListBlock extends Component {
/>,
] }
</BlockCrashBoundary>
{ showUI && <BlockMobileToolbar uid={ block.uid } renderBlockMenu={ renderBlockMenu } /> }
{ shouldShowMobileToolbar && <BlockMobileToolbar uid={ block.uid } renderBlockMenu={ renderBlockMenu } /> }
</IgnoreNestedEvents>
{ !! error && <BlockCrashWarning /> }
<BlockInsertionPoint
uid={ block.uid }
rootUID={ rootUID }
layout={ layout }
/>
{ ! showSideInserter && (
<BlockInsertionPoint
uid={ block.uid }
rootUID={ rootUID }
layout={ layout }
/>
) }
{ showSideInserter && (
<div className="editor-block-list__side-inserter">
<InserterWithShortcuts uid={ block.uid } layout={ layout } onToggle={ this.selectOnOpen } />
</div>
) }
</IgnoreNestedEvents>
);
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
Expand Down
45 changes: 10 additions & 35 deletions editor/components/block-list/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -275,41 +275,6 @@
}
}

// Left side inserter
> .editor-inserter {
position: absolute;
left: 2px;
top: 16px;

// Mobile
display: none;
@include break-small {
display: block;
}

.editor-inserter__toggle {
width: $icon-button-size-small;
padding: 2px;

// Adjust inserter design
box-shadow: inset 0 0 0 1px $light-gray-500;
border-radius: 50%;

// Hide the outer ring on the inserter, to visually lighten it
&:before {
content: '';
position: absolute;
top: 2px;
right: 2px;
bottom: 2px;
left: 2px;
display: block;
border: 4px solid $white;
border-radius: 50%;
}
}
}

// Left and right side UI
> .editor-block-settings-menu,
> .editor-block-mover {
Expand Down Expand Up @@ -509,3 +474,13 @@ $sticky-bottom-offset: 20px;
margin-top: - $sticky-bottom-offset - 1px;
padding-top: 2px;
}

.editor-block-list__side-inserter {
position: absolute;
top: 10px;
right: 10px;

@include break-small {
right: $block-mover-padding-visible + 10px;
}
}
76 changes: 76 additions & 0 deletions editor/components/inserter-with-shortcuts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import { filter, isEmpty } from 'lodash';

/**
* WordPress dependencies
*/
import { BlockIcon, createBlock, getDefaultBlockName } from '@wordpress/blocks';
import { compose } from '@wordpress/element';
import { IconButton, withContext } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import './style.scss';
import Inserter from '../inserter';
import { getFrequentInserterItems } from '../../store/selectors';
import { replaceBlocks } from '../../store/actions';

function InserterWithShortcuts( { items, isLocked, onToggle, onInsert } ) {
if ( isLocked ) {
return null;
}

const itemsWithoutDefaultBlock = filter( items, ( item ) =>
item.name !== getDefaultBlockName() || ! isEmpty( item.initialAttributes )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ! isEmpty( item.initialAttributes ) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so that shared paragraph blocks don't get filtered out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a casual observer, I'd have no hints from the context available to reach this conclusion. Even now, I'm not clear what it is about shared blocks that causes us to filter them.

I'd suggest some combination of code comment and/or explaining variable.

).slice( 0, 2 );

return (
<div className="editor-inserter-with-shortcuts">
<Inserter
position="top left"
onToggle={ onToggle }
/>

{ itemsWithoutDefaultBlock.map( ( item ) => (
<IconButton
key={ item.id }
className="editor-inserter-with-shortcuts__block"
onClick={ () => onInsert( item ) }
label={ sprintf( __( 'Add %s' ), item.title ) }
icon={ (
<span className="editor-inserter-with-shortcuts__block-icon">
<BlockIcon icon={ item.icon } />
</span>
) }
/>
) ) }
</div>
);
}

export default compose(
withContext( 'editor' )( ( settings ) => {
const { templateLock, blockTypes } = settings;

return {
isLocked: !! templateLock,
enabledBlockTypes: blockTypes,
};
} ),
connect(
( state, { enabledBlockTypes } ) => ( {
items: getFrequentInserterItems( state, enabledBlockTypes, 3 ),
} ),
( dispatch, { uid, layout } ) => ( {
onInsert( { name, initialAttributes } ) {
const block = createBlock( name, { ...initialAttributes, layout } );
return dispatch( replaceBlocks( uid, block ) );
},
} )
),
)( InserterWithShortcuts );
16 changes: 16 additions & 0 deletions editor/components/inserter-with-shortcuts/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.editor-inserter-with-shortcuts {
display: flex;
align-items: center;
flex-direction: row-reverse;

.components-icon-button {
border-radius: $button-style__radius-roundrect;
}
}

.editor-inserter-with-shortcuts__block {
margin-right: 5px;
width: 36px;
height: 36px;
padding-top: 8px;
}
1 change: 1 addition & 0 deletions editor/store/defaults.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const PREFERENCES_DEFAULTS = {
recentInserts: [],
insertUsage: {},
};
11 changes: 11 additions & 0 deletions editor/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
mapValues,
findIndex,
reject,
omitBy,
} from 'lodash';

/**
Expand Down Expand Up @@ -614,9 +615,11 @@ export function preferences( state = PREFERENCES_DEFAULTS, action ) {
switch ( action.type ) {
case 'INSERT_BLOCKS':
return action.blocks.reduce( ( prevState, block ) => {
let id = block.name;
const insert = { name: block.name };
if ( isReusableBlock( block ) ) {
insert.ref = block.attributes.ref;
id += '/' + block.attributes.ref;
}

const isSameAsInsert = ( { name, ref } ) => name === insert.name && ref === insert.ref;
Expand All @@ -627,12 +630,20 @@ export function preferences( state = PREFERENCES_DEFAULTS, action ) {
insert,
...reject( prevState.recentInserts, isSameAsInsert ),
],
insertUsage: {
...prevState.insertUsage,
[ id ]: {
count: prevState.insertUsage[ id ] ? prevState.insertUsage[ id ].count + 1 : 1,
insert,
},
},
};
}, state );

case 'REMOVE_REUSABLE_BLOCK':
return {
...state,
insertUsage: omitBy( state.insertUsage, ( { insert } ) => insert.ref === action.id ),
recentInserts: reject( state.recentInserts, insert => insert.ref === action.id ),
};
}
Expand Down
Loading