Skip to content

Commit

Permalink
Add an alternative block appender when the container doesn't support …
Browse files Browse the repository at this point in the history
…the default block (#10136)

* Add an alternative block appender when the container doesn't support the default bloock

* Tweak the appender style

* Code style tweak

* Update appendder styles as suggested

* Adding an e2e test for the alternative appender

* Polish the visuals a little bit.

* chore: Tidy up code
  • Loading branch information
youknowriad authored Oct 4, 2018
1 parent 0332aa9 commit 590cf72
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 94 deletions.
81 changes: 81 additions & 0 deletions packages/editor/src/components/block-list-appender/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* External dependencies
*/
import { last } from 'lodash';

/**
* WordPress dependencies
*/
import { withSelect } from '@wordpress/data';
import { getDefaultBlockName } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { Button, Dashicon } from '@wordpress/components';

/**
* Internal dependencies
*/
import IgnoreNestedEvents from '../ignore-nested-events';
import DefaultBlockAppender from '../default-block-appender';
import Inserter from '../inserter';

function BlockListAppender( {
blockClientIds,
layout,
isGroupedByLayout,
rootClientId,
canInsertDefaultBlock,
isLocked,
} ) {
if ( isLocked ) {
return null;
}

const defaultLayout = isGroupedByLayout ? layout : undefined;

if ( canInsertDefaultBlock ) {
return (
<IgnoreNestedEvents childHandledEvents={ [ 'onFocus', 'onClick', 'onKeyDown' ] }>
<DefaultBlockAppender
rootClientId={ rootClientId }
lastBlockClientId={ last( blockClientIds ) }
layout={ defaultLayout }
/>
</IgnoreNestedEvents>
);
}

return (
<div className="block-list-appender">
<Inserter
rootClientId={ rootClientId }
layout={ defaultLayout }
renderToggle={ ( { onToggle, disabled, isOpen } ) => (
<Button
aria-label={ __( 'Add block' ) }
onClick={ onToggle }
className="block-list-appender__toggle"
aria-haspopup="true"
aria-expanded={ isOpen }
disabled={ disabled }
>
<Dashicon icon="insert" />
</Button>
) }
/>
</div>
);
}

export default withSelect( ( select, { rootClientId } ) => {
const {
getBlockOrder,
canInsertBlockType,
getTemplateLock,
} = select( 'core/editor' );

return {
isLocked: !! getTemplateLock( rootClientId ),
blockClientIds: getBlockOrder( rootClientId ),
canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ),
};
} )( BlockListAppender );
17 changes: 17 additions & 0 deletions packages/editor/src/components/block-list-appender/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.block-list-appender > .editor-inserter {
display: block;
}

.block-list-appender__toggle {
display: flex;
align-items: center;
justify-content: center;
padding: $grid-size-large;
outline: $border-width dashed $dark-gray-150;
width: 100%;
color: $dark-gray-500;

&:hover {
outline: $border-width dashed $dark-gray-500;
}
}
2 changes: 1 addition & 1 deletion packages/editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import BlockContextualToolbar from './block-contextual-toolbar';
import BlockMultiControls from './multi-controls';
import BlockMobileToolbar from './block-mobile-toolbar';
import BlockInsertionPoint from './insertion-point';
import IgnoreNestedEvents from './ignore-nested-events';
import IgnoreNestedEvents from '../ignore-nested-events';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import Inserter from '../inserter';
import withHoverAreas from './with-hover-areas';
Expand Down
23 changes: 7 additions & 16 deletions packages/editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
mapValues,
sortBy,
throttle,
last,
} from 'lodash';
import classnames from 'classnames';

Expand All @@ -17,15 +16,13 @@ import classnames from 'classnames';
*/
import { Component } from '@wordpress/element';
import { withSelect, withDispatch } from '@wordpress/data';
import { getDefaultBlockName } from '@wordpress/blocks';
import { compose } from '@wordpress/compose';

/**
* Internal dependencies
*/
import BlockListBlock from './block';
import IgnoreNestedEvents from './ignore-nested-events';
import DefaultBlockAppender from '../default-block-appender';
import BlockListAppender from '../block-list-appender';

