diff --git a/packages/editor/src/components/block-list-appender/index.js b/packages/editor/src/components/block-list-appender/index.js
new file mode 100644
index 0000000000000..5ee01b1e7c138
--- /dev/null
+++ b/packages/editor/src/components/block-list-appender/index.js
@@ -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 (
+
+
+
+ );
+ }
+
+ return (
+
+ (
+
+
+
+ ) }
+ />
+
+ );
+}
+
+export default withSelect( ( select, { rootClientId } ) => {
+ const {
+ getBlockOrder,
+ canInsertBlockType,
+ getTemplateLock,
+ } = select( 'core/editor' );
+
+ return {
+ isLocked: !! getTemplateLock( rootClientId ),
+ blockClientIds: getBlockOrder( rootClientId ),
+ canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ),
+ };
+} )( BlockListAppender );
diff --git a/packages/editor/src/components/block-list-appender/style.scss b/packages/editor/src/components/block-list-appender/style.scss
new file mode 100644
index 0000000000000..a31989fcc5432
--- /dev/null
+++ b/packages/editor/src/components/block-list-appender/style.scss
@@ -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;
+ }
+}
diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js
index e417996f4e01a..04850e6f590c7 100644
--- a/packages/editor/src/components/block-list/block.js
+++ b/packages/editor/src/components/block-list/block.js
@@ -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';
diff --git a/packages/editor/src/components/block-list/index.js b/packages/editor/src/components/block-list/index.js
index b041dbeaedce7..093b93740b6e9 100644
--- a/packages/editor/src/components/block-list/index.js
+++ b/packages/editor/src/components/block-list/index.js
@@ -8,7 +8,6 @@ import {
mapValues,
sortBy,
throttle,
- last,
} from 'lodash';
import classnames from 'classnames';
@@ -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 ) {
@@ -194,7 +191,6 @@ class BlockList extends Component {
layout,
isGroupedByLayout,
rootClientId,
- canInsertDefaultBlock,
isDraggable,
} = this.props;
@@ -224,15 +220,12 @@ class BlockList extends Component {
isDraggable={ isDraggable }
/>
) ) }
- { canInsertDefaultBlock && (
-
-
-
- ) }
+
+
);
}
@@ -247,7 +240,6 @@ export default compose( [
getMultiSelectedBlocksStartClientId,
getMultiSelectedBlocksEndClientId,
getBlockSelectionStart,
- canInsertBlockType,
} = select( 'core/editor' );
const { rootClientId } = ownProps;
@@ -258,7 +250,6 @@ export default compose( [
selectionStartClientId: getBlockSelectionStart(),
isSelectionEnabled: isSelectionEnabled(),
isMultiSelecting: isMultiSelecting(),
- canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ),
};
} ),
withDispatch( ( dispatch ) => {
diff --git a/packages/editor/src/components/block-list/ignore-nested-events.js b/packages/editor/src/components/ignore-nested-events/index.js
similarity index 100%
rename from packages/editor/src/components/block-list/ignore-nested-events.js
rename to packages/editor/src/components/ignore-nested-events/index.js
diff --git a/packages/editor/src/components/block-list/test/ignore-nested-events.js b/packages/editor/src/components/ignore-nested-events/test/index.js
similarity index 97%
rename from packages/editor/src/components/block-list/test/ignore-nested-events.js
rename to packages/editor/src/components/ignore-nested-events/test/index.js
index 48559baf29909..533fd26d67c8f 100644
--- a/packages/editor/src/components/block-list/test/ignore-nested-events.js
+++ b/packages/editor/src/components/ignore-nested-events/test/index.js
@@ -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', () => {
diff --git a/packages/editor/src/components/inserter/index.js b/packages/editor/src/components/inserter/index.js
index be1aada97c5e4..73a971a7de5c7 100644
--- a/packages/editor/src/components/inserter/index.js
+++ b/packages/editor/src/components/inserter/index.js
@@ -15,6 +15,18 @@ import InserterMenu from './menu';
export { default as InserterResultsPortal } from './results-portal';
+const defaultRenderToggle = ( { onToggle, disabled, isOpen } ) => (
+
+);
+
class Inserter extends Component {
constructor() {
super( ...arguments );
@@ -36,10 +48,10 @@ class Inserter extends Component {
items,
position,
title,
- children,
onInsertBlock,
rootClientId,
disabled,
+ renderToggle = defaultRenderToggle,
} = this.props;
if ( items.length === 0 ) {
@@ -54,19 +66,7 @@ class Inserter extends Component {
onToggle={ this.onToggle }
expandOnMobile
headerTitle={ title }
- renderToggle={ ( { onToggle, isOpen } ) => (
-
- { children }
-
- ) }
+ renderToggle={ ( { onToggle, isOpen } ) => renderToggle( { onToggle, isOpen, disabled } ) }
renderContent={ ( { onClose } ) => {
const onSelect = ( item ) => {
onInsertBlock( item );
@@ -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 } );
diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss
index f6e6b8e586e66..f49b64c3a3afb 100644
--- a/packages/editor/src/style.scss
+++ b/packages/editor/src/style.scss
@@ -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";
diff --git a/test/e2e/specs/__snapshots__/inner-blocks-templates.test.js.snap b/test/e2e/specs/__snapshots__/container-blocks.test.js.snap
similarity index 52%
rename from test/e2e/specs/__snapshots__/inner-blocks-templates.test.js.snap
rename to test/e2e/specs/__snapshots__/container-blocks.test.js.snap
index 82e5c8a59df17..d48bb64ef8589 100644
--- a/test/e2e/specs/__snapshots__/inner-blocks-templates.test.js.snap
+++ b/test/e2e/specs/__snapshots__/container-blocks.test.js.snap
@@ -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`] = `
+"
+
+
+
+"
+`;
+
+exports[`InnerBlocks Template Sync Ensures blocks without locking are kept intact even if they do not match the template 1`] = `
"
Content…
@@ -16,7 +24,7 @@ exports[`Correctly Renders Block Icons on Inserter and Inspector InnerBlocks Tem
"
`;
-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`] = `
"
Content…
diff --git a/test/e2e/specs/container-blocks.test.js b/test/e2e/specs/container-blocks.test.js
new file mode 100644
index 0000000000000..301f6a8cf12c9
--- /dev/null
+++ b/test/e2e/specs/container-blocks.test.js
@@ -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 = `
+
+ added paragraph
+
+ `;
+ await insertBlock( blockName );
+ await switchToEditor( 'Code' );
+ await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => {
+ const blockDelimiter = ``;
+ 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();
+ } );
+} );
diff --git a/test/e2e/specs/inner-blocks-templates.test.js b/test/e2e/specs/inner-blocks-templates.test.js
deleted file mode 100644
index f2264bef1c70a..0000000000000
--- a/test/e2e/specs/inner-blocks-templates.test.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Internal dependencies
- */
-import {
- newPost,
- insertBlock,
- switchToEditor,
- getEditedPostContent,
-} from '../support/utils';
-import { activatePlugin, deactivatePlugin } from '../support/plugins';
-
-describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => {
- beforeAll( async () => {
- await activatePlugin( 'gutenberg-test-innerblocks-templates' );
- } );
-
- beforeEach( async () => {
- await newPost();
- } );
-
- afterAll( async () => {
- await deactivatePlugin( 'gutenberg-test-innerblocks-templates' );
- } );
-
- describe( 'InnerBlocks Template Sync', () => {
- const insertBlockAndAddParagraphInside = async ( blockName, blockSlug ) => {
- const paragraphToAdd = `
-
- added paragraph
-
- `;
- await insertBlock( blockName );
- await switchToEditor( 'Code' );
- await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => {
- const blockDelimiter = ``;
- element.value = element.value.replace( blockDelimiter, _paragraph + blockDelimiter );
- }, paragraphToAdd, blockSlug );
- // press enter inside the code editor so the onChange events for the new value trigger
- 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();
- } );
- } );
-} );
diff --git a/test/e2e/test-plugins/container-without-paragraph.php b/test/e2e/test-plugins/container-without-paragraph.php
new file mode 100644
index 0000000000000..2d7a20c002605
--- /dev/null
+++ b/test/e2e/test-plugins/container-without-paragraph.php
@@ -0,0 +1,19 @@
+