diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index aa6a44461ee73..c84e21614d6d8 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -101,6 +101,7 @@ export function RichTextWrapper( onMerge, onSplit, __unstableOnSplitAtEnd: onSplitAtEnd, + __unstableOnSplitAtDoubleLineEnd: onSplitAtDoubleLineEnd, identifier, preserveWhiteSpace, __unstablePastePlainText: pastePlainText, @@ -363,6 +364,7 @@ export function RichTextWrapper( onChange, disableLineBreaks, onSplitAtEnd, + onSplitAtDoubleLineEnd, } ), useFirefoxCompat(), anchorRef, diff --git a/packages/block-editor/src/components/rich-text/use-enter.js b/packages/block-editor/src/components/rich-text/use-enter.js index 08c5abf9d7b6b..40d4f9be4759f 100644 --- a/packages/block-editor/src/components/rich-text/use-enter.js +++ b/packages/block-editor/src/components/rich-text/use-enter.js @@ -4,9 +4,9 @@ import { useRef } from '@wordpress/element'; import { useRefEffect } from '@wordpress/compose'; import { ENTER } from '@wordpress/keycodes'; -import { insert } from '@wordpress/rich-text'; +import { insert, remove } from '@wordpress/rich-text'; import { getBlockTransforms, findTransform } from '@wordpress/blocks'; -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useRegistry } from '@wordpress/data'; /** * Internal dependencies @@ -15,6 +15,7 @@ import { store as blockEditorStore } from '../../store'; import { splitValue } from './split-value'; export function useEnter( props ) { + const registry = useRegistry(); const { __unstableMarkAutomaticChange } = useDispatch( blockEditorStore ); const propsRef = useRef( props ); propsRef.current = props; @@ -36,6 +37,7 @@ export function useEnter( props ) { onChange, disableLineBreaks, onSplitAtEnd, + onSplitAtDoubleLineEnd, } = propsRef.current; event.preventDefault(); @@ -63,21 +65,35 @@ export function useEnter( props ) { } const { text, start, end } = _value; - const canSplitAtEnd = - onSplitAtEnd && start === end && end === text.length; - if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) { + if ( event.shiftKey ) { if ( ! disableLineBreaks ) { onChange( insert( _value, '\n' ) ); } - } else if ( ! canSplit && canSplitAtEnd ) { - onSplitAtEnd(); } else if ( canSplit ) { splitValue( { value: _value, onReplace, onSplit, } ); + } else if ( onSplitAtEnd && start === end && end === text.length ) { + onSplitAtEnd(); + } else if ( + // For some blocks it's desirable to split at the end of the + // block when there are two line breaks at the end of the + // block, so triple Enter exits the block. + onSplitAtDoubleLineEnd && + start === end && + end === text.length && + text.slice( -2 ) === '\n\n' + ) { + registry.batch( () => { + _value.start = _value.end - 2; + onChange( remove( _value ) ); + onSplitAtDoubleLineEnd(); + } ); + } else if ( ! disableLineBreaks ) { + onChange( insert( _value, '\n' ) ); } } diff --git a/packages/block-library/src/code/block.json b/packages/block-library/src/code/block.json index 4d19423f1b629..80df74b5062b5 100644 --- a/packages/block-library/src/code/block.json +++ b/packages/block-library/src/code/block.json @@ -10,7 +10,8 @@ "content": { "type": "string", "source": "html", - "selector": "code" + "selector": "code", + "__unstablePreserveWhiteSpace": true } }, "supports": { diff --git a/packages/block-library/src/code/edit.js b/packages/block-library/src/code/edit.js index 4153b14d753cd..a3dbedeaf2335 100644 --- a/packages/block-library/src/code/edit.js +++ b/packages/block-library/src/code/edit.js @@ -3,20 +3,32 @@ */ import { __ } from '@wordpress/i18n'; import { RichText, useBlockProps } from '@wordpress/block-editor'; +import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; -export default function CodeEdit( { attributes, setAttributes, onRemove } ) { +export default function CodeEdit( { + attributes, + setAttributes, + onRemove, + insertBlocksAfter, + mergeBlocks, +} ) { const blockProps = useBlockProps(); return (
 			 setAttributes( { content } ) }
 				onRemove={ onRemove }
+				onMerge={ mergeBlocks }
 				placeholder={ __( 'Write codeā€¦' ) }
 				aria-label={ __( 'Code' ) }
 				preserveWhiteSpace
 				__unstablePastePlainText
+				__unstableOnSplitAtDoubleLineEnd={ () =>
+					insertBlocksAfter( createBlock( getDefaultBlockName() ) )
+				}
 			/>
 		
); diff --git a/packages/block-library/src/code/index.js b/packages/block-library/src/code/index.js index 091c59ffbfca4..c602045256d6e 100644 --- a/packages/block-library/src/code/index.js +++ b/packages/block-library/src/code/index.js @@ -29,6 +29,11 @@ export const settings = { /* eslint-enable @wordpress/i18n-no-collapsible-whitespace */ }, }, + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + '\n\n' + attributesToMerge.content, + }; + }, transforms, edit, save, diff --git a/packages/block-library/src/preformatted/edit.js b/packages/block-library/src/preformatted/edit.js index 7117d0617905f..072475ff072c9 100644 --- a/packages/block-library/src/preformatted/edit.js +++ b/packages/block-library/src/preformatted/edit.js @@ -3,12 +3,14 @@ */ import { __ } from '@wordpress/i18n'; import { RichText, useBlockProps } from '@wordpress/block-editor'; +import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; export default function PreformattedEdit( { attributes, mergeBlocks, setAttributes, onRemove, + insertBlocksAfter, style, } ) { const { content } = attributes; @@ -31,6 +33,9 @@ export default function PreformattedEdit( { onMerge={ mergeBlocks } { ...blockProps } __unstablePastePlainText + __unstableOnSplitAtDoubleLineEnd={ () => + insertBlocksAfter( createBlock( getDefaultBlockName() ) ) + } /> ); } diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index 491acd02bab1d..a2e1fecc18552 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -34,7 +34,7 @@ export const settings = { save, merge( attributes, attributesToMerge ) { return { - content: attributes.content + attributesToMerge.content, + content: attributes.content + '\n\n' + attributesToMerge.content, }; }, }; diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 1ca846fb848b5..a76ecc1ff6ab4 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -13,12 +13,14 @@ import { AlignmentToolbar, useBlockProps, } from '@wordpress/block-editor'; +import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; export default function VerseEdit( { attributes, setAttributes, mergeBlocks, onRemove, + insertBlocksAfter, style, } ) { const { textAlign, content } = attributes; @@ -56,6 +58,9 @@ export default function VerseEdit( { textAlign={ textAlign } { ...blockProps } __unstablePastePlainText + __unstableOnSplitAtDoubleLineEnd={ () => + insertBlocksAfter( createBlock( getDefaultBlockName() ) ) + } /> ); diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index 6cf8b9b9fb9e4..5d80b63a4ca84 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -34,7 +34,7 @@ export const settings = { deprecated, merge( attributes, attributesToMerge ) { return { - content: attributes.content + attributesToMerge.content, + content: attributes.content + '\n\n' + attributesToMerge.content, }; }, edit, diff --git a/test/e2e/specs/editor/blocks/__snapshots__/Preformatted-should-preserve-white-space-when-merging-1-chromium.txt b/test/e2e/specs/editor/blocks/__snapshots__/Preformatted-should-preserve-white-space-when-merging-1-chromium.txt index a89cb7555bbf8..3d3ef935d9f44 100644 --- a/test/e2e/specs/editor/blocks/__snapshots__/Preformatted-should-preserve-white-space-when-merging-1-chromium.txt +++ b/test/e2e/specs/editor/blocks/__snapshots__/Preformatted-should-preserve-white-space-when-merging-1-chromium.txt @@ -1,5 +1,6 @@
1
 2
+
 3
\ No newline at end of file diff --git a/test/e2e/specs/editor/blocks/preformatted.spec.js b/test/e2e/specs/editor/blocks/preformatted.spec.js index d359ec9def85c..5cd20a051c4fb 100644 --- a/test/e2e/specs/editor/blocks/preformatted.spec.js +++ b/test/e2e/specs/editor/blocks/preformatted.spec.js @@ -33,7 +33,6 @@ test.describe( 'Preformatted', () => { await page.keyboard.type( '1' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( '2' ); - await page.keyboard.press( 'Enter' ); await editor.insertBlock( { name: 'core/paragraph' } ); await page.keyboard.type( '3' ); await page.keyboard.press( 'ArrowLeft' ); diff --git a/test/e2e/specs/editor/blocks/verse-code-preformatted.spec.js b/test/e2e/specs/editor/blocks/verse-code-preformatted.spec.js new file mode 100644 index 0000000000000..c3642bfbca312 --- /dev/null +++ b/test/e2e/specs/editor/blocks/verse-code-preformatted.spec.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +// This spec tests common behavior of Verse, Code, and Preformatted blocks. +[ 'core/verse', 'core/code', 'core/preformatted' ].forEach( ( blockName ) => { + test.describe( blockName, () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test( 'should exit on triple Enter and merge back', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { name: blockName } ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'b' ); + + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: blockName, + attributes: { + content: 'a', + }, + }, + { + name: 'core/paragraph', + attributes: { + content: 'b', + }, + }, + ] ); + + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Backspace' ); + + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: blockName, + attributes: { + content: 'a\n\nb', + }, + }, + ] ); + } ); + } ); +} );