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 @@ +