From a6ad1fab562d4ff582ac0cfd89aedf52cfb33c68 Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 8 Sep 2023 18:51:42 +0300 Subject: [PATCH 1/6] RichText: replace deprecate multiline prop with simple multiple instances --- .../src/components/rich-text/index.js | 81 ++++++------ .../src/components/rich-text/multiline.js | 120 ++++++++++++++++++ 2 files changed, 165 insertions(+), 36 deletions(-) create mode 100644 packages/block-editor/src/components/rich-text/multiline.js diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index a22b251dd607c2..9bdac14d77cae5 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -46,6 +46,7 @@ import { useFirefoxCompat } from './use-firefox-compat'; import FormatEdit from './format-edit'; import { getMultilineTag, getAllowedFormats } from './utils'; import { Content } from './content'; +import RichTextMultiline from './multiline'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -81,12 +82,12 @@ function removeNativeProps( props ) { return restProps; } -function RichTextWrapper( +export function RichTextWrapper( { children, tagName = 'div', - value: originalValue = '', - onChange: originalOnChange, + value: adjustedValue = '', + onChange: adjustedOnChange, isSelected: originalIsSelected, multiline, inlineToolbar, @@ -111,18 +112,6 @@ function RichTextWrapper( }, forwardedRef ) { - if ( multiline ) { - deprecated( 'wp.blockEditor.RichText multiline prop', { - since: '6.1', - version: '6.3', - alternative: 'nested blocks (InnerBlocks)', - link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/', - } ); - } - - const instanceId = useInstanceId( RichTextWrapper ); - - identifier = identifier || instanceId; props = removeNativeProps( props ); const anchorRef = useRef(); @@ -164,26 +153,6 @@ function RichTextWrapper( } ); const hasFormats = ! adjustedAllowedFormats || adjustedAllowedFormats.length > 0; - let adjustedValue = originalValue; - let adjustedOnChange = originalOnChange; - - // Handle deprecated format. - if ( Array.isArray( originalValue ) ) { - deprecated( 'wp.blockEditor.RichText value prop as children type', { - since: '6.1', - version: '6.3', - alternative: 'value prop as string', - link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/', - } ); - - adjustedValue = childrenSource.toHTML( originalValue ); - adjustedOnChange = ( newValue ) => - originalOnChange( - childrenSource.fromDOM( - __unstableCreateElement( document, newValue ).childNodes - ) - ); - } const onSelectionChange = useCallback( ( start, end ) => { @@ -421,7 +390,47 @@ function RichTextWrapper( ); } -const ForwardedRichTextContainer = forwardRef( RichTextWrapper ); +const ForwardedRichTextWrapper = forwardRef( RichTextWrapper ); + +function RichTextSwitcher( props, ref ) { + let value = props.value; + let onChange = props.onChange; + + // Handle deprecated format. + if ( Array.isArray( value ) ) { + deprecated( 'wp.blockEditor.RichText value prop as children type', { + since: '6.1', + version: '6.3', + alternative: 'value prop as string', + link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/', + } ); + + value = childrenSource.toHTML( props.value ); + onChange = ( newValue ) => + props.onChange( + childrenSource.fromDOM( + __unstableCreateElement( document, newValue ).childNodes + ) + ); + } + + const Component = props.multiline + ? RichTextMultiline + : ForwardedRichTextWrapper; + const instanceId = useInstanceId( RichTextSwitcher ); + + return ( + + ); +} + +const ForwardedRichTextContainer = forwardRef( RichTextSwitcher ); ForwardedRichTextContainer.Content = Content; ForwardedRichTextContainer.isEmpty = ( value ) => { diff --git a/packages/block-editor/src/components/rich-text/multiline.js b/packages/block-editor/src/components/rich-text/multiline.js new file mode 100644 index 00000000000000..49075d80196bdf --- /dev/null +++ b/packages/block-editor/src/components/rich-text/multiline.js @@ -0,0 +1,120 @@ +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { RichTextWrapper } from './'; +import { store as blockEditorStore } from '../../store'; +import { useBlockEditContext } from '../block-edit'; + +function RichTextMultiline( + { + children, + identifier, + tagName: TagName = 'div', + value = '', + onChange, + multiline, + ...props + }, + forwardedRef +) { + deprecated( 'wp.blockEditor.RichText multiline prop', { + since: '6.1', + version: '6.3', + alternative: 'nested blocks (InnerBlocks)', + link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/', + } ); + + const { clientId } = useBlockEditContext(); + const { selectionChange } = useDispatch( blockEditorStore ); + + const multilineTagName = multiline.toLowerCase(); + value = value || `<${ multilineTagName }>`; + const padded = `${ value }<${ multilineTagName }>`; + const values = padded.split( + `<${ multilineTagName }>` + ); + + values.shift(); + values.pop(); + + function _onChange( newValues ) { + onChange( + `<${ multilineTagName }>${ newValues.join( + `<${ multilineTagName }>` + ) }` + ); + } + + return ( + + { values.map( ( _value, index ) => { + return ( + { + const newValues = values.slice(); + newValues[ index ] = newValue; + _onChange( newValues ); + } } + isSelected={ undefined } + onSplit={ ( v ) => v } + onReplace={ ( array ) => { + const newValues = values.slice(); + newValues.splice( index, 1, ...array ); + _onChange( newValues ); + selectionChange( + clientId, + `${ identifier }-${ index + 1 }`, + 0, + 0 + ); + } } + onMerge={ ( forward ) => { + const newValues = values.slice(); + let offset = 0; + if ( forward ) { + if ( ! newValues[ index + 1 ] ) return; + newValues.splice( + index, + 2, + newValues[ index ] + newValues[ index + 1 ] + ); + offset = newValues[ index ].length; + } else { + if ( ! newValues[ index - 1 ] ) return; + newValues.splice( + index - 1, + 2, + newValues[ index - 1 ] + newValues[ index ] + ); + offset = newValues[ index - 1 ].length; + } + _onChange( newValues ); + selectionChange( + clientId, + `${ identifier }-${ + index - ( forward ? 0 : 1 ) + }`, + offset, + offset + ); + } } + { ...props } + /> + ); + } ) } + + ); +} + +export default forwardRef( RichTextMultiline ); From 67c320c0aec140e38119e475a379d233c37821db Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 8 Sep 2023 19:46:30 +0300 Subject: [PATCH 2/6] Remove multiline prop from base rich text --- .../src/components/rich-text/index.js | 6 +- .../src/components/rich-text/index.native.js | 56 ++--- .../src/components/rich-text/multiline.js | 3 +- .../src/components/rich-text/split-value.js | 24 +-- .../src/components/rich-text/use-enter.js | 53 ++--- .../components/rich-text/use-paste-handler.js | 35 +--- packages/block-editor/src/store/utils.js | 13 +- .../block-library/src/pullquote/deprecated.js | 21 +- packages/rich-text/README.md | 7 +- packages/rich-text/src/component/index.js | 15 +- .../rich-text/src/component/index.native.js | 23 +- .../src/component/use-copy-handler.js | 4 +- .../rich-text/src/component/use-delete.js | 28 +-- packages/rich-text/src/create.js | 147 +------------ packages/rich-text/src/get-text-content.js | 14 +- packages/rich-text/src/index.ts | 4 +- .../rich-text/src/insert-line-separator.js | 43 ---- packages/rich-text/src/is-empty.js | 36 ---- .../rich-text/src/remove-line-separator.js | 55 ----- packages/rich-text/src/special-characters.js | 5 - packages/rich-text/src/split.js | 8 +- packages/rich-text/src/test/create.js | 51 ++--- packages/rich-text/src/test/helpers/index.js | 196 ------------------ .../src/test/insert-line-separator.js | 104 ---------- packages/rich-text/src/test/is-empty.js | 61 +----- packages/rich-text/src/test/split.js | 33 --- packages/rich-text/src/test/to-dom.js | 21 +- packages/rich-text/src/test/to-html-string.js | 15 -- packages/rich-text/src/to-dom.js | 8 +- packages/rich-text/src/to-html-string.js | 7 +- packages/rich-text/src/to-tree.js | 85 +------- 31 files changed, 108 insertions(+), 1073 deletions(-) delete mode 100644 packages/rich-text/src/insert-line-separator.js delete mode 100644 packages/rich-text/src/remove-line-separator.js delete mode 100644 packages/rich-text/src/test/insert-line-separator.js diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 9bdac14d77cae5..aa6a44461ee73c 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -44,7 +44,7 @@ import { useInputEvents } from './use-input-events'; import { useInsertReplacementText } from './use-insert-replacement-text'; import { useFirefoxCompat } from './use-firefox-compat'; import FormatEdit from './format-edit'; -import { getMultilineTag, getAllowedFormats } from './utils'; +import { getAllowedFormats } from './utils'; import { Content } from './content'; import RichTextMultiline from './multiline'; @@ -146,7 +146,6 @@ export function RichTextWrapper( const { getSelectionStart, getSelectionEnd, getBlockRootClientId } = useSelect( blockEditorStore ); const { selectionChange } = useDispatch( blockEditorStore ); - const multilineTag = getMultilineTag( multiline ); const adjustedAllowedFormats = getAllowedFormats( { allowedFormats, disableFormats, @@ -261,7 +260,6 @@ export function RichTextWrapper( onSelectionChange, placeholder, __unstableIsSelected: isSelected, - __unstableMultilineTag: multilineTag, __unstableDisableFormats: disableFormats, preserveWhiteSpace, __unstableDependencies: [ ...dependencies, tagName ], @@ -349,7 +347,6 @@ export function RichTextWrapper( onReplace, onSplit, __unstableEmbedURLOnPaste, - multilineTag, preserveWhiteSpace, pastePlainText, } ), @@ -363,7 +360,6 @@ export function RichTextWrapper( value, onReplace, onSplit, - multilineTag, onChange, disableLineBreaks, onSplitAtEnd, diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index b0c82848db6876..67f41f9ae8c108 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -20,13 +20,9 @@ import { __experimentalRichText as RichText, __unstableCreateElement, isEmpty, - __unstableIsEmptyLine as isEmptyLine, insert, - __unstableInsertLineSeparator as insertLineSeparator, create, - replace, split, - __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR, toHTMLString, slice, } from '@wordpress/rich-text'; @@ -338,32 +334,20 @@ function RichTextWrapper( onCustomEnter(); } - if ( multiline ) { - if ( shiftKey ) { - if ( ! disableLineBreaks ) { - onChange( insert( value, '\n' ) ); - } - } else if ( canSplit && isEmptyLine( value ) ) { - splitValue( value ); - } else { - onChange( insertLineSeparator( value ) ); - } - } else { - const { text, start: splitStart, end: splitEnd } = value; - const canSplitAtEnd = - onSplitAtEnd && - splitStart === splitEnd && - splitEnd === text.length; - - if ( shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) { - if ( ! disableLineBreaks ) { - onChange( insert( value, '\n' ) ); - } - } else if ( ! canSplit && canSplitAtEnd ) { - onSplitAtEnd(); - } else if ( canSplit ) { - splitValue( value ); + const { text, start: splitStart, end: splitEnd } = value; + const canSplitAtEnd = + onSplitAtEnd && + splitStart === splitEnd && + splitEnd === text.length; + + if ( shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) { + if ( ! disableLineBreaks ) { + onChange( insert( value, '\n' ) ); } + } else if ( ! canSplit && canSplitAtEnd ) { + onSplitAtEnd(); + } else if ( canSplit ) { + splitValue( value ); } }, // eslint-disable-next-line react-hooks/exhaustive-deps @@ -471,20 +455,8 @@ function RichTextWrapper( } ); if ( typeof content === 'string' ) { - let valueToInsert = create( { html: content } ); - + const valueToInsert = create( { html: content } ); addActiveFormats( valueToInsert, activeFormats ); - - // If the content should be multiline, we should process text - // separated by a line break as separate lines. - if ( multilineTag ) { - valueToInsert = replace( - valueToInsert, - /\n+/g, - LINE_SEPARATOR - ); - } - onChange( insert( value, valueToInsert ) ); } else if ( content.length > 0 ) { // When an URL is pasted in an empty paragraph then the EmbedHandlerPicker should showcase options allowing the transformation of that URL diff --git a/packages/block-editor/src/components/rich-text/multiline.js b/packages/block-editor/src/components/rich-text/multiline.js index 49075d80196bdf..16905f08e4d85d 100644 --- a/packages/block-editor/src/components/rich-text/multiline.js +++ b/packages/block-editor/src/components/rich-text/multiline.js @@ -11,6 +11,7 @@ import { useDispatch } from '@wordpress/data'; import { RichTextWrapper } from './'; import { store as blockEditorStore } from '../../store'; import { useBlockEditContext } from '../block-edit'; +import { getMultilineTag } from './utils'; function RichTextMultiline( { @@ -34,7 +35,7 @@ function RichTextMultiline( const { clientId } = useBlockEditContext(); const { selectionChange } = useDispatch( blockEditorStore ); - const multilineTagName = multiline.toLowerCase(); + const multilineTagName = getMultilineTag( multiline ); value = value || `<${ multilineTagName }>`; const padded = `${ value }<${ multilineTagName }>`; const values = padded.split( diff --git a/packages/block-editor/src/components/rich-text/split-value.js b/packages/block-editor/src/components/rich-text/split-value.js index 0ec083c9fe1e50..17f54d9c9edd01 100644 --- a/packages/block-editor/src/components/rich-text/split-value.js +++ b/packages/block-editor/src/components/rich-text/split-value.js @@ -8,13 +8,7 @@ import { isEmpty, split, toHTMLString } from '@wordpress/rich-text'; * as a result of splitting the block by pressing enter, or with blocks as a * result of splitting the block by pasting block content in the instance. */ -export function splitValue( { - value, - pastedBlocks = [], - onReplace, - onSplit, - multilineTag, -} ) { +export function splitValue( { value, pastedBlocks = [], onReplace, onSplit } ) { if ( ! onReplace || ! onSplit ) { return; } @@ -38,13 +32,7 @@ export function splitValue( { // the enter key. if ( ! hasPastedBlocks || ! isEmpty( before ) ) { blocks.push( - onSplit( - toHTMLString( { - value: before, - multilineTag, - } ), - ! isAfterOriginal - ) + onSplit( toHTMLString( { value: before } ), ! isAfterOriginal ) ); lastPastedBlockIndex += 1; } @@ -60,13 +48,7 @@ export function splitValue( { // the enter key. if ( ! hasPastedBlocks || ! isEmpty( after ) ) { blocks.push( - onSplit( - toHTMLString( { - value: after, - multilineTag, - } ), - isAfterOriginal - ) + onSplit( toHTMLString( { value: after } ), isAfterOriginal ) ); } 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 623203fb687df0..08c5abf9d7b6bc 100644 --- a/packages/block-editor/src/components/rich-text/use-enter.js +++ b/packages/block-editor/src/components/rich-text/use-enter.js @@ -4,11 +4,7 @@ import { useRef } from '@wordpress/element'; import { useRefEffect } from '@wordpress/compose'; import { ENTER } from '@wordpress/keycodes'; -import { - insert, - __unstableIsEmptyLine as isEmptyLine, - __unstableInsertLineSeparator as insertLineSeparator, -} from '@wordpress/rich-text'; +import { insert } from '@wordpress/rich-text'; import { getBlockTransforms, findTransform } from '@wordpress/blocks'; import { useDispatch } from '@wordpress/data'; @@ -37,7 +33,6 @@ export function useEnter( props ) { value, onReplace, onSplit, - multilineTag, onChange, disableLineBreaks, onSplitAtEnd, @@ -67,40 +62,22 @@ export function useEnter( props ) { } } - if ( multilineTag ) { - if ( event.shiftKey ) { - if ( ! disableLineBreaks ) { - onChange( insert( _value, '\n' ) ); - } - } else if ( canSplit && isEmptyLine( _value ) ) { - splitValue( { - value: _value, - onReplace, - onSplit, - multilineTag, - } ); - } else { - onChange( insertLineSeparator( _value ) ); - } - } else { - const { text, start, end } = _value; - const canSplitAtEnd = - onSplitAtEnd && start === end && end === text.length; + const { text, start, end } = _value; + const canSplitAtEnd = + onSplitAtEnd && start === end && end === text.length; - if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) { - if ( ! disableLineBreaks ) { - onChange( insert( _value, '\n' ) ); - } - } else if ( ! canSplit && canSplitAtEnd ) { - onSplitAtEnd(); - } else if ( canSplit ) { - splitValue( { - value: _value, - onReplace, - onSplit, - multilineTag, - } ); + if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) { + if ( ! disableLineBreaks ) { + onChange( insert( _value, '\n' ) ); } + } else if ( ! canSplit && canSplitAtEnd ) { + onSplitAtEnd(); + } else if ( canSplit ) { + splitValue( { + value: _value, + onReplace, + onSplit, + } ); } } diff --git a/packages/block-editor/src/components/rich-text/use-paste-handler.js b/packages/block-editor/src/components/rich-text/use-paste-handler.js index d64d7ca6b15bb5..6ebd33e507d6c3 100644 --- a/packages/block-editor/src/components/rich-text/use-paste-handler.js +++ b/packages/block-editor/src/components/rich-text/use-paste-handler.js @@ -9,13 +9,7 @@ import { findTransform, getBlockTransforms, } from '@wordpress/blocks'; -import { - isEmpty, - insert, - create, - replace, - __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR, -} from '@wordpress/rich-text'; +import { isEmpty, insert, create } from '@wordpress/rich-text'; import { isURL } from '@wordpress/url'; /** @@ -27,23 +21,6 @@ import { shouldDismissPastedFiles } from '../../utils/pasting'; /** @typedef {import('@wordpress/rich-text').RichTextValue} RichTextValue */ -/** - * Replaces line separators with line breaks if not multiline. - * Replaces line breaks with line separators if multiline. - * - * @param {RichTextValue} value Value to adjust. - * @param {boolean} isMultiline Whether to adjust to multiline or not. - * - * @return {RichTextValue} Adjusted value. - */ -function adjustLines( value, isMultiline ) { - if ( isMultiline ) { - return replace( value, /\n+/g, LINE_SEPARATOR ); - } - - return replace( value, new RegExp( LINE_SEPARATOR, 'g' ), '\n' ); -} - export function usePasteHandler( props ) { const propsRef = useRef( props ); propsRef.current = props; @@ -59,7 +36,6 @@ export function usePasteHandler( props ) { onReplace, onSplit, __unstableEmbedURLOnPaste, - multilineTag, preserveWhiteSpace, pastePlainText, } = propsRef.current; @@ -178,7 +154,6 @@ export function usePasteHandler( props ) { pastedBlocks: blocks, onReplace, onSplit, - multilineTag, } ); } @@ -220,12 +195,7 @@ export function usePasteHandler( props ) { } ); if ( typeof content === 'string' ) { - let valueToInsert = create( { html: content } ); - - // If the content should be multiline, we should process text - // separated by a line break as separate lines. - valueToInsert = adjustLines( valueToInsert, !! multilineTag ); - + const valueToInsert = create( { html: content } ); addActiveFormats( valueToInsert, value.activeFormats ); onChange( insert( value, valueToInsert ) ); } else if ( content.length > 0 ) { @@ -237,7 +207,6 @@ export function usePasteHandler( props ) { pastedBlocks: content, onReplace, onSplit, - multilineTag, } ); } } diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js index 66749753a9b802..4a19d76d1a4723 100644 --- a/packages/block-editor/src/store/utils.js +++ b/packages/block-editor/src/store/utils.js @@ -6,14 +6,7 @@ * @return {Object} The mapped object. */ export function mapRichTextSettings( attributeDefinition ) { - const { - multiline: multilineTag, - __unstableMultilineWrapperTags: multilineWrapperTags, - __unstablePreserveWhiteSpace: preserveWhiteSpace, - } = attributeDefinition; - return { - multilineTag, - multilineWrapperTags, - preserveWhiteSpace, - }; + const { __unstablePreserveWhiteSpace: preserveWhiteSpace } = + attributeDefinition; + return { preserveWhiteSpace }; } diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js index 46ac8d2f13708b..a936aab8680b7e 100644 --- a/packages/block-library/src/pullquote/deprecated.js +++ b/packages/block-library/src/pullquote/deprecated.js @@ -14,12 +14,6 @@ import { useBlockProps, } from '@wordpress/block-editor'; import { select } from '@wordpress/data'; -import { - create, - replace, - toHTMLString, - __UNSTABLE_LINE_SEPARATOR, -} from '@wordpress/rich-text'; /** * Internal dependencies @@ -64,13 +58,14 @@ function parseBorderColor( styleString ) { } function multilineToInline( value ) { - return toHTMLString( { - value: replace( - create( { html: value, multilineTag: 'p' } ), - new RegExp( __UNSTABLE_LINE_SEPARATOR, 'g' ), - '\n' - ), - } ); + value = value || `

`; + const padded = `

${ value }

`; + const values = padded.split( `

` ); + + values.shift(); + values.pop(); + + return values.join( '\n' ); } const v5 = { diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index b88d0ffea6b521..84f33bc3afaf19 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -151,7 +151,7 @@ _Returns_ ### create -Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any input, an empty value will be created. If `multilineTag` is provided, any content of direct children whose type matches `multilineTag` will be separated by two newlines. The optional functions can be used to filter out content. +Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any input, an empty value will be created. The optional functions can be used to filter out content. A value will have the following shape, which you are strongly encouraged not to modify without the use of helper functions: @@ -174,8 +174,6 @@ _Parameters_ - _$1.text_ `[string]`: Text to create value from. - _$1.html_ `[string]`: HTML to create value from. - _$1.range_ `[Range]`: Range to create value from. -- _$1.multilineTag_ `[string]`: Multiline tag if the structure is multiline. -- _$1.multilineWrapperTags_ `[Array]`: Tags where lines can be found if nesting is possible. - _$1.preserveWhiteSpace_ `[boolean]`: Whether or not to collapse white space characters. - _$1.\_\_unstableIsEditableTree_ `[boolean]`: @@ -416,13 +414,12 @@ _Returns_ ### toHTMLString -Create an HTML string from a Rich Text value. If a `multilineTag` is provided, text separated by a line separator will be wrapped in it. +Create an HTML string from a Rich Text value. _Parameters_ - _$1_ `Object`: Named argements. - _$1.value_ `RichTextValue`: Rich text value. -- _$1.multilineTag_ `[string]`: Multiline tag. - _$1.preserveWhiteSpace_ `[boolean]`: Whether or not to use newline characters for line breaks. _Returns_ diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 2b437e4436777b..4aaa0b88ebc148 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -28,7 +28,6 @@ export function useRichText( { preserveWhiteSpace, onSelectionChange, onChange, - __unstableMultilineTag: multilineTag, __unstableDisableFormats: disableFormats, __unstableIsSelected: isSelected, __unstableDependencies = [], @@ -51,9 +50,6 @@ export function useRichText( { return create( { element: ref.current, range, - multilineTag, - multilineWrapperTags: - multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, __unstableIsEditableTree: true, preserveWhiteSpace, } ); @@ -63,9 +59,6 @@ export function useRichText( { apply( { value: newRecord, current: ref.current, - multilineTag, - multilineWrapperTags: - multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, prepareEditableTree: __unstableAddInvisibleFormats, __unstableDomOnly: domOnly, placeholder, @@ -80,9 +73,6 @@ export function useRichText( { _value.current = value; record.current = create( { html: value, - multilineTag, - multilineWrapperTags: - multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, preserveWhiteSpace, } ); if ( disableFormats ) { @@ -149,7 +139,6 @@ export function useRichText( { formats: __unstableBeforeSerialize( newRecord ), } : newRecord, - multilineTag, preserveWhiteSpace, } ); } @@ -179,7 +168,6 @@ export function useRichText( { formats: __unstableBeforeSerialize( newRecord ), } : newRecord, - multilineTag, preserveWhiteSpace, } ); @@ -227,13 +215,12 @@ export function useRichText( { ref, useDefaultStyle(), useBoundaryStyle( { record } ), - useCopyHandler( { record, multilineTag, preserveWhiteSpace } ), + useCopyHandler( { record, preserveWhiteSpace } ), useSelectObject(), useFormatBoundaries( { record, applyRecord } ), useDelete( { createRecord, handleChange, - multilineTag, } ), useInputAndSelection( { record, diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 14fd806d95b3a6..e4beb09acaa443 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -40,10 +40,9 @@ import { getActiveFormat } from '../get-active-format'; import { getActiveFormats } from '../get-active-formats'; import { insert } from '../insert'; import { getTextContent } from '../get-text-content'; -import { isEmpty, isEmptyLine } from '../is-empty'; +import { isEmpty } from '../is-empty'; import { create } from '../create'; import { toHTMLString } from '../to-html-string'; -import { removeLineSeparator } from '../remove-line-separator'; import { isCollapsed } from '../is-collapsed'; import { remove } from '../remove'; import { getFormatColors } from '../get-format-colors'; @@ -436,7 +435,7 @@ export class RichText extends Component { } const isReverse = keyCode === BACKSPACE; - const { onDelete, __unstableMultilineTag: multilineTag } = this.props; + const { onDelete } = this.props; this.lastEventCount = event.nativeEvent.eventCount; this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; @@ -452,24 +451,6 @@ export class RichText extends Component { return; } - if ( multilineTag ) { - if ( - isReverse && - value.start === 0 && - value.end === 0 && - isEmptyLine( value ) - ) { - newValue = removeLineSeparator( value, ! isReverse ); - } else { - newValue = removeLineSeparator( value, isReverse ); - } - if ( newValue ) { - this.onFormatChange( newValue ); - event.preventDefault(); - return; - } - } - // Only process delete if the key press occurs at an uncollapsed edge. if ( ! onDelete || diff --git a/packages/rich-text/src/component/use-copy-handler.js b/packages/rich-text/src/component/use-copy-handler.js index b25e64428a1b4d..c62d83351971c3 100644 --- a/packages/rich-text/src/component/use-copy-handler.js +++ b/packages/rich-text/src/component/use-copy-handler.js @@ -17,8 +17,7 @@ export function useCopyHandler( props ) { propsRef.current = props; return useRefEffect( ( element ) => { function onCopy( event ) { - const { record, multilineTag, preserveWhiteSpace } = - propsRef.current; + const { record, preserveWhiteSpace } = propsRef.current; const { ownerDocument } = element; if ( isCollapsed( record.current ) || @@ -33,7 +32,6 @@ export function useCopyHandler( props ) { let html = toHTMLString( { value: selectedRecord, - multilineTag, preserveWhiteSpace, } ); diff --git a/packages/rich-text/src/component/use-delete.js b/packages/rich-text/src/component/use-delete.js index 3694db42c41b39..69ccd0ee0dc0b6 100644 --- a/packages/rich-text/src/component/use-delete.js +++ b/packages/rich-text/src/component/use-delete.js @@ -9,8 +9,6 @@ import { BACKSPACE, DELETE } from '@wordpress/keycodes'; * Internal dependencies */ import { remove } from '../remove'; -import { removeLineSeparator } from '../remove-line-separator'; -import { isEmptyLine } from '../is-empty'; export function useDelete( props ) { const propsRef = useRef( props ); @@ -18,8 +16,7 @@ export function useDelete( props ) { return useRefEffect( ( element ) => { function onKeyDown( event ) { const { keyCode } = event; - const { createRecord, handleChange, multilineTag } = - propsRef.current; + const { createRecord, handleChange } = propsRef.current; if ( event.defaultPrevented ) { return; @@ -31,34 +28,11 @@ export function useDelete( props ) { const currentValue = createRecord(); const { start, end, text } = currentValue; - const isReverse = keyCode === BACKSPACE; // Always handle full content deletion ourselves. if ( start === 0 && end !== 0 && end === text.length ) { handleChange( remove( currentValue ) ); event.preventDefault(); - return; - } - - if ( multilineTag ) { - let newValue; - - // Check to see if we should remove the first item if empty. - if ( - isReverse && - currentValue.start === 0 && - currentValue.end === 0 && - isEmptyLine( currentValue ) - ) { - newValue = removeLineSeparator( currentValue, ! isReverse ); - } else { - newValue = removeLineSeparator( currentValue, isReverse ); - } - - if ( newValue ) { - handleChange( newValue ); - event.preventDefault(); - } } } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index fa2befc603b7e4..793bdca77f71d5 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -9,11 +9,7 @@ import { select } from '@wordpress/data'; import { store as richTextStore } from './store'; import { createElement } from './create-element'; import { mergePair } from './concat'; -import { - LINE_SEPARATOR, - OBJECT_REPLACEMENT_CHARACTER, - ZWNBSP, -} from './special-characters'; +import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters'; /** @typedef {import('./types').RichTextValue} RichTextValue */ @@ -111,10 +107,8 @@ function toFormat( { tagName, attributes } ) { /** * Create a RichText value from an `Element` tree (DOM), an HTML string or a * plain text string, with optionally a `Range` object to set the selection. If - * called without any input, an empty value will be created. If - * `multilineTag` is provided, any content of direct children whose type matches - * `multilineTag` will be separated by two newlines. The optional functions can - * be used to filter out content. + * called without any input, an empty value will be created. The optional + * functions can be used to filter out content. * * A value will have the following shape, which you are strongly encouraged not * to modify without the use of helper functions: @@ -141,12 +135,8 @@ function toFormat( { tagName, attributes } ) { * @param {string} [$1.text] Text to create value from. * @param {string} [$1.html] HTML to create value from. * @param {Range} [$1.range] Range to create value from. - * @param {string} [$1.multilineTag] Multiline tag if the structure is - * multiline. - * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if - * nesting is possible. - * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white - * space characters. + * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse + * white space characters. * @param {boolean} [$1.__unstableIsEditableTree] * * @return {RichTextValue} A rich text value. @@ -156,8 +146,6 @@ export function create( { text, html, range, - multilineTag, - multilineWrapperTags, __unstableIsEditableTree: isEditableTree, preserveWhiteSpace, } = {} ) { @@ -179,20 +167,9 @@ export function create( { return createEmptyValue(); } - if ( ! multilineTag ) { - return createFromElement( { - element, - range, - isEditableTree, - preserveWhiteSpace, - } ); - } - - return createFromMultilineElement( { + return createFromElement( { element, range, - multilineTag, - multilineWrapperTags, isEditableTree, preserveWhiteSpace, } ); @@ -317,16 +294,11 @@ export function removeReservedCharacters( string ) { /** * Creates a Rich Text value from a DOM element and range. * - * @param {Object} $1 Named argements. - * @param {Element} [$1.element] Element to create value from. - * @param {Range} [$1.range] Range to create value from. - * @param {string} [$1.multilineTag] Multiline tag if the structure is - * multiline. - * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if - * nesting is possible. - * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white - * space characters. - * @param {Array} [$1.currentWrapperTags] + * @param {Object} $1 Named argements. + * @param {Element} [$1.element] Element to create value from. + * @param {Range} [$1.range] Range to create value from. + * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white + * space characters. * @param {boolean} [$1.isEditableTree] * * @return {RichTextValue} A rich text value. @@ -334,9 +306,6 @@ export function removeReservedCharacters( string ) { function createFromElement( { element, range, - multilineTag, - multilineWrapperTags, - currentWrapperTags = [], isEditableTree, preserveWhiteSpace, } ) { @@ -444,30 +413,9 @@ function createFromElement( { if ( format ) delete format.formatType; - if ( - multilineWrapperTags && - multilineWrapperTags.indexOf( tagName ) !== -1 - ) { - const value = createFromMultilineElement( { - element: node, - range, - multilineTag, - multilineWrapperTags, - currentWrapperTags: [ ...currentWrapperTags, format ], - isEditableTree, - preserveWhiteSpace, - } ); - - accumulateSelection( accumulator, node, range, value ); - mergePair( accumulator, value ); - continue; - } - const value = createFromElement( { element: node, range, - multilineTag, - multilineWrapperTags, isEditableTree, preserveWhiteSpace, } ); @@ -516,79 +464,6 @@ function createFromElement( { return accumulator; } -/** - * Creates a rich text value from a DOM element and range that should be - * multiline. - * - * @param {Object} $1 Named argements. - * @param {Element} [$1.element] Element to create value from. - * @param {Range} [$1.range] Range to create value from. - * @param {string} [$1.multilineTag] Multiline tag if the structure is - * multiline. - * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if - * nesting is possible. - * @param {Array} [$1.currentWrapperTags] Whether to prepend a line - * separator. - * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white - * space characters. - * @param {boolean} [$1.isEditableTree] - * - * @return {RichTextValue} A rich text value. - */ -function createFromMultilineElement( { - element, - range, - multilineTag, - multilineWrapperTags, - currentWrapperTags = [], - isEditableTree, - preserveWhiteSpace, -} ) { - const accumulator = createEmptyValue(); - - if ( ! element || ! element.hasChildNodes() ) { - return accumulator; - } - - const length = element.children.length; - - // Optimise for speed. - for ( let index = 0; index < length; index++ ) { - const node = element.children[ index ]; - - if ( node.nodeName.toLowerCase() !== multilineTag ) { - continue; - } - - const value = createFromElement( { - element: node, - range, - multilineTag, - multilineWrapperTags, - currentWrapperTags, - isEditableTree, - preserveWhiteSpace, - } ); - - // Multiline value text should be separated by a line separator. - if ( index !== 0 || currentWrapperTags.length > 0 ) { - mergePair( accumulator, { - formats: [ , ], - replacements: - currentWrapperTags.length > 0 - ? [ currentWrapperTags ] - : [ , ], - text: LINE_SEPARATOR, - } ); - } - - accumulateSelection( accumulator, node, range, value ); - mergePair( accumulator, value ); - } - - return accumulator; -} - /** * Gets the attributes of an element in object shape. * diff --git a/packages/rich-text/src/get-text-content.js b/packages/rich-text/src/get-text-content.js index f400e4b56497f6..71fc9e7a749f0b 100644 --- a/packages/rich-text/src/get-text-content.js +++ b/packages/rich-text/src/get-text-content.js @@ -1,18 +1,10 @@ /** * Internal dependencies */ -import { - OBJECT_REPLACEMENT_CHARACTER, - LINE_SEPARATOR, -} from './special-characters'; +import { OBJECT_REPLACEMENT_CHARACTER } from './special-characters'; /** @typedef {import('./types').RichTextValue} RichTextValue */ -const pattern = new RegExp( - `[${ OBJECT_REPLACEMENT_CHARACTER }${ LINE_SEPARATOR }]`, - 'g' -); - /** * Get the textual content of a Rich Text value. This is similar to * `Element.textContent`. @@ -22,7 +14,5 @@ const pattern = new RegExp( * @return {string} The text content. */ export function getTextContent( { text } ) { - return text.replace( pattern, ( c ) => - c === OBJECT_REPLACEMENT_CHARACTER ? '' : '\n' - ); + return text.replace( OBJECT_REPLACEMENT_CHARACTER, '' ); } diff --git a/packages/rich-text/src/index.ts b/packages/rich-text/src/index.ts index 1f59320df3a63c..f487845a4cd76f 100644 --- a/packages/rich-text/src/index.ts +++ b/packages/rich-text/src/index.ts @@ -7,21 +7,19 @@ export { getActiveFormats } from './get-active-formats'; export { getActiveObject } from './get-active-object'; export { getTextContent } from './get-text-content'; export { isCollapsed } from './is-collapsed'; -export { isEmpty, isEmptyLine as __unstableIsEmptyLine } from './is-empty'; +export { isEmpty } from './is-empty'; export { join } from './join'; export { registerFormatType } from './register-format-type'; export { removeFormat } from './remove-format'; export { remove } from './remove'; export { replace } from './replace'; export { insert } from './insert'; -export { insertLineSeparator as __unstableInsertLineSeparator } from './insert-line-separator'; export { insertObject } from './insert-object'; export { slice } from './slice'; export { split } from './split'; export { toDom as __unstableToDom } from './to-dom'; export { toHTMLString } from './to-html-string'; export { toggleFormat } from './toggle-format'; -export { LINE_SEPARATOR as __UNSTABLE_LINE_SEPARATOR } from './special-characters'; export { unregisterFormatType } from './unregister-format-type'; export { createElement as __unstableCreateElement } from './create-element'; diff --git a/packages/rich-text/src/insert-line-separator.js b/packages/rich-text/src/insert-line-separator.js deleted file mode 100644 index d7a5aa0f97593f..00000000000000 --- a/packages/rich-text/src/insert-line-separator.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Internal dependencies - */ - -import { insert } from './insert'; -import { LINE_SEPARATOR } from './special-characters'; - -/** @typedef {import('./types').RichTextValue} RichTextValue */ - -/** - * Insert a line break character into a Rich Text value at the given - * `startIndex`. Any content between `startIndex` and `endIndex` will be - * removed. Indices are retrieved from the selection if none are provided. - * - * @param {RichTextValue} value Value to modify. - * @param {number} [startIndex] Start index. - * @param {number} [endIndex] End index. - * - * @return {RichTextValue} A new value with the value inserted. - */ -export function insertLineSeparator( - value, - startIndex = value.start, - endIndex = value.end -) { - const beforeText = value.text.slice( 0, startIndex ); - const previousLineSeparatorIndex = beforeText.lastIndexOf( LINE_SEPARATOR ); - const previousLineSeparatorFormats = - value.replacements[ previousLineSeparatorIndex ]; - let replacements = [ , ]; - - if ( previousLineSeparatorFormats ) { - replacements = [ previousLineSeparatorFormats ]; - } - - const valueToInsert = { - formats: [ , ], - replacements, - text: LINE_SEPARATOR, - }; - - return insert( value, valueToInsert, startIndex, endIndex ); -} diff --git a/packages/rich-text/src/is-empty.js b/packages/rich-text/src/is-empty.js index 7baf296bd2a3d6..86bc64bb36b565 100644 --- a/packages/rich-text/src/is-empty.js +++ b/packages/rich-text/src/is-empty.js @@ -1,8 +1,3 @@ -/** - * Internal dependencies - */ -import { LINE_SEPARATOR } from './special-characters'; - /** @typedef {import('./types').RichTextValue} RichTextValue */ /** @@ -16,34 +11,3 @@ import { LINE_SEPARATOR } from './special-characters'; export function isEmpty( { text } ) { return text.length === 0; } - -/** - * Check if the current collapsed selection is on an empty line in case of a - * multiline value. - * - * @param {RichTextValue} value Value te check. - * - * @return {boolean} True if the line is empty, false if not. - */ -export function isEmptyLine( { text, start, end } ) { - if ( start !== end ) { - return false; - } - - if ( text.length === 0 ) { - return true; - } - - if ( start === 0 && text.slice( 0, 1 ) === LINE_SEPARATOR ) { - return true; - } - - if ( start === text.length && text.slice( -1 ) === LINE_SEPARATOR ) { - return true; - } - - return ( - text.slice( start - 1, end + 1 ) === - `${ LINE_SEPARATOR }${ LINE_SEPARATOR }` - ); -} diff --git a/packages/rich-text/src/remove-line-separator.js b/packages/rich-text/src/remove-line-separator.js deleted file mode 100644 index fa45616a45a72a..00000000000000 --- a/packages/rich-text/src/remove-line-separator.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Internal dependencies - */ - -import { LINE_SEPARATOR } from './special-characters'; -import { isCollapsed } from './is-collapsed'; -import { remove } from './remove'; - -/** @typedef {import('./types').RichTextValue} RichTextValue */ - -/** - * Removes a line separator character, if existing, from a Rich Text value at - * the current indices. If no line separator exists on the indices it will - * return undefined. - * - * @param {RichTextValue} value Value to modify. - * @param {boolean} backward Indicates if are removing from the start - * index or the end index. - * - * @return {RichTextValue|undefined} A new value with the line separator - * removed. Or undefined if no line separator - * is found on the position. - */ -export function removeLineSeparator( value, backward = true ) { - const { replacements, text, start, end } = value; - const collapsed = isCollapsed( value ); - let index = start - 1; - let removeStart = collapsed ? start - 1 : start; - let removeEnd = end; - if ( ! backward ) { - index = end; - removeStart = start; - removeEnd = collapsed ? end + 1 : end; - } - - if ( text[ index ] !== LINE_SEPARATOR ) { - return; - } - - let newValue; - // If the line separator that is about te be removed - // contains wrappers, remove the wrappers first. - if ( collapsed && replacements[ index ] && replacements[ index ].length ) { - const newReplacements = replacements.slice(); - - newReplacements[ index ] = replacements[ index ].slice( 0, -1 ); - newValue = { - ...value, - replacements: newReplacements, - }; - } else { - newValue = remove( value, removeStart, removeEnd ); - } - return newValue; -} diff --git a/packages/rich-text/src/special-characters.js b/packages/rich-text/src/special-characters.js index 078525ec1777e6..a05f614daf94b7 100644 --- a/packages/rich-text/src/special-characters.js +++ b/packages/rich-text/src/special-characters.js @@ -1,8 +1,3 @@ -/** - * Line separator character, used for multiline text. - */ -export const LINE_SEPARATOR = '\u2028'; - /** * Object replacement character, used as a placeholder for objects. */ diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js index cf329d7ef0985e..1853fb7bce585d 100644 --- a/packages/rich-text/src/split.js +++ b/packages/rich-text/src/split.js @@ -2,8 +2,6 @@ * Internal dependencies */ -import { replace } from './replace'; - /** @typedef {import('./types').RichTextValue} RichTextValue */ /** @@ -76,9 +74,5 @@ function splitAtSelection( end: 0, }; - return [ - // Ensure newlines are trimmed. - replace( before, /\u2028+$/, '' ), - replace( after, /^\u2028+/, '' ), - ]; + return [ before, after ]; } diff --git a/packages/rich-text/src/test/create.js b/packages/rich-text/src/test/create.js index 3f79f4f0fb78ef..212496c8cf6b8c 100644 --- a/packages/rich-text/src/test/create.js +++ b/packages/rich-text/src/test/create.js @@ -17,39 +17,28 @@ describe( 'create', () => { require( '../store' ); } ); - spec.forEach( - ( { - description, - multilineTag, - multilineWrapperTags, - html, - createRange, - record, - } ) => { - if ( html === undefined ) { - return; - } + spec.forEach( ( { description, html, createRange, record } ) => { + if ( html === undefined ) { + return; + } - // eslint-disable-next-line jest/valid-title - it( description, () => { - const element = createElement( document, html ); - const range = createRange( element ); - const createdRecord = create( { - element, - range, - multilineTag, - multilineWrapperTags, - } ); - const formatsLength = getSparseArrayLength( record.formats ); - const createdFormatsLength = getSparseArrayLength( - createdRecord.formats - ); - - expect( createdRecord ).toEqual( record ); - expect( createdFormatsLength ).toEqual( formatsLength ); + // eslint-disable-next-line jest/valid-title + it( description, () => { + const element = createElement( document, html ); + const range = createRange( element ); + const createdRecord = create( { + element, + range, } ); - } - ); + const formatsLength = getSparseArrayLength( record.formats ); + const createdFormatsLength = getSparseArrayLength( + createdRecord.formats + ); + + expect( createdRecord ).toEqual( record ); + expect( createdFormatsLength ).toEqual( formatsLength ); + } ); + } ); specWithRegistration.forEach( ( { diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index ae7521e55e25bf..cff9daa3e24ece 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -11,8 +11,6 @@ const em = { type: 'em' }; const strong = { type: 'strong' }; const img = { type: 'img', attributes: { src: '' } }; const a = { type: 'a', attributes: { href: '#' } }; -const ul = { type: 'ul' }; -const ol = { type: 'ol' }; export const spec = [ { @@ -440,200 +438,6 @@ export const spec = [ end: 2, }, }, - { - description: 'should handle empty multiline value', - multilineTag: 'p', - html: '

', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element.firstChild, - endOffset: 0, - endContainer: element.firstChild, - } ), - startPath: [ 0, 0, 0 ], - endPath: [ 0, 0, 0 ], - record: { - start: 0, - end: 0, - formats: [], - replacements: [], - text: '', - }, - }, - { - description: 'should handle multiline value', - multilineTag: 'p', - html: '

one

two

', - createRange: ( element ) => ( { - startOffset: 1, - startContainer: element.querySelector( 'p' ).firstChild, - endOffset: 0, - endContainer: element.lastChild, - } ), - startPath: [ 0, 0, 1 ], - endPath: [ 1, 0, 0 ], - record: { - start: 1, - end: 4, - formats: [ , , , , , , , ], - replacements: [ , , , , , , , ], - text: 'one\u2028two', - }, - }, - { - description: 'should handle multiline list value', - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - html: '
  • one
    • a
    • b
      1. 1
      2. 2
  • three
  • ', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element, - endOffset: 1, - endContainer: element.querySelector( 'ol > li' ).firstChild, - } ), - startPath: [ 0, 0, 0 ], - endPath: [ 0, 1, 1, 1, 0, 0, 1 ], - record: { - start: 0, - end: 9, - formats: [ , , , , , , , , , , , , , , , , , ], - replacements: [ - , - , - , - [ ul ], - , - [ ul ], - , - [ ul, ol ], - , - [ ul, ol ], - , - , - , - , - , - , - , - ], - text: 'one\u2028a\u2028b\u20281\u20282\u2028three', - }, - }, - { - description: 'should handle empty list value', - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - html: '
  • ', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element.firstChild, - endOffset: 0, - endContainer: element.firstChild, - } ), - startPath: [ 0, 0, 0 ], - endPath: [ 0, 0, 0 ], - record: { - start: 0, - end: 0, - formats: [], - replacements: [], - text: '', - }, - }, - { - description: 'should handle nested empty list value', - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - html: '
  • ', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element.querySelector( 'ul > li' ), - endOffset: 0, - endContainer: element.querySelector( 'ul > li' ), - } ), - startPath: [ 0, 2, 0, 0, 0 ], - endPath: [ 0, 2, 0, 0, 0 ], - record: { - start: 1, - end: 1, - formats: [ , ], - replacements: [ [ ul ] ], - text: '\u2028', - }, - }, - { - description: 'should handle middle empty list value', - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - html: '
  • ', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element.firstChild.nextSibling, - endOffset: 0, - endContainer: element.firstChild.nextSibling, - } ), - startPath: [ 1, 1, 1 ], - endPath: [ 1, 1, 1 ], - record: { - start: 1, - end: 1, - formats: [ , , ], - replacements: [ , , ], - text: '\u2028\u2028', - }, - }, - { - description: 'should handle multiline value with empty', - multilineTag: 'p', - html: '

    one

    ', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element.lastChild, - endOffset: 0, - endContainer: element.lastChild, - } ), - startPath: [ 1, 0, 0 ], - endPath: [ 1, 0, 0 ], - record: { - start: 4, - end: 4, - formats: [ , , , , ], - replacements: [ , , , , ], - text: 'one\u2028', - }, - }, - { - description: 'should handle multiline value with element selection', - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - html: '
  • one
  • ', - createRange: ( element ) => ( { - startOffset: 1, - startContainer: element.firstChild, - endOffset: 1, - endContainer: element.firstChild, - } ), - startPath: [ 0, 0, 3 ], - endPath: [ 0, 0, 3 ], - record: { - start: 3, - end: 3, - formats: [ , , , ], - replacements: [ , , , ], - text: 'one', - }, - }, - { - description: 'should ignore formats at line separator', - multilineTag: 'p', - startPath: [], - endPath: [], - record: { - formats: [ [ em ], [ em ], [ em ], [ em ], [ em ], [ em ], [ em ] ], - replacements: [ , , , , , , , ], - text: 'one\u2028two', - }, - }, { description: 'should remove padding', html: ZWNBSP, diff --git a/packages/rich-text/src/test/insert-line-separator.js b/packages/rich-text/src/test/insert-line-separator.js deleted file mode 100644 index 497555cc4a01a0..00000000000000 --- a/packages/rich-text/src/test/insert-line-separator.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * External dependencies - */ -import deepFreeze from 'deep-freeze'; - -/** - * Internal dependencies - */ - -import { insertLineSeparator } from '../insert-line-separator'; -import { LINE_SEPARATOR } from '../special-characters'; -import { getSparseArrayLength } from './helpers'; - -describe( 'insertLineSeparator', () => { - const ol = { type: 'ol' }; - - it( 'should insert line separator at end', () => { - const value = { - formats: [ , ], - replacements: [ , ], - text: '1', - start: 1, - end: 1, - }; - const expected = { - formats: [ , , ], - replacements: [ , , ], - text: `1${ LINE_SEPARATOR }`, - start: 2, - end: 2, - }; - const result = insertLineSeparator( deepFreeze( value ) ); - - expect( result ).not.toBe( value ); - expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); - } ); - - it( 'should insert line separator at start', () => { - const value = { - formats: [ , ], - replacements: [ , ], - text: '1', - start: 0, - end: 0, - }; - const expected = { - formats: [ , , ], - replacements: [ , , ], - text: `${ LINE_SEPARATOR }1`, - start: 1, - end: 1, - }; - const result = insertLineSeparator( deepFreeze( value ) ); - - expect( result ).not.toBe( value ); - expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); - } ); - - it( 'should insert line separator with previous line separator formats', () => { - const value = { - formats: [ , , , , , ], - replacements: [ , , , [ ol ], , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a`, - start: 5, - end: 5, - }; - const expected = { - formats: [ , , , , , , ], - replacements: [ , , , [ ol ], , [ ol ] ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a${ LINE_SEPARATOR }`, - start: 6, - end: 6, - }; - const result = insertLineSeparator( deepFreeze( value ) ); - - expect( result ).not.toBe( value ); - expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); - } ); - - it( 'should insert line separator without formats if previous line separator did not have any', () => { - const value = { - formats: [ , , , , , ], - replacements: [ , , , , , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a`, - start: 5, - end: 5, - }; - const expected = { - formats: [ , , , , , , ], - replacements: [ , , , , , , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a${ LINE_SEPARATOR }`, - start: 6, - end: 6, - }; - const result = insertLineSeparator( deepFreeze( value ) ); - - expect( result ).not.toBe( value ); - expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); - } ); -} ); diff --git a/packages/rich-text/src/test/is-empty.js b/packages/rich-text/src/test/is-empty.js index 0080ba3a0914fb..6cd1d0b6d08275 100644 --- a/packages/rich-text/src/test/is-empty.js +++ b/packages/rich-text/src/test/is-empty.js @@ -2,7 +2,7 @@ * Internal dependencies */ -import { isEmpty, isEmptyLine } from '../is-empty'; +import { isEmpty } from '../is-empty'; describe( 'isEmpty', () => { it( 'should return true', () => { @@ -23,62 +23,3 @@ describe( 'isEmpty', () => { expect( isEmpty( one ) ).toBe( false ); } ); } ); - -describe( 'isEmptyLine', () => { - it( 'should return true', () => { - const one = { - formats: [], - text: '', - start: 0, - end: 0, - }; - const two = { - formats: [ , , ], - text: '\u2028', - start: 0, - end: 0, - }; - const three = { - formats: [ , , ], - text: '\u2028', - start: 1, - end: 1, - }; - const four = { - formats: [ , , , , ], - text: '\u2028\u2028', - start: 1, - end: 1, - }; - const five = { - formats: [ , , , , ], - text: 'a\u2028\u2028b', - start: 2, - end: 2, - }; - - expect( isEmptyLine( one ) ).toBe( true ); - expect( isEmptyLine( two ) ).toBe( true ); - expect( isEmptyLine( three ) ).toBe( true ); - expect( isEmptyLine( four ) ).toBe( true ); - expect( isEmptyLine( five ) ).toBe( true ); - } ); - - it( 'should return false', () => { - const one = { - formats: [ , , , , ], - text: '\u2028a\u2028', - start: 1, - end: 1, - }; - const two = { - formats: [ , , , , ], - text: '\u2028\n', - start: 1, - end: 1, - }; - - expect( isEmptyLine( one ) ).toBe( false ); - expect( isEmptyLine( two ) ).toBe( false ); - } ); -} ); diff --git a/packages/rich-text/src/test/split.js b/packages/rich-text/src/test/split.js index 7de61c1b3efeb8..05a76889ee6864 100644 --- a/packages/rich-text/src/test/split.js +++ b/packages/rich-text/src/test/split.js @@ -112,39 +112,6 @@ describe( 'split', () => { } ); } ); - it( 'should split multiline', () => { - const record = { - formats: [ , , , , , , , , , , ], - replacements: [ , , , , , , , , , , ], - text: 'test\u2028\u2028test', - start: 5, - end: 5, - }; - const expected = [ - { - formats: [ , , , , ], - replacements: [ , , , , ], - text: 'test', - }, - { - formats: [ , , , , ], - replacements: [ , , , , ], - text: 'test', - start: 0, - end: 0, - }, - ]; - const result = split( deepFreeze( record ) ); - - expect( result ).toEqual( expected ); - result.forEach( ( item, index ) => { - expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ).toBe( - getSparseArrayLength( expected[ index ].formats ) - ); - } ); - } ); - it( 'should split search', () => { const record = { start: 6, diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js index 1c6aa50623e951..4f9b2df86cad35 100644 --- a/packages/rich-text/src/test/to-dom.js +++ b/packages/rich-text/src/test/to-dom.js @@ -11,19 +11,16 @@ describe( 'recordToDom', () => { require( '../store' ); } ); - spec.forEach( - ( { description, multilineTag, record, startPath, endPath } ) => { - // eslint-disable-next-line jest/valid-title - it( description, () => { - const { body, selection } = toDom( { - value: record, - multilineTag, - } ); - expect( body ).toMatchSnapshot(); - expect( selection ).toEqual( { startPath, endPath } ); + spec.forEach( ( { description, record, startPath, endPath } ) => { + // eslint-disable-next-line jest/valid-title + it( description, () => { + const { body, selection } = toDom( { + value: record, } ); - } - ); + expect( body ).toMatchSnapshot(); + expect( selection ).toEqual( { startPath, endPath } ); + } ); + } ); } ); describe( 'applyValue', () => { diff --git a/packages/rich-text/src/test/to-html-string.js b/packages/rich-text/src/test/to-html-string.js index 507359e99ddcf0..32360e6805d69f 100644 --- a/packages/rich-text/src/test/to-html-string.js +++ b/packages/rich-text/src/test/to-html-string.js @@ -97,21 +97,6 @@ describe( 'toHTMLString', () => { ); } ); - it( 'should extract recreate HTML 6', () => { - const HTML = '
  • one
    • two
  • three
  • '; - const element = createNode( `
      ${ HTML }
    ` ); - const multilineTag = 'li'; - const multilineWrapperTags = [ 'ul', 'ol' ]; - const value = create( { element, multilineTag, multilineWrapperTags } ); - const result = toHTMLString( { - value, - multilineTag, - multilineWrapperTags, - } ); - - expect( result ).toEqual( HTML ); - } ); - it( 'should serialize neighbouring formats of same type', () => { const HTML = 'aa'; const element = createNode( `

    ${ HTML }

    ` ); diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 305eebaf3e4a6e..e7288e4ba16332 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -104,7 +104,6 @@ function remove( node ) { export function toDom( { value, - multilineTag, prepareEditableTree, isEditableTree = true, placeholder, @@ -134,7 +133,6 @@ export function toDom( { const tree = toTree( { value, - multilineTag, createEmpty, append, getLastChild, @@ -165,13 +163,11 @@ export function toDom( { /** * Create an `Element` tree from a Rich Text value and applies the difference to - * the `Element` tree contained by `current`. If a `multilineTag` is provided, - * text separated by two new lines will be wrapped in an `Element` of that type. + * the `Element` tree contained by `current`. * * @param {Object} $1 Named arguments. * @param {RichTextValue} $1.value Value to apply. * @param {HTMLElement} $1.current The live root node to apply the element tree to. - * @param {string} [$1.multilineTag] Multiline tag. * @param {Function} [$1.prepareEditableTree] Function to filter editorable formats. * @param {boolean} [$1.__unstableDomOnly] Only apply elements, no selection. * @param {string} [$1.placeholder] Placeholder text. @@ -179,7 +175,6 @@ export function toDom( { export function apply( { value, current, - multilineTag, prepareEditableTree, __unstableDomOnly, placeholder, @@ -187,7 +182,6 @@ export function apply( { // Construct a new element tree in memory. const { body, selection } = toDom( { value, - multilineTag, prepareEditableTree, placeholder, doc: current.ownerDocument, diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index 0b2689248afb72..66ae1d82b38450 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -17,21 +17,18 @@ import { toTree } from './to-tree'; /** @typedef {import('./types').RichTextValue} RichTextValue */ /** - * Create an HTML string from a Rich Text value. If a `multilineTag` is - * provided, text separated by a line separator will be wrapped in it. + * Create an HTML string from a Rich Text value. * * @param {Object} $1 Named argements. * @param {RichTextValue} $1.value Rich text value. - * @param {string} [$1.multilineTag] Multiline tag. * @param {boolean} [$1.preserveWhiteSpace] Whether or not to use newline * characters for line breaks. * * @return {string} HTML string. */ -export function toHTMLString( { value, multilineTag, preserveWhiteSpace } ) { +export function toHTMLString( { value, preserveWhiteSpace } ) { const tree = toTree( { value, - multilineTag, preserveWhiteSpace, createEmpty, append, diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 4db974aaad7142..c380570db561de 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -4,11 +4,7 @@ import { getActiveFormats } from './get-active-formats'; import { getFormatType } from './get-format-type'; -import { - LINE_SEPARATOR, - OBJECT_REPLACEMENT_CHARACTER, - ZWNBSP, -} from './special-characters'; +import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters'; function restoreOnAttributes( attributes, isEditableTree ) { if ( isEditableTree ) { @@ -133,7 +129,6 @@ function isEqualUntil( a, b, index ) { export function toTree( { value, - multilineTag, preserveWhiteSpace, createEmpty, append, @@ -151,21 +146,13 @@ export function toTree( { const { formats, replacements, text, start, end } = value; const formatsLength = formats.length + 1; const tree = createEmpty(); - const multilineFormat = { type: multilineTag }; const activeFormats = getActiveFormats( value ); const deepestActiveFormat = activeFormats[ activeFormats.length - 1 ]; - let lastSeparatorFormats; let lastCharacterFormats; let lastCharacter; - // If we're building a multiline tree, start off with a multiline element. - if ( multilineTag ) { - append( append( tree, { type: multilineTag } ), '' ); - lastCharacterFormats = lastSeparatorFormats = [ multilineFormat ]; - } else { - append( tree, '' ); - } + append( tree, '' ); for ( let i = 0; i < formatsLength; i++ ) { const character = text.charAt( i ); @@ -173,62 +160,13 @@ export function toTree( { 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. - if ( multilineTag ) { - if ( character === LINE_SEPARATOR ) { - characterFormats = lastSeparatorFormats = ( - replacements[ i ] || [] - ).reduce( - ( accumulator, format ) => { - accumulator.push( format, multilineFormat ); - return accumulator; - }, - [ multilineFormat ] - ); - } else { - characterFormats = [ - ...lastSeparatorFormats, - ...( characterFormats || [] ), - ]; - } - } - + const characterFormats = formats[ i ]; let pointer = getLastChild( tree ); - if ( shouldInsertPadding && character === LINE_SEPARATOR ) { - let node = pointer; - - while ( ! isText( node ) ) { - node = getLastChild( node ); - } - - append( getParent( node ), ZWNBSP ); - } - - // Set selection for the start of line. - if ( lastCharacter === LINE_SEPARATOR ) { - let node = pointer; - - while ( ! isText( node ) ) { - node = getLastChild( node ); - } - - if ( onStartIndex && start === i ) { - onStartIndex( tree, node ); - } - - if ( onEndIndex && end === i ) { - onEndIndex( tree, node ); - } - } - if ( characterFormats ) { characterFormats.forEach( ( format, formatIndex ) => { if ( @@ -239,11 +177,7 @@ export function toTree( { characterFormats, lastCharacterFormats, formatIndex - ) && - // Do not reuse the last element if the character is a - // line separator. - ( character !== LINE_SEPARATOR || - characterFormats.length - 1 !== formatIndex ) + ) ) { pointer = getLastChild( pointer ); return; @@ -253,9 +187,7 @@ export function toTree( { format; const boundaryClass = - isEditableTree && - character !== LINE_SEPARATOR && - format === deepestActiveFormat; + isEditableTree && format === deepestActiveFormat; const parent = getParent( pointer ); const newNode = append( @@ -278,13 +210,6 @@ export function toTree( { } ); } - // No need for further processing if the character is a line separator. - if ( character === LINE_SEPARATOR ) { - lastCharacterFormats = characterFormats; - lastCharacter = character; - continue; - } - // If there is selection at 0, handle it before characters are inserted. if ( i === 0 ) { if ( onStartIndex && start === 0 ) { From 42a96f537c8ed3d3f1f2762961f0e4ff954a4cf5 Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 8 Sep 2023 21:15:36 +0300 Subject: [PATCH 3/6] remove obsolete snapshots --- .../src/test/__snapshots__/to-dom.js.snap | 124 ------------------ 1 file changed, 124 deletions(-) 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 a0eb7b3cb0a3ce..5e2fbcac00de67 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -201,114 +201,6 @@ exports[`recordToDom should handle double br 1`] = ` `; -exports[`recordToDom should handle empty list value 1`] = ` - -
  • - -  -
  • - -`; - -exports[`recordToDom should handle empty multiline value 1`] = ` - -

    - -  -

    - -`; - -exports[`recordToDom should handle middle empty list value 1`] = ` - -
  • - -  -
  • -
  • - -  -
  • -
  • - -  -
  • - -`; - -exports[`recordToDom should handle multiline list value 1`] = ` - -
  • - one -
      -
    • - a -
    • -
    • - b -
        -
      1. - 1 -
      2. -
      3. - 2 -
      4. -
      -
    • -
    -
  • -
  • - three -
  • - -`; - -exports[`recordToDom should handle multiline value 1`] = ` - -

    - one -

    -

    - two -

    - -`; - -exports[`recordToDom should handle multiline value with element selection 1`] = ` - -
  • - one -
  • - -`; - -exports[`recordToDom should handle multiline value with empty 1`] = ` - -

    - one -

    -

    - -  -

    - -`; - -exports[`recordToDom should handle nested empty list value 1`] = ` - -
  • - -  -
      -
    • - -  -
    • -
    -
  • - -`; - exports[`recordToDom should handle selection before br 1`] = ` a @@ -323,22 +215,6 @@ exports[`recordToDom should handle selection before br 1`] = ` `; -exports[`recordToDom should ignore formats at line separator 1`] = ` - -

    - - one - -

    -

    - - two - - -

    - -`; - exports[`recordToDom should ignore manually added object replacement character 1`] = ` test From 6adc72b915cc2062da60f8b5a71f76063e08b08a Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 8 Sep 2023 22:06:16 +0300 Subject: [PATCH 4/6] Fix pullquote to inline convert --- packages/block-library/src/pullquote/deprecated.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js index a936aab8680b7e..2839a6d2b88042 100644 --- a/packages/block-library/src/pullquote/deprecated.js +++ b/packages/block-library/src/pullquote/deprecated.js @@ -65,7 +65,7 @@ function multilineToInline( value ) { values.shift(); values.pop(); - return values.join( '\n' ); + return values.join( '
    ' ); } const v5 = { From 01558cf117ab54478f535ed5c8acef688e0d0de8 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 11 Sep 2023 14:45:56 +0300 Subject: [PATCH 5/6] Add e2e tests --- .../src/components/rich-text/multiline.js | 4 +- .../rich-text-deprecated-multiline.spec.js | 126 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js diff --git a/packages/block-editor/src/components/rich-text/multiline.js b/packages/block-editor/src/components/rich-text/multiline.js index 16905f08e4d85d..760a7718fd864a 100644 --- a/packages/block-editor/src/components/rich-text/multiline.js +++ b/packages/block-editor/src/components/rich-text/multiline.js @@ -90,7 +90,7 @@ function RichTextMultiline( 2, newValues[ index ] + newValues[ index + 1 ] ); - offset = newValues[ index ].length; + offset = newValues[ index ].length - 1; } else { if ( ! newValues[ index - 1 ] ) return; newValues.splice( @@ -98,7 +98,7 @@ function RichTextMultiline( 2, newValues[ index - 1 ] + newValues[ index ] ); - offset = newValues[ index - 1 ].length; + offset = newValues[ index - 1 ].length - 1; } _onChange( newValues ); selectionChange( diff --git a/test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js b/test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js new file mode 100644 index 00000000000000..9fa3fef903c7fc --- /dev/null +++ b/test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js @@ -0,0 +1,126 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'RichText deprecated multiline', () => { + test.beforeEach( async ( { admin, page, editor } ) => { + await admin.createNewPost(); + await page.evaluate( () => { + const registerBlockType = window.wp.blocks.registerBlockType; + const { useBlockProps, RichText } = window.wp.blockEditor; + const el = window.wp.element.createElement; + registerBlockType( 'core/rich-text-deprecated-multiline', { + apiVersion: 3, + title: 'Deprecated RichText multiline', + attributes: { + value: { + type: 'string', + source: 'html', + selector: 'blockquote', + }, + }, + edit: function Edit( { attributes, setAttributes } ) { + return el( RichText, { + ...useBlockProps(), + tagName: 'blockquote', + multiline: 'p', + value: attributes.value, + onChange( value ) { + setAttributes( { value } ); + }, + } ); + }, + save( { attributes } ) { + return el( RichText.Content, { + tagName: 'blockquote', + multiline: 'p', + value: attributes.value, + } ); + }, + } ); + } ); + await editor.insertBlock( { + name: 'core/rich-text-deprecated-multiline', + } ); + await page.keyboard.press( 'ArrowDown' ); + } ); + + test( 'should save', async ( { page, editor } ) => { + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: 'core/rich-text-deprecated-multiline', + attributes: { + value: '

    1

    2

    ', + }, + }, + ] ); + + // Test serialised output. + expect( await editor.getEditedPostContent() ).toBe( + ` +

    1

    2

    +` + ); + } ); + + test( 'should split in middle', async ( { page, editor } ) => { + await page.keyboard.type( '12' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Enter' ); + // Test selection after split. + await page.keyboard.type( '‸' ); + + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: 'core/rich-text-deprecated-multiline', + attributes: { + value: '

    1

    ‸2

    ', + }, + }, + ] ); + } ); + + test( 'should merge two lines', async ( { page, editor } ) => { + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Backspace' ); + // Test selection after merge. + await page.keyboard.type( '‸' ); + + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: 'core/rich-text-deprecated-multiline', + attributes: { + value: '

    1‸2

    ', + }, + }, + ] ); + } ); + + test( 'should merge two lines (forward)', async ( { page, editor } ) => { + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Delete' ); + // Test selection after merge. + await page.keyboard.type( '‸' ); + + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: 'core/rich-text-deprecated-multiline', + attributes: { + value: '

    1‸2

    ', + }, + }, + ] ); + } ); +} ); From 960c8ac4812d84d5a00291b321cfda12f758ab3d Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 11 Sep 2023 15:19:26 +0300 Subject: [PATCH 6/6] Remove some lingering mentions, log change --- .../block-api/block-attributes.md | 33 ------------------- packages/block-editor/CHANGELOG.md | 4 +++ packages/editor/README.md | 5 --- schemas/json/block.json | 4 --- 4 files changed, 4 insertions(+), 42 deletions(-) diff --git a/docs/reference-guides/block-api/block-attributes.md b/docs/reference-guides/block-api/block-attributes.md index eafc73c79938f3..0fbbeeb13680e6 100644 --- a/docs/reference-guides/block-api/block-attributes.md +++ b/docs/reference-guides/block-api/block-attributes.md @@ -305,39 +305,6 @@ Attribute available in the block: { "content": "The inner text of the figcaption element" } ``` -Use the `multiline` property to extract the inner HTML of matching tag names for the use in `RichText` with the `multiline` prop. - -_Example_: Extract the `content` attribute from a blockquote element found in the block's markup. - -Saved content: -```html -
    - Block Content - -
    -

    First line

    -

    Second line

    -
    -
    -``` - -Attribute definition: -```js -{ - content: { - type: 'string', - source: 'html', - multiline: 'p', - selector: 'blockquote', - } -} -``` - -Attribute available in the block: -```js -{ "content": "

    First line

    Second line

    " } -``` - ### `query` source Use `query` to extract an array of values from markup. Entries of the array are determined by the `selector` argument, where each matched element within the block will have an entry structured corresponding to the second argument, an object of attribute sources. diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 63a6281f78b426..5441536e1ab112 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- The Deprecated multiline prop on RichText will now fall back to using multiple + rich text instances instead of a single multiline instance. The prop remains + deprecated. + ## 12.9.0 (2023-08-31) ### Enhancements diff --git a/packages/editor/README.md b/packages/editor/README.md index dc8ec694de07a6..157136b28d0d66 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -91,11 +91,6 @@ The following properties (non-exhaustive list) are made available: - `placeholder: string` - A text hint to be shown to the user when the field value is empty, similar to the [`input` and `textarea` attribute of the same name](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/HTML5_updates#The_placeholder_attribute). -- `multiline: String` - A tag name to use for the tag that should be inserted - when Enter is pressed. For example: `li` in a list block, and `p` for a - block that can contain multiple paragraphs. The default is that only inline - elements are allowed to be used in inserted into the text, effectively - disabling the behavior of the "Enter" key. Example: diff --git a/schemas/json/block.json b/schemas/json/block.json index f20fb5b0dea972..b181bf2d9ab403 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -173,10 +173,6 @@ "type": "string", "description": "Use an attribute source to extract the value from an attribute in the markup. The attribute is specified by the attribute field, which must be supplied.\n\nExample: Extract the src attribute from an image found in the block’s markup." }, - "multiline": { - "type": "string", - "description": "Use the multiline property to extract the inner HTML of matching tag names for the use in RichText with the multiline prop." - }, "query": { "type": "object", "description": "Use query to extract an array of values from markup. Entries of the array are determined by the selector argument, where each matched element within the block will have an entry structured corresponding to the second argument, an object of attribute sources."