diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 0d02a8e655bfa3..dca0ce4763cdbb 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -72,7 +72,7 @@ exports[`adding blocks should create valid paragraph blocks when rapidly pressin exports[`adding blocks should insert line break at end 1`] = ` " -

a

+

a

" `; @@ -90,7 +90,7 @@ exports[`adding blocks should insert line break at start 1`] = ` exports[`adding blocks should insert line break in empty container 1`] = ` " -



+


" `; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index 119fce765c5f43..b2e8ec4875aaca 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -146,13 +146,13 @@ exports[`List should indent and outdent level 2 3`] = ` exports[`List should insert a line break on shift+enter 1`] = ` " - + " `; exports[`List should insert a line break on shift+enter in a non trailing list item 1`] = ` " - + " `; diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 3ca992296e4fb2..e72a70b61e46c6 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -193,6 +193,7 @@ export class RichText extends Component { multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, prepareEditableTree: this.props.prepareEditableTree, + __unstableIsEditableTree: true, } ); } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 90d54c82c45611..82348bbd4fa8aa 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -108,6 +108,7 @@ export function create( { range, multilineTag, multilineWrapperTags, + __unstableIsEditableTree: isEditableTree, } = {} ) { if ( typeof text === 'string' && text.length > 0 ) { return { @@ -128,6 +129,7 @@ export function create( { return createFromElement( { element, range, + isEditableTree, } ); } @@ -136,6 +138,7 @@ export function create( { range, multilineTag, multilineWrapperTags, + isEditableTree, } ); } @@ -257,6 +260,7 @@ function createFromElement( { multilineTag, multilineWrapperTags, currentWrapperTags = [], + isEditableTree, } ) { const accumulator = createEmptyValue(); @@ -291,7 +295,10 @@ function createFromElement( { continue; } - if ( node.getAttribute( 'data-rich-text-padding' ) ) { + if ( + node.getAttribute( 'data-rich-text-padding' ) || + ( isEditableTree && type === 'br' && ! node.getAttribute( 'data-rich-text-line-break' ) ) + ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; } @@ -433,7 +440,7 @@ function createFromMultilineElement( { continue; } - let value = createFromElement( { + const value = createFromElement( { element: node, range, multilineTag, @@ -441,23 +448,6 @@ function createFromMultilineElement( { currentWrapperTags, } ); - // If a line consists of one single line break (invisible), consider the - // line empty, wether this is the browser's doing or not. - if ( value.text === '\n' ) { - const start = value.start; - const end = value.end; - - value = createEmptyValue(); - - if ( start !== undefined ) { - value.start = 0; - } - - if ( end !== undefined ) { - value.end = 0; - } - } - // Multiline value text should be separated by a double line break. if ( index !== 0 || currentWrapperTags.length > 0 ) { const formats = currentWrapperTags.length > 0 ? [ currentWrapperTags ] : [ , ]; @@ -495,7 +485,7 @@ function getAttributes( { element } ) { for ( let i = 0; i < length; i++ ) { const { name, value } = element.attributes[ i ]; - if ( name === 'data-rich-text-format-boundary' ) { + if ( name.indexOf( 'data-rich-text-' ) === 0 ) { continue; } diff --git a/packages/rich-text/src/insert-line-break.js b/packages/rich-text/src/insert-line-break.js index 540214d614f3c6..08b0b70b864e63 100644 --- a/packages/rich-text/src/insert-line-break.js +++ b/packages/rich-text/src/insert-line-break.js @@ -3,32 +3,14 @@ */ import { insert } from './insert'; -import { LINE_SEPARATOR } from './special-characters'; /** - * Inserts a line break at the given or selected position. Inserts two line - * breaks if at the end of a line. + * Inserts a line break at the given or selected position. * * @param {Object} value Value to modify. * - * @return {Object} The value with the line break(s) inserted. + * @return {Object} The value with the line break inserted. */ export function insertLineBreak( value ) { - const { text, end } = value; - const length = text.length; - - let toInsert = '\n'; - - // If the caret is at the end of the text, and there is no - // trailing line break or no text at all, we have to insert two - // line breaks in order to create a new line visually and place - // the caret there. - if ( - ( end === length || text[ end ] === LINE_SEPARATOR ) && - ( text[ end - 1 ] !== '\n' || length === 0 ) - ) { - toInsert = '\n\n'; - } - - return insert( value, toInsert ); + return insert( value, '\n' ); } diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index 78378090137bd5..a68cdab1c911eb 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -132,8 +132,13 @@ exports[`recordToDom should filter format boundary attributes 1`] = ` exports[`recordToDom should handle br 1`] = ` -
+
+
`; @@ -143,17 +148,24 @@ exports[`recordToDom should handle br with formatting 1`] = ` data-rich-text-format-boundary="true" > -
+
+
`; exports[`recordToDom should handle br with text 1`] = ` te -
+
st `; @@ -161,9 +173,13 @@ exports[`recordToDom should handle br with text 1`] = ` exports[`recordToDom should handle double br 1`] = ` a -
+
-
+
b `; @@ -197,12 +213,14 @@ exports[`recordToDom should handle middle empty list value 1`] = `
+

  • +
  • @@ -276,6 +294,7 @@ exports[`recordToDom should handle multiline value with empty 1`] = ` exports[`recordToDom should handle nested empty list value 1`] = `
  • +
    @@ -294,9 +313,13 @@ exports[`recordToDom should handle nested empty list value 1`] = ` exports[`recordToDom should handle selection before br 1`] = ` a -
    +
    -
    +
    b `; diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 2828b36718ca71..e12757ad03530d 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -459,8 +459,8 @@ export const spec = [ endOffset: 0, endContainer: element.querySelector( 'ul > li' ), } ), - startPath: [ 0, 0, 0, 0, 0 ], - endPath: [ 0, 0, 0, 0, 0 ], + startPath: [ 0, 2, 0, 0, 0 ], + endPath: [ 0, 2, 0, 0, 0 ], record: { start: 1, end: 1, @@ -479,8 +479,8 @@ export const spec = [ endOffset: 0, endContainer: element.firstChild.nextSibling, } ), - startPath: [ 1, 0, 0 ], - endPath: [ 1, 0, 0 ], + startPath: [ 1, 2, 0 ], + endPath: [ 1, 2, 0 ], record: { start: 1, end: 1, diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 222d06ad800747..987849172bf5f9 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -113,39 +113,6 @@ function remove( node ) { return node.parentNode.removeChild( node ); } -function createLinePadding( doc ) { - const element = doc.createElement( 'br' ); - element.setAttribute( 'data-rich-text-padding', 'true' ); - return element; -} - -function padEmptyLines( { element, multilineWrapperTags } ) { - const length = element.childNodes.length; - const doc = element.ownerDocument; - - for ( let index = 0; index < length; index++ ) { - const child = element.childNodes[ index ]; - - if ( child.nodeType === TEXT_NODE ) { - if ( length === 1 && ! child.nodeValue ) { - // Pad if the only child is an empty text node. - element.appendChild( createLinePadding( doc ) ); - } - } else { - if ( - multilineWrapperTags && - ! child.previousSibling && - multilineWrapperTags.indexOf( child.nodeName.toLowerCase() ) !== -1 - ) { - // Pad the line if there is no content before a nested wrapper. - element.insertBefore( createLinePadding( doc ), child ); - } - - padEmptyLines( { element: child, multilineWrapperTags } ); - } - } -} - function prepareFormats( prepareEditableTree = [], value ) { return prepareEditableTree.reduce( ( accumlator, fn ) => { return fn( accumlator, value.text ); @@ -186,10 +153,6 @@ export function toDom( { isEditableTree, } ); - if ( isEditableTree ) { - padEmptyLines( { element: tree, multilineWrapperTags } ); - } - return { body: tree, selection: { startPath, endPath }, diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 9ce962d540d496..c89bdc94a2d061 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -80,6 +80,14 @@ function getDeepestActiveFormat( value ) { return activeFormats[ selectedFormat - 1 ]; } +const padding = { + type: 'br', + attributes: { + 'data-rich-text-padding': 'true', + }, + object: true, +}; + export function toTree( { value, multilineTag, @@ -116,6 +124,15 @@ export function toTree( { for ( let i = 0; i < formatsLength; i++ ) { const character = text.charAt( i ); + const shouldInsertPadding = isEditableTree && ( + // Pad the line if the line is empty. + ! lastCharacter || + lastCharacter === LINE_SEPARATOR || + // Pad the line if the previous character is a line break, otherwise + // the line break won't be visible. + lastCharacter === '\n' + ); + let characterFormats = formats[ i ]; // Set multiline tags in queue for building the tree. @@ -136,6 +153,17 @@ export function toTree( { let pointer = getLastChild( tree ); + if ( shouldInsertPadding && character === LINE_SEPARATOR ) { + let node = pointer; + + while ( ! isText( node ) ) { + node = getLastChild( node ); + } + + append( getParent( node ), padding ); + append( getParent( node ), '' ); + } + // Set selection for the start of line. if ( lastCharacter === LINE_SEPARATOR ) { let node = pointer; @@ -214,7 +242,13 @@ export function toTree( { if ( character !== OBJECT_REPLACEMENT_CHARACTER ) { if ( character === '\n' ) { - pointer = append( getParent( pointer ), { type: 'br', object: true } ); + pointer = append( getParent( pointer ), { + type: 'br', + attributes: isEditableTree ? { + 'data-rich-text-line-break': 'true', + } : undefined, + object: true, + } ); // Ensure pointer is text node. pointer = append( getParent( pointer ), '' ); } else if ( ! isText( pointer ) ) { @@ -232,6 +266,10 @@ export function toTree( { onEndIndex( tree, pointer ); } + if ( shouldInsertPadding && i === text.length ) { + append( getParent( pointer ), padding ); + } + lastCharacterFormats = characterFormats; lastCharacter = character; }