diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index c32d929392d8d..70456ac213fc3 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -19,6 +19,8 @@ import { serializeRawBlock, switchToBlockType, store as blocksStore, + getDefaultBlockName, + isUnmodifiedBlock, } from '@wordpress/blocks'; import { withFilters } from '@wordpress/components'; import { @@ -311,7 +313,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { __unstableMarkLastChangeAsPersistent, moveBlocksToPosition, removeBlock, - selectBlock, } = dispatch( blockEditorStore ); // Do not add new properties here, use `useDispatch` instead to avoid @@ -348,8 +349,71 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { getBlockAttributes, getBlockName, getBlockOrder, + getBlockIndex, + getBlockRootClientId, + canInsertBlockType, } = registry.select( blockEditorStore ); + /** + * Moves the block with clientId up one level. If the block type + * cannot be inserted at the new location, it will be attempted to + * convert to the default block type. + * + * @param {string} _clientId The block to move. + * @param {boolean} changeSelection Whether to change the selection + * to the moved block. + */ + function moveFirstItemUp( _clientId, changeSelection = true ) { + const targetRootClientId = getBlockRootClientId( _clientId ); + const blockOrder = getBlockOrder( _clientId ); + const [ firstClientId ] = blockOrder; + + if ( + blockOrder.length === 1 && + isUnmodifiedBlock( getBlock( firstClientId ) ) + ) { + removeBlock( _clientId ); + } else { + if ( + canInsertBlockType( + getBlockName( firstClientId ), + targetRootClientId + ) + ) { + moveBlocksToPosition( + [ firstClientId ], + _clientId, + targetRootClientId, + getBlockIndex( _clientId ) + ); + } else { + const replacement = switchToBlockType( + getBlock( firstClientId ), + getDefaultBlockName() + ); + + if ( replacement && replacement.length ) { + registry.batch( () => { + insertBlocks( + replacement, + getBlockIndex( _clientId ), + targetRootClientId, + changeSelection + ); + removeBlock( firstClientId, false ); + } ); + } + } + + if ( + ! getBlockOrder( _clientId ).length && + isUnmodifiedBlock( getBlock( _clientId ) ) + ) { + removeBlock( _clientId, false ); + } + } + } + // For `Delete` or forward merge, we should do the exact same thing // as `Backspace`, but from the other block. if ( forward ) { @@ -400,15 +464,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { return; } - // Check if it's possibile to "unwrap" the following block - // before trying to merge. - const replacement = switchToBlockType( - getBlock( nextBlockClientId ), - '*' - ); - - if ( replacement && replacement.length ) { - replaceBlocks( nextBlockClientId, replacement ); + if ( getBlockOrder( nextBlockClientId ).length ) { + moveFirstItemUp( nextBlockClientId, false ); } else { mergeBlocks( clientId, nextBlockClientId ); } @@ -453,18 +510,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { } } - // Attempt to "unwrap" the block contents when there's no - // preceding block to merge with. - const replacement = switchToBlockType( - getBlock( rootClientId ), - '*' - ); - if ( replacement && replacement.length ) { - registry.batch( () => { - replaceBlocks( rootClientId, replacement ); - selectBlock( replacement[ 0 ].clientId, 0 ); - } ); - } + moveFirstItemUp( rootClientId ); } } }, diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 91817675d6eed..061ff216a1383 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -20,6 +20,8 @@ import { getBlockType, __experimentalGetAccessibleBlockLabel as getAccessibleBlockLabel, switchToBlockType, + getDefaultBlockName, + isUnmodifiedBlock, } from '@wordpress/blocks'; import { useSetting } from '@wordpress/block-editor'; @@ -436,8 +438,72 @@ export default compose( [ getBlockAttributes, getBlockName, getBlockOrder, + getBlockIndex, + getBlockRootClientId, + canInsertBlockType, } = registry.select( blockEditorStore ); + /** + * Moves the block with clientId up one level. If the block type + * cannot be inserted at the new location, it will be attempted to + * convert to the default block type. + * + * @param {string} _clientId The block to move. + * @param {boolean} changeSelection Whether to change the selection + * to the moved block. + */ + function moveFirstItemUp( _clientId, changeSelection = true ) { + const targetRootClientId = + getBlockRootClientId( _clientId ); + const blockOrder = getBlockOrder( _clientId ); + const [ firstClientId ] = blockOrder; + + if ( + blockOrder.length === 1 && + isUnmodifiedBlock( getBlock( firstClientId ) ) + ) { + removeBlock( _clientId ); + } else { + if ( + canInsertBlockType( + getBlockName( firstClientId ), + targetRootClientId + ) + ) { + moveBlocksToPosition( + [ firstClientId ], + _clientId, + targetRootClientId, + getBlockIndex( _clientId ) + ); + } else { + const replacement = switchToBlockType( + getBlock( firstClientId ), + getDefaultBlockName() + ); + + if ( replacement && replacement.length ) { + registry.batch( () => { + insertBlocks( + replacement, + getBlockIndex( _clientId ), + targetRootClientId, + changeSelection + ); + removeBlock( firstClientId, false ); + } ); + } + } + + if ( + ! getBlockOrder( _clientId ).length && + isUnmodifiedBlock( getBlock( _clientId ) ) + ) { + removeBlock( _clientId, false ); + } + } + } + // For `Delete` or forward merge, we should do the exact same thing // as `Backspace`, but from the other block. if ( forward ) { @@ -488,15 +554,8 @@ export default compose( [ return; } - // Check if it's possibile to "unwrap" the following block - // before trying to merge. - const replacement = switchToBlockType( - getBlock( nextBlockClientId ), - '*' - ); - - if ( replacement && replacement.length ) { - replaceBlocks( nextBlockClientId, replacement ); + if ( getBlockOrder( nextBlockClientId ).length ) { + moveFirstItemUp( nextBlockClientId, false ); } else { mergeBlocks( clientId, nextBlockClientId ); } @@ -541,18 +600,7 @@ export default compose( [ } } - // Attempt to "unwrap" the block contents when there's no - // preceding block to merge with. - const replacement = switchToBlockType( - getBlock( rootClientId ), - '*' - ); - if ( replacement && replacement.length ) { - registry.batch( () => { - replaceBlocks( rootClientId, replacement ); - selectBlock( replacement[ 0 ].clientId, 0 ); - } ); - } + moveFirstItemUp( rootClientId ); } } }, diff --git a/packages/block-library/src/list-item/index.js b/packages/block-library/src/list-item/index.js index 61756019baf92..00adc1c2c4026 100644 --- a/packages/block-library/src/list-item/index.js +++ b/packages/block-library/src/list-item/index.js @@ -10,6 +10,7 @@ import initBlock from '../utils/init-block'; import metadata from './block.json'; import edit from './edit'; import save from './save'; +import transforms from './transforms'; const { name } = metadata; @@ -25,6 +26,7 @@ export const settings = { content: attributes.content + attributesToMerge.content, }; }, + transforms, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/list-item/transforms.js b/packages/block-library/src/list-item/transforms.js new file mode 100644 index 0000000000000..6e05f8501b5a3 --- /dev/null +++ b/packages/block-library/src/list-item/transforms.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +const transforms = { + to: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( attributes ) => + createBlock( 'core/paragraph', attributes ), + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/list/test/edit.native.js b/packages/block-library/src/list/test/edit.native.js index 9defa338782db..e661fbe3a626d 100644 --- a/packages/block-library/src/list/test/edit.native.js +++ b/packages/block-library/src/list/test/edit.native.js @@ -361,7 +361,7 @@ describe( 'List block', () => { ` ); } ); - it( 'unwraps list items when attempting to merge with non-list block', async () => { + it( 'unwraps first item when attempting to merge with non-list block', async () => { const initialHtml = `

A quick brown fox.

@@ -400,14 +400,16 @@ describe( 'List block', () => { "

A quick brown fox.

- +

One

- - -

Two

- " + + + + " ` ); } ); } ); diff --git a/packages/block-library/src/list/transforms.js b/packages/block-library/src/list/transforms.js index 2f11119768ef8..a6263d7ad639c 100644 --- a/packages/block-library/src/list/transforms.js +++ b/packages/block-library/src/list/transforms.js @@ -114,17 +114,6 @@ const transforms = { ); }, } ) ), - { - type: 'block', - blocks: [ '*' ], - transform: ( _attributes, childBlocks ) => { - return getListContentFlat( childBlocks ).map( ( content ) => - createBlock( 'core/paragraph', { - content, - } ) - ); - }, - }, ], }; diff --git a/packages/block-library/src/utils/transformation-categories.native.js b/packages/block-library/src/utils/transformation-categories.native.js index 3ddf60d2a4c22..e001f9b67c785 100644 --- a/packages/block-library/src/utils/transformation-categories.native.js +++ b/packages/block-library/src/utils/transformation-categories.native.js @@ -3,6 +3,7 @@ const transformationCategories = { 'core/paragraph', 'core/heading', 'core/list', + 'core/list-item', 'core/quote', 'core/pullquote', 'core/preformatted', diff --git a/packages/blocks/README.md b/packages/blocks/README.md index f78783f728339..a03fddf71fe1c 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -529,10 +529,9 @@ _Returns_ - `boolean`: Whether the given block is a template part. -### isUnmodifiedDefaultBlock +### isUnmodifiedBlock -Determines whether the block is a default block -and its attributes are equal to the default attributes +Determines whether the block's attributes are equal to the default attributes which means the block is unmodified. _Parameters_ @@ -541,7 +540,20 @@ _Parameters_ _Returns_ -- `boolean`: Whether the block is an unmodified default block +- `boolean`: Whether the block is an unmodified block. + +### isUnmodifiedDefaultBlock + +Determines whether the block is a default block and its attributes are equal +to the default attributes which means the block is unmodified. + +_Parameters_ + +- _block_ `WPBlock`: Block Object + +_Returns_ + +- `boolean`: Whether the block is an unmodified default block. ### isValidBlockContent diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index afa11f82c6b6b..2ddeb3a60f0ab 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -137,6 +137,7 @@ export { unregisterBlockVariation, } from './registration'; export { + isUnmodifiedBlock, isUnmodifiedDefaultBlock, normalizeIconObject, isValidIcon, diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index bc68ccd189537..c43445c627226 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -30,37 +30,40 @@ extend( [ namesPlugin, a11yPlugin ] ); const ICON_COLORS = [ '#191e23', '#f8f9f9' ]; /** - * Determines whether the block is a default block - * and its attributes are equal to the default attributes + * Determines whether the block's attributes are equal to the default attributes * which means the block is unmodified. * * @param {WPBlock} block Block Object * - * @return {boolean} Whether the block is an unmodified default block + * @return {boolean} Whether the block is an unmodified block. */ -export function isUnmodifiedDefaultBlock( block ) { - const defaultBlockName = getDefaultBlockName(); - if ( block.name !== defaultBlockName ) { - return false; - } - +export function isUnmodifiedBlock( block ) { // Cache a created default block if no cache exists or the default block // name changed. - if ( - ! isUnmodifiedDefaultBlock.block || - isUnmodifiedDefaultBlock.block.name !== defaultBlockName - ) { - isUnmodifiedDefaultBlock.block = createBlock( defaultBlockName ); + if ( ! isUnmodifiedBlock[ block.name ] ) { + isUnmodifiedBlock[ block.name ] = createBlock( block.name ); } - const newDefaultBlock = isUnmodifiedDefaultBlock.block; - const blockType = getBlockType( defaultBlockName ); + const newBlock = isUnmodifiedBlock[ block.name ]; + const blockType = getBlockType( block.name ); return Object.keys( blockType?.attributes ?? {} ).every( - ( key ) => newDefaultBlock.attributes[ key ] === block.attributes[ key ] + ( key ) => newBlock.attributes[ key ] === block.attributes[ key ] ); } +/** + * Determines whether the block is a default block and its attributes are equal + * to the default attributes which means the block is unmodified. + * + * @param {WPBlock} block Block Object + * + * @return {boolean} Whether the block is an unmodified default block. + */ +export function isUnmodifiedDefaultBlock( block ) { + return block.name === getDefaultBlockName() && isUnmodifiedBlock( block ); +} + /** * Function that checks if the parameter is a valid icon. * diff --git a/packages/e2e-tests/specs/editor/blocks/quote.test.js b/packages/e2e-tests/specs/editor/blocks/quote.test.js index bb61fc7a5ee0a..9b0a4bac546dd 100644 --- a/packages/e2e-tests/specs/editor/blocks/quote.test.js +++ b/packages/e2e-tests/specs/editor/blocks/quote.test.js @@ -191,9 +191,9 @@ describe( 'Quote', () => {

1

- -

2

- " + +
2
+ " ` ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/block-switcher.test.js b/packages/e2e-tests/specs/editor/various/block-switcher.test.js index eea665e688888..6e5c192a758ad 100644 --- a/packages/e2e-tests/specs/editor/various/block-switcher.test.js +++ b/packages/e2e-tests/specs/editor/various/block-switcher.test.js @@ -82,9 +82,9 @@ describe( 'Block Switcher', () => { await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. - expect( await hasBlockSwitcher() ).toBeTruthy(); + expect( await hasBlockSwitcher() ).toBeFalsy(); // Verify the correct block transforms appear. - expect( await getAvailableBlockTransforms() ).toHaveLength( 1 ); + expect( await getAvailableBlockTransforms() ).toHaveLength( 0 ); } ); describe( 'Conditional tranformation options', () => { diff --git a/test/e2e/specs/editor/blocks/list.spec.js b/test/e2e/specs/editor/blocks/list.spec.js index daa23241a4e8e..0a1159f6d5d49 100644 --- a/test/e2e/specs/editor/blocks/list.spec.js +++ b/test/e2e/specs/editor/blocks/list.spec.js @@ -1090,9 +1090,11 @@ test.describe( 'List', () => {

- -

2

-` + + +` ); } ); diff --git a/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-18edb-roduces-more-than-one-block-on-forward-delete-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-18edb-roduces-more-than-one-block-on-forward-delete-2-chromium.txt new file mode 100644 index 0000000000000..463d4ecae0e09 --- /dev/null +++ b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-18edb-roduces-more-than-one-block-on-forward-delete-2-chromium.txt @@ -0,0 +1,9 @@ + +

hi-item 1

+ + + + + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-2a1ee-roduces-more-than-one-block-on-forward-delete-1-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-2a1ee-roduces-more-than-one-block-on-forward-delete-1-chromium.txt index cb5b5fcf148b3..04346aeb9e959 100644 --- a/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-2a1ee-roduces-more-than-one-block-on-forward-delete-1-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-2a1ee-roduces-more-than-one-block-on-forward-delete-1-chromium.txt @@ -1,3 +1,13 @@ -

hi-

- \ No newline at end of file +

hi

+ + + +

item 1

+ + + + + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-46dfa-rge-produces-more-than-one-block-on-backspace-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-46dfa-rge-produces-more-than-one-block-on-backspace-2-chromium.txt new file mode 100644 index 0000000000000..463d4ecae0e09 --- /dev/null +++ b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-46dfa-rge-produces-more-than-one-block-on-backspace-2-chromium.txt @@ -0,0 +1,9 @@ + +

hi-item 1

+ + + + + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-92273-rge-produces-more-than-one-block-on-backspace-1-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-92273-rge-produces-more-than-one-block-on-backspace-1-chromium.txt index 33adf8bef295a..04346aeb9e959 100644 --- a/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-92273-rge-produces-more-than-one-block-on-backspace-1-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/splitting-and-merging-blocks-test-restore-sele-92273-rge-produces-more-than-one-block-on-backspace-1-chromium.txt @@ -3,9 +3,11 @@ -

-item 1

+

item 1

- -

item 2

- \ No newline at end of file + + + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/splitting-merging.spec.js b/test/e2e/specs/editor/various/splitting-merging.spec.js index 45f4d9347edcc..510c1cb46cf3b 100644 --- a/test/e2e/specs/editor/various/splitting-merging.spec.js +++ b/test/e2e/specs/editor/various/splitting-merging.spec.js @@ -377,7 +377,11 @@ test.describe( 'splitting and merging blocks', () => { await page.keyboard.type( 'item 1' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'item 2' ); - await pageUtils.pressKeyTimes( 'ArrowUp', 2 ); + await pageUtils.pressKeyTimes( 'ArrowUp', 3 ); + await page.keyboard.press( 'Delete' ); + + expect( await editor.getEditedPostContent() ).toMatchSnapshot(); + await page.keyboard.press( 'Delete' ); // Carret should be in the first block and at the proper position. await page.keyboard.type( '-' ); @@ -395,6 +399,10 @@ test.describe( 'splitting and merging blocks', () => { await page.keyboard.type( 'item 2' ); await page.keyboard.press( 'ArrowUp' ); await pageUtils.pressKeyTimes( 'ArrowLeft', 6 ); + await page.keyboard.press( 'Backspace' ); + + expect( await editor.getEditedPostContent() ).toMatchSnapshot(); + await page.keyboard.press( 'Backspace' ); // Carret should be in the first block and at the proper position. await page.keyboard.type( '-' );