class BlockList extends Component {
constructor( props ) {
Expand Down Expand Up @@ -194,7 +191,6 @@ class BlockList extends Component {
layout,
isGroupedByLayout,
rootClientId,
canInsertDefaultBlock,
isDraggable,
} = this.props;

Expand Down Expand Up @@ -224,15 +220,12 @@ class BlockList extends Component {
isDraggable={ isDraggable }
/>
) ) }
{ canInsertDefaultBlock && (
<IgnoreNestedEvents childHandledEvents={ [ 'onFocus', 'onClick', 'onKeyDown' ] }>
<DefaultBlockAppender
rootClientId={ rootClientId }
lastBlockClientId={ last( blockClientIds ) }
layout={ defaultLayout }
/>
</IgnoreNestedEvents>
) }

<BlockListAppender
rootClientId={ rootClientId }
layout={ layout }
isGroupedByLayout={ isGroupedByLayout }
/>
</div>
);
}
Expand All @@ -247,7 +240,6 @@ export default compose( [
getMultiSelectedBlocksStartClientId,
getMultiSelectedBlocksEndClientId,
getBlockSelectionStart,
canInsertBlockType,
} = select( 'core/editor' );
const { rootClientId } = ownProps;

Expand All @@ -258,7 +250,6 @@ export default compose( [
selectionStartClientId: getBlockSelectionStart(),
isSelectionEnabled: isSelectionEnabled(),
isMultiSelecting: isMultiSelecting(),
canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ),
};
} ),
withDispatch( ( dispatch ) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { mount } from 'enzyme';
/**
* Internal dependencies
*/
import IgnoreNestedEvents from '../ignore-nested-events';
import IgnoreNestedEvents from '../';

describe( 'IgnoreNestedEvents', () => {
it( 'passes props to its rendered div', () => {
Expand Down
45 changes: 25 additions & 20 deletions packages/editor/src/components/inserter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ import InserterMenu from './menu';

export { default as InserterResultsPortal } from './results-portal';

const defaultRenderToggle = ( { onToggle, disabled, isOpen } ) => (
<IconButton
icon="insert"
label={ __( 'Add block' ) }
onClick={ onToggle }
className="editor-inserter__toggle"
aria-haspopup="true"
aria-expanded={ isOpen }
disabled={ disabled }
/>
);

class Inserter extends Component {
constructor() {
super( ...arguments );
Expand All @@ -36,10 +48,10 @@ class Inserter extends Component {
items,
position,
title,
children,
onInsertBlock,
rootClientId,
disabled,
renderToggle = defaultRenderToggle,
} = this.props;

if ( items.length === 0 ) {
Expand All @@ -54,19 +66,7 @@ class Inserter extends Component {
onToggle={ this.onToggle }
expandOnMobile
headerTitle={ title }
renderToggle={ ( { onToggle, isOpen } ) => (
<IconButton
icon="insert"
label={ __( 'Add block' ) }
onClick={ onToggle }
className="editor-inserter__toggle"
aria-haspopup="true"
aria-expanded={ isOpen }
disabled={ disabled }
>
{ children }
</IconButton>
) }
renderToggle={ ( { onToggle, isOpen } ) => renderToggle( { onToggle, isOpen, disabled } ) }
renderContent={ ( { onClose } ) => {
const onSelect = ( item ) => {
onInsertBlock( item );
Expand All @@ -88,26 +88,31 @@ class Inserter extends Component {
}

export default compose( [
withSelect( ( select ) => {
withSelect( ( select, { rootClientId, layout } ) => {
const {
getEditedPostAttribute,
getBlockInsertionPoint,
getSelectedBlock,
getInserterItems,
getBlockOrder,
} = select( 'core/editor' );
const insertionPoint = getBlockInsertionPoint();
const { rootClientId } = insertionPoint;
const parentId = rootClientId || insertionPoint.rootClientId;
return {
title: getEditedPostAttribute( 'title' ),
insertionPoint,
insertionPoint: {
rootClientId: parentId,
layout: rootClientId ? layout : insertionPoint.layout,
index: rootClientId ? getBlockOrder( rootClientId ).length : insertionPoint.index,
},
selectedBlock: getSelectedBlock(),
items: getInserterItems( rootClientId ),
rootClientId,
items: getInserterItems( parentId ),
rootClientId: parentId,
};
} ),
withDispatch( ( dispatch, ownProps ) => ( {
onInsertBlock: ( item ) => {
const { insertionPoint, selectedBlock } = ownProps;
const { selectedBlock, insertionPoint } = ownProps;
const { index, rootClientId, layout } = insertionPoint;
const { name, initialAttributes } = item;
const insertedBlock = createBlock( name, { ...initialAttributes, layout } );
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "./components/block-icon/style.scss";
@import "./components/block-inspector/style.scss";
@import "./components/block-list/style.scss";
@import "./components/block-list-appender/style.scss";
@import "./components/block-compare/style.scss";
@import "./components/block-mover/style.scss";
@import "./components/block-preview/style.scss";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Correctly Renders Block Icons on Inserter and Inspector InnerBlocks Template Sync Ensures blocks without locking are kept intact even if they do not match the template 1`] = `
exports[`Container block without paragraph support ensures we can use the alternative block appender properly 1`] = `
"<!-- wp:test/container-without-paragraph -->
<!-- wp:image -->
<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- /wp:test/container-without-paragraph -->"
`;
exports[`InnerBlocks Template Sync Ensures blocks without locking are kept intact even if they do not match the template 1`] = `
"<!-- wp:test/test-inner-blocks-no-locking -->
<!-- wp:paragraph {\\"fontSize\\":\\"large\\"} -->
<p class=\\"has-large-font-size\\">Content…</p>
Expand All @@ -16,7 +24,7 @@ exports[`Correctly Renders Block Icons on Inserter and Inspector InnerBlocks Tem
<!-- /wp:test/test-inner-blocks-no-locking -->"
`;
exports[`Correctly Renders Block Icons on Inserter and Inspector InnerBlocks Template Sync Removes blocks that are not expected by the template if a lock all exists 1`] = `
exports[`InnerBlocks Template Sync Removes blocks that are not expected by the template if a lock all exists 1`] = `
"<!-- wp:test/test-inner-blocks-locking-all -->
<!-- wp:paragraph {\\"fontSize\\":\\"large\\"} -->
<p class=\\"has-large-font-size\\">Content…</p>
Expand Down
79 changes: 79 additions & 0 deletions test/e2e/specs/container-blocks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Internal dependencies
*/
import {
newPost,
insertBlock,
switchToEditor,
getEditedPostContent,
} from '../support/utils';
import { activatePlugin, deactivatePlugin } from '../support/plugins';

describe( 'InnerBlocks Template Sync', () => {
beforeAll( async () => {
await activatePlugin( 'gutenberg-test-innerblocks-templates' );
} );

beforeEach( async () => {
await newPost();
} );

afterAll( async () => {
await deactivatePlugin( 'gutenberg-test-innerblocks-templates' );
} );

const insertBlockAndAddParagraphInside = async ( blockName, blockSlug ) => {
const paragraphToAdd = `
<!-- wp:paragraph -->
<p>added paragraph</p>
<!-- /wp:paragraph -->
`;
await insertBlock( blockName );
await switchToEditor( 'Code' );
await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => {
const blockDelimiter = `<!-- /wp:${ _blockSlug } -->`;
element.value = element.value.replace( blockDelimiter, `${ _paragraph }${ blockDelimiter }` );
}, paragraphToAdd, blockSlug );
// Press "Enter" inside the Code Editor to fire the `onChange` event for the new value.
await page.click( '.editor-post-text-editor' );
await page.keyboard.press( 'Enter' );
await switchToEditor( 'Visual' );
};

it( 'Ensures blocks without locking are kept intact even if they do not match the template ', async () => {
await insertBlockAndAddParagraphInside( 'Test Inner Blocks no locking', 'test/test-inner-blocks-no-locking' );
expect( await getEditedPostContent() ).toMatchSnapshot();
} );

it( 'Removes blocks that are not expected by the template if a lock all exists ', async () => {
await insertBlockAndAddParagraphInside( 'Test InnerBlocks locking all', 'test/test-inner-blocks-locking-all' );
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
} );

describe( 'Container block without paragraph support', () => {
beforeAll( async () => {
await activatePlugin( 'gutenberg-test-container-block-without-paragraph' );
} );

beforeEach( async () => {
await newPost();
} );

afterAll( async () => {
await deactivatePlugin( 'gutenberg-test-container-block-without-paragraph' );
} );

it( 'ensures we can use the alternative block appender properly', async () => {
await insertBlock( 'Container without paragraph' );

// Open the specific appender used when there's no paragraph support.
await page.click( '.editor-inner-blocks .block-list-appender .block-list-appender__toggle' );

// Insert an image block.
await page.click( '.editor-inserter__results button[aria-label="Image"]' );

// Check the inserted content.
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
} );
Loading

0 comments on commit 590cf72

Please sign in to comment.