From c20d7afebf0e8d471f3dfd9406addb4a540af280 Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 14 Nov 2023 11:45:32 +0200 Subject: [PATCH 01/21] RichText: pass value to store (squashed) --- package-lock.json | 2 + .../src/components/rich-text/content.js | 2 +- .../rich-text/get-rich-text-values.js | 7 +- .../rich-text/native/index.native.js | 2 +- .../components/rich-text/use-input-rules.js | 4 +- .../components/rich-text/use-paste-handler.js | 7 +- packages/block-editor/src/utils/selection.js | 9 ++- .../block-library/src/paragraph/block.json | 5 +- .../__snapshots__/transforms.native.js.snap | 2 +- .../__snapshots__/transforms.native.js.snap | 2 +- packages/blocks/package.json | 1 + packages/blocks/src/api/matchers.js | 12 ++++ .../src/api/parser/get-block-attributes.js | 22 ++++++- packages/blocks/src/api/utils.js | 43 ++++++++++-- .../src/footnotes/get-footnotes-order.js | 2 +- packages/core-data/src/footnotes/index.js | 18 +++-- packages/rich-text/README.md | 5 ++ packages/rich-text/src/component/index.js | 20 ++++-- packages/rich-text/src/create.js | 66 ++++++++++++++++++- packages/rich-text/src/index.ts | 2 +- schemas/json/block.json | 2 + ...al-selection-of-text-blocks-2-chromium.txt | 2 +- ...ith-other-blocks-in-between-2-chromium.txt | 2 +- ...al-selection-of-text-blocks-2-chromium.txt | 2 +- ...ith-other-blocks-in-between-2-chromium.txt | 2 +- .../fixtures/blocks/core__cover.json | 2 +- .../blocks/core__cover.serialized.html | 4 +- .../blocks/core__cover__deprecated-6.json | 2 +- .../core__cover__deprecated-6.serialized.html | 4 +- .../blocks/core__cover__deprecated-7.json | 2 +- .../core__cover__deprecated-7.serialized.html | 4 +- .../blocks/core__cover__deprecated-8.json | 2 +- .../core__cover__deprecated-8.serialized.html | 4 +- .../blocks/core__cover__gradient-image.json | 2 +- ...ore__cover__gradient-image.serialized.html | 2 +- .../blocks/core__cover__video-overlay.json | 2 +- ...core__cover__video-overlay.serialized.html | 4 +- .../fixtures/blocks/core__cover__video.json | 2 +- .../blocks/core__cover__video.serialized.html | 4 +- .../core__text__converts-to-paragraph.json | 2 +- ...ext__converts-to-paragraph.serialized.html | 2 +- .../documents/ms-word-online-out.html | 6 +- .../documents/ms-word-styled-out.html | 2 +- .../documents/slack-paragraphs-out.html | 2 +- .../fixtures/documents/slack-quote-out.html | 2 +- .../non-matched-tags-handling.test.js | 6 +- 46 files changed, 228 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4b4aeb47f6bad..f65b8101b9da90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54674,6 +54674,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", "@wordpress/shortcode": "file:../shortcode", "change-case": "^4.1.2", "colord": "^2.7.0", @@ -70139,6 +70140,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", "@wordpress/shortcode": "file:../shortcode", "change-case": "^4.1.2", "colord": "^2.7.0", diff --git a/packages/block-editor/src/components/rich-text/content.js b/packages/block-editor/src/components/rich-text/content.js index 9762582f86f141..cb13dd01213418 100644 --- a/packages/block-editor/src/components/rich-text/content.js +++ b/packages/block-editor/src/components/rich-text/content.js @@ -29,7 +29,7 @@ export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { value = `<${ MultilineTag }>`; } - const content = { value }; + const content = { value?.valueOf() }; if ( Tag ) { const { format, ...restProps } = props; diff --git a/packages/block-editor/src/components/rich-text/get-rich-text-values.js b/packages/block-editor/src/components/rich-text/get-rich-text-values.js index bd1c62ea5e6f61..88e6013bca4ddd 100644 --- a/packages/block-editor/src/components/rich-text/get-rich-text-values.js +++ b/packages/block-editor/src/components/rich-text/get-rich-text-values.js @@ -6,6 +6,7 @@ import { getSaveElement, __unstableGetBlockProps as getBlockProps, } from '@wordpress/blocks'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies @@ -95,5 +96,9 @@ export function getRichTextValues( blocks = [] ) { const values = []; addValuesForBlocks( values, blocks ); getBlockProps.skipFilters = false; - return values; + return values.map( ( value ) => + value instanceof RichTextData + ? value + : new RichTextData( { html: value } ) + ); } diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 951d52ece6d694..130e8be10238a8 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -191,7 +191,7 @@ export class RichText extends Component { selectionEnd: end, colorPalette, } = this.props; - const { value } = this.props; + const { value = '' } = this.props; const currentValue = this.formatToValue( value ); const { formats, replacements, text } = currentValue; diff --git a/packages/block-editor/src/components/rich-text/use-input-rules.js b/packages/block-editor/src/components/rich-text/use-input-rules.js index 5aa47e7c7b4d74..607165d2ead62d 100644 --- a/packages/block-editor/src/components/rich-text/use-input-rules.js +++ b/packages/block-editor/src/components/rich-text/use-input-rules.js @@ -28,7 +28,9 @@ function findSelection( blocks ) { if ( attributeKey ) { blocks[ i ].attributes[ attributeKey ] = blocks[ i ].attributes[ attributeKey - ].replace( START_OF_SELECTED_AREA, '' ); + ] + .toString() + .replace( START_OF_SELECTED_AREA, '' ); return [ blocks[ i ].clientId, attributeKey, 0, 0 ]; } 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 1302e2d0dce469..5baf5858701219 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 @@ -8,7 +8,7 @@ import { findTransform, getBlockTransforms, } from '@wordpress/blocks'; -import { isEmpty, insert, create } from '@wordpress/rich-text'; +import { isEmpty, insert, create, RichTextData } from '@wordpress/rich-text'; import { isURL } from '@wordpress/url'; /** @@ -134,7 +134,10 @@ export function usePasteHandler( props ) { tagName, } ); - if ( typeof content === 'string' ) { + if ( + typeof content === 'string' || + content instanceof RichTextData + ) { const transformed = formatTypes.reduce( ( accumlator, { __unstablePasteRule } ) => { // Only allow one transform. diff --git a/packages/block-editor/src/utils/selection.js b/packages/block-editor/src/utils/selection.js index 68c634d591c5e9..733ec8f1c37192 100644 --- a/packages/block-editor/src/utils/selection.js +++ b/packages/block-editor/src/utils/selection.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { RichTextData } from '@wordpress/rich-text'; + /** * A robust way to retain selection position through various * transforms is to insert a special character at the position and @@ -19,8 +24,8 @@ export function retrieveSelectedAttribute( blockAttributes ) { return Object.keys( blockAttributes ).find( ( name ) => { const value = blockAttributes[ name ]; return ( - typeof value === 'string' && - value.indexOf( START_OF_SELECTED_AREA ) !== -1 + ( typeof value === 'string' || value instanceof RichTextData ) && + value.toString().indexOf( START_OF_SELECTED_AREA ) !== -1 ); } ); } diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index 85f56f4a838f50..3fe4fbb34e1029 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -13,10 +13,9 @@ "type": "string" }, "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "p", - "default": "", "__experimentalRole": "content" }, "dropCap": { diff --git a/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap index 81adef5b228388..e39fc8b92b2401 100644 --- a/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap @@ -26,6 +26,6 @@ exports[`Preformatted block transforms to Group block 1`] = ` exports[`Preformatted block transforms to Paragraph block 1`] = ` " -

Some preformatted text...
And more!

+

Some preformatted text...
And more!

" `; diff --git a/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap index 5570a6cf5d67d9..46e536a0f19f36 100644 --- a/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap @@ -20,6 +20,6 @@ exports[`Verse block transforms to Group block 1`] = ` exports[`Verse block transforms to Paragraph block 1`] = ` " -

Come
Home.

+

Come
Home.

" `; diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 961cb338d73378..928d9d94740b4f 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -42,6 +42,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", "@wordpress/shortcode": "file:../shortcode", "change-case": "^4.1.2", "colord": "^2.7.0", diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index 7a6ac84891658a..a6ae94efe20630 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -3,6 +3,11 @@ */ export { attr, prop, text, query } from 'hpq'; +/** + * WordPress dependencies + */ +import { RichTextData } from '@wordpress/rich-text'; + /** * Internal dependencies */ @@ -41,3 +46,10 @@ export function html( selector, multilineTag ) { return match.innerHTML; }; } + +export const richText = ( selector ) => ( el ) => { + return new RichTextData( { + element: selector ? el.querySelector( selector ) : el, + preserveWhiteSpace: false, + } ); +}; diff --git a/packages/blocks/src/api/parser/get-block-attributes.js b/packages/blocks/src/api/parser/get-block-attributes.js index cc81c108005529..1d687cedecc63c 100644 --- a/packages/blocks/src/api/parser/get-block-attributes.js +++ b/packages/blocks/src/api/parser/get-block-attributes.js @@ -9,12 +9,22 @@ import memoize from 'memize'; */ import { pipe } from '@wordpress/compose'; import { applyFilters } from '@wordpress/hooks'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies */ -import { attr, html, text, query, node, children, prop } from '../matchers'; -import { normalizeBlockType } from '../utils'; +import { + attr, + html, + text, + query, + node, + children, + prop, + richText, +} from '../matchers'; +import { normalizeBlockType, getDefault } from '../utils'; /** * Higher-order hpq matcher which enhances an attribute matcher to return true @@ -58,6 +68,9 @@ export const toBooleanAttributeMatcher = ( matcher ) => */ export function isOfType( value, type ) { switch ( type ) { + case 'rich-text': + return value instanceof RichTextData; + case 'string': return typeof value === 'string'; @@ -134,6 +147,7 @@ export function getBlockAttribute( case 'property': case 'html': case 'text': + case 'rich-text': case 'children': case 'node': case 'query': @@ -152,7 +166,7 @@ export function getBlockAttribute( } if ( value === undefined ) { - value = attributeSchema.default; + value = getDefault( attributeSchema ); } return value; @@ -211,6 +225,8 @@ export const matcherFromSource = memoize( ( sourceConfig ) => { return html( sourceConfig.selector, sourceConfig.multiline ); case 'text': return text( sourceConfig.selector ); + case 'rich-text': + return richText( sourceConfig.selector ); case 'children': return children( sourceConfig.selector ); case 'node': diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index c43445c6272264..35096f6e68240f 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -11,6 +11,7 @@ import a11yPlugin from 'colord/plugins/a11y'; import { Component, isValidElement } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies @@ -47,8 +48,12 @@ export function isUnmodifiedBlock( block ) { const newBlock = isUnmodifiedBlock[ block.name ]; const blockType = getBlockType( block.name ); - return Object.keys( blockType?.attributes ?? {} ).every( - ( key ) => newBlock.attributes[ key ] === block.attributes[ key ] + function isEqual( a, b ) { + return ( a?.valueOf() ?? a ) === ( b?.valueOf() ?? b ); + } + + return Object.keys( blockType?.attributes ?? {} ).every( ( key ) => + isEqual( newBlock.attributes[ key ], block.attributes[ key ] ) ); } @@ -243,6 +248,16 @@ export function getAccessibleBlockLabel( ); } +export function getDefault( attributeSchema ) { + if ( attributeSchema.default !== undefined ) { + return attributeSchema.default; + } + + if ( attributeSchema.type === 'rich-text' ) { + return new RichTextData(); + } +} + /** * Ensure attributes contains only values defined by block type, and merge * default values for missing attributes. @@ -264,9 +279,27 @@ export function __experimentalSanitizeBlockAttributes( name, attributes ) { const value = attributes[ key ]; if ( undefined !== value ) { - accumulator[ key ] = value; - } else if ( schema.hasOwnProperty( 'default' ) ) { - accumulator[ key ] = schema.default; + if ( schema.type === 'rich-text' ) { + if ( value instanceof RichTextData ) { + accumulator[ key ] = value; + } else if ( typeof value === 'string' ) { + accumulator[ key ] = new RichTextData( { + html: value, + } ); + } + } else if ( + schema.type === 'string' && + value instanceof RichTextData + ) { + accumulator[ key ] = value.toString(); + } else { + accumulator[ key ] = value; + } + } else { + const _default = getDefault( schema ); + if ( undefined !== _default ) { + accumulator[ key ] = _default; + } } if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) { diff --git a/packages/core-data/src/footnotes/get-footnotes-order.js b/packages/core-data/src/footnotes/get-footnotes-order.js index 42adeed7621e8f..169ca44264b2d5 100644 --- a/packages/core-data/src/footnotes/get-footnotes-order.js +++ b/packages/core-data/src/footnotes/get-footnotes-order.js @@ -14,7 +14,7 @@ function getBlockFootnotesOrder( block ) { if ( ! cache.has( block ) ) { const order = []; for ( const value of getRichTextValuesCached( block ) ) { - if ( ! value || ! value.includes( 'data-fn' ) ) { + if ( ! value ) { continue; } diff --git a/packages/core-data/src/footnotes/index.js b/packages/core-data/src/footnotes/index.js index fa1c5fad5c7e7d..c95aa048168194 100644 --- a/packages/core-data/src/footnotes/index.js +++ b/packages/core-data/src/footnotes/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { create, toHTMLString } from '@wordpress/rich-text'; +import { RichTextData, create, toHTMLString } from '@wordpress/rich-text'; /** * Internal dependencies @@ -53,11 +53,11 @@ export function updateFootnotesFromMeta( blocks, meta ) { continue; } - if ( typeof value !== 'string' ) { - continue; - } - - if ( value.indexOf( 'data-fn' ) === -1 ) { + // To do, remove support for string values. + if ( + typeof value !== 'string' && + ! ( value instanceof RichTextData ) + ) { continue; } @@ -78,7 +78,11 @@ export function updateFootnotesFromMeta( blocks, meta ) { } } ); - attributes[ key ] = toHTMLString( { value: richTextValue } ); + if ( typeof value === 'string' ) { + attributes[ key ] = toHTMLString( { value: richTextValue } ); + } else { + attributes[ key ] = new RichTextData( richTextValue ); + } } return attributes; diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 90726ff238c1bd..4f99cc33413fe9 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -175,6 +175,7 @@ _Parameters_ - _$1.html_ `[string]`: HTML to create value from. - _$1.range_ `[Range]`: Range to create value from. - _$1.\_\_unstableIsEditableTree_ `[boolean]`: +- _$1.preserveWhiteSpace_ `[boolean]`: _Returns_ @@ -355,6 +356,10 @@ _Returns_ - `RichTextValue`: A new value with replacements applied. +### RichTextData + +Undocumented declaration. + ### RichTextValue An object which represents a formatted string. See main `@wordpress/rich-text` documentation for more information. diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 0e9291b7a5e03d..f9251b0aa83dc9 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -8,7 +8,7 @@ import { useRegistry } from '@wordpress/data'; /** * Internal dependencies */ -import { collapseWhiteSpace, create } from '../create'; +import { collapseWhiteSpace, create, RichTextData } from '../create'; import { apply } from '../to-dom'; import { toHTMLString } from '../to-html-string'; import { useDefaultStyle } from './use-default-style'; @@ -71,9 +71,10 @@ export function useRichText( { function setRecordFromProps() { _value.current = value; record.current = create( { - html: preserveWhiteSpace - ? value - : collapseWhiteSpace( typeof value === 'string' ? value : '' ), + html: + typeof value !== 'string' || preserveWhiteSpace + ? value + : collapseWhiteSpace( value ), } ); if ( disableFormats ) { record.current.formats = Array( value.length ); @@ -116,7 +117,7 @@ export function useRichText( { if ( disableFormats ) { _value.current = newRecord.text; - } else { + } else if ( typeof value === 'string' ) { _value.current = toHTMLString( { value: __unstableBeforeSerialize ? { @@ -125,6 +126,15 @@ export function useRichText( { } : newRecord, } ); + } else { + _value.current = new RichTextData( + __unstableBeforeSerialize + ? { + ...newRecord, + formats: __unstableBeforeSerialize( newRecord ), + } + : newRecord + ); } const { start, end, formats, text } = newRecord; diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index a23baf70078bc9..1d18c59a946525 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -10,6 +10,7 @@ import { store as richTextStore } from './store'; import { createElement } from './create-element'; import { mergePair } from './concat'; import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters'; +import { toHTMLString } from './to-html-string'; /** @typedef {import('./types').RichTextValue} RichTextValue */ @@ -96,6 +97,29 @@ function toFormat( { tagName, attributes } ) { }; } +const RichTextInternalData = Symbol( 'RichTextInternalData' ); + +export class RichTextData { + constructor( init ) { + this[ RichTextInternalData ] = init?.formats ? init : create( init ); + } + get html() { + return toHTMLString( { value: this[ RichTextInternalData ] } ); + } + valueOf() { + return this.html; + } + toString() { + return this.html; + } + toJSON() { + return this.html; + } + get length() { + return this[ RichTextInternalData ].text.length; + } +} + /** * 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 @@ -128,7 +152,7 @@ function toFormat( { tagName, attributes } ) { * @param {string} [$1.html] HTML to create value from. * @param {Range} [$1.range] Range to create value from. * @param {boolean} [$1.__unstableIsEditableTree] - * + * @param {boolean} [$1.preserveWhiteSpace] * @return {RichTextValue} A rich text value. */ export function create( { @@ -137,7 +161,13 @@ export function create( { html, range, __unstableIsEditableTree: isEditableTree, + preserveWhiteSpace, } = {} ) { + // Ideally we use the instance internally as well. + if ( html instanceof RichTextData ) { + return { ...html[ RichTextInternalData ] }; + } + if ( typeof text === 'string' && text.length > 0 ) { return { formats: Array( text.length ), @@ -156,6 +186,40 @@ export function create( { return createEmptyValue(); } + function collapseWhiteSpaceElement( el, isRoot = true ) { + Array.from( el.childNodes ).forEach( ( node ) => { + if ( node.nodeType === node.TEXT_NODE ) { + node.nodeValue = node.nodeValue + .replace( /[\n\t\r]+/g, ' ' ) + .replace( / {2,}/g, ' ' ); + } else if ( node.nodeType === node.ELEMENT_NODE ) { + collapseWhiteSpaceElement( node, false ); + } + } ); + + if ( + el.firstChild && + el.firstChild.nodeType === el.TEXT_NODE && + el.firstChild.nodeValue.startsWith( ' ' ) + ) { + el.firstChild.nodeValue = el.firstChild.nodeValue.slice( 1 ); + } + + if ( + isRoot && + el.lastChild && + el.lastChild.nodeType === el.TEXT_NODE && + el.lastChild.nodeValue.endsWith( ' ' ) + ) { + el.lastChild.nodeValue = el.lastChild.nodeValue.slice( 0, -1 ); + } + } + + if ( element && preserveWhiteSpace === false ) { + element.normalize(); + collapseWhiteSpaceElement( element ); + } + return createFromElement( { element, range, diff --git a/packages/rich-text/src/index.ts b/packages/rich-text/src/index.ts index 14d26cab8f7fb1..f82317d81573d0 100644 --- a/packages/rich-text/src/index.ts +++ b/packages/rich-text/src/index.ts @@ -1,7 +1,7 @@ export { store } from './store'; export { applyFormat } from './apply-format'; export { concat } from './concat'; -export { create } from './create'; +export { RichTextData, create } from './create'; export { getActiveFormat } from './get-active-format'; export { getActiveFormats } from './get-active-formats'; export { getActiveObject } from './get-active-object'; diff --git a/schemas/json/block.json b/schemas/json/block.json index d5b4a04452eaa6..7e0c8715a4abda 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -114,6 +114,7 @@ "object", "array", "string", + "rich-text", "integer", "number" ] @@ -159,6 +160,7 @@ "enum": [ "attribute", "text", + "rich-text", "html", "raw", "query", diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt index 18b145e715a96e..f23061be5765a5 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt @@ -3,7 +3,7 @@ -

B

+

B

diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt index 98ec917dd6da5a..039fd06b6a410b 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt @@ -7,7 +7,7 @@ -

B

+

B

diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt index 75d374dd0deb42..b6d9703a212cf4 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt @@ -3,7 +3,7 @@ -

B

+

B

diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt index e862b0f6116960..90011a21d7eb71 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt @@ -7,7 +7,7 @@ -

B

+

B

diff --git a/test/integration/fixtures/blocks/core__cover.json b/test/integration/fixtures/blocks/core__cover.json index a7ed06e153012d..a16cd2fe7ea79c 100644 --- a/test/integration/fixtures/blocks/core__cover.json +++ b/test/integration/fixtures/blocks/core__cover.json @@ -19,7 +19,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "\n\t\t\tGuten Berg!\n\t\t", + "content": "Guten Berg!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover.serialized.html b/test/integration/fixtures/blocks/core__cover.serialized.html index b7eeb8cf4b85d8..e518c8042f9325 100644 --- a/test/integration/fixtures/blocks/core__cover.serialized.html +++ b/test/integration/fixtures/blocks/core__cover.serialized.html @@ -1,7 +1,5 @@
-

- Guten Berg! -

+

Guten Berg!

diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-6.json b/test/integration/fixtures/blocks/core__cover__deprecated-6.json index 62bea629e8efe6..2889f8a324ac1d 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-6.json +++ b/test/integration/fixtures/blocks/core__cover__deprecated-6.json @@ -16,7 +16,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "\n\t\t\tGuten Berg!\n\t\t", + "content": "Guten Berg!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html b/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html index 657c10cc928964..458471eae5d49a 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html @@ -1,8 +1,6 @@
-

- Guten Berg! -

+

Guten Berg!

diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-7.json b/test/integration/fixtures/blocks/core__cover__deprecated-7.json index 1ebd864bd38301..8997b7a88ea318 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-7.json +++ b/test/integration/fixtures/blocks/core__cover__deprecated-7.json @@ -17,7 +17,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "\n\t\t\tGuten Berg!\n\t\t", + "content": "Guten Berg!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html b/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html index baa8e32e7e8238..17dba6228cad5e 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html @@ -1,7 +1,5 @@
-

- Guten Berg! -

+

Guten Berg!

diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-8.json b/test/integration/fixtures/blocks/core__cover__deprecated-8.json index 9d5142e60fdd53..14252b71c00cd2 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-8.json +++ b/test/integration/fixtures/blocks/core__cover__deprecated-8.json @@ -18,7 +18,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "\n Guten Berg!\n ", + "content": "Guten Berg!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html b/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html index fd99791a8e5247..e518c8042f9325 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html @@ -1,7 +1,5 @@
-

- Guten Berg! -

+

Guten Berg!

diff --git a/test/integration/fixtures/blocks/core__cover__gradient-image.json b/test/integration/fixtures/blocks/core__cover__gradient-image.json index 5be874e5064346..25edbb780646ac 100644 --- a/test/integration/fixtures/blocks/core__cover__gradient-image.json +++ b/test/integration/fixtures/blocks/core__cover__gradient-image.json @@ -20,7 +20,7 @@ "isValid": true, "attributes": { "align": "center", - "content": " Cover! ", + "content": "Cover!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html b/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html index 135949383f712c..32a435e10c2008 100644 --- a/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html @@ -1,5 +1,5 @@
-

Cover!

+

Cover!

diff --git a/test/integration/fixtures/blocks/core__cover__video-overlay.json b/test/integration/fixtures/blocks/core__cover__video-overlay.json index 7916e3f7a20b6e..9360369e88c756 100644 --- a/test/integration/fixtures/blocks/core__cover__video-overlay.json +++ b/test/integration/fixtures/blocks/core__cover__video-overlay.json @@ -21,7 +21,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "\n\t\t\tGuten Berg!\n\t\t", + "content": "Guten Berg!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html b/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html index 3cbf472fe84c4a..dd3cf49b976bc9 100644 --- a/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html @@ -1,7 +1,5 @@
-

- Guten Berg! -

+

Guten Berg!

diff --git a/test/integration/fixtures/blocks/core__cover__video.json b/test/integration/fixtures/blocks/core__cover__video.json index 698239ef191838..9ccddf0955cd12 100644 --- a/test/integration/fixtures/blocks/core__cover__video.json +++ b/test/integration/fixtures/blocks/core__cover__video.json @@ -19,7 +19,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "\n\t\t\tGuten Berg!\n\t\t", + "content": "Guten Berg!", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__video.serialized.html b/test/integration/fixtures/blocks/core__cover__video.serialized.html index 0fb6cbf9e1266e..80fe15b5b5c80f 100644 --- a/test/integration/fixtures/blocks/core__cover__video.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__video.serialized.html @@ -1,7 +1,5 @@
-

- Guten Berg! -

+

Guten Berg!

diff --git a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json index 2e4cd5c6444d47..ed2dec4ffc59f4 100644 --- a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json +++ b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json @@ -3,7 +3,7 @@ "name": "core/paragraph", "isValid": true, "attributes": { - "content": "This is an old-style text block. Changed to paragraph in #2135.", + "content": "This is an old-style text block. Changed to paragraph in #2135.", "dropCap": false }, "innerBlocks": [] diff --git a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html index 61fd699dace637..7a11c004984d1f 100644 --- a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html +++ b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html @@ -1,3 +1,3 @@ -

This is an old-style text block. Changed to paragraph in #2135.

+

This is an old-style text block. Changed to paragraph in #2135.

diff --git a/test/integration/fixtures/documents/ms-word-online-out.html b/test/integration/fixtures/documents/ms-word-online-out.html index 398281520f2542..1016918e9fe387 100644 --- a/test/integration/fixtures/documents/ms-word-online-out.html +++ b/test/integration/fixtures/documents/ms-word-online-out.html @@ -1,9 +1,9 @@ -

This is a heading 

+

This is a heading 

-

This is a paragraph with a link

+

This is a paragraph with a link

@@ -43,7 +43,7 @@ -

An image: 

+

An image: 

diff --git a/test/integration/fixtures/documents/ms-word-styled-out.html b/test/integration/fixtures/documents/ms-word-styled-out.html index 277d8d37bbc630..8107bab7aa1d25 100644 --- a/test/integration/fixtures/documents/ms-word-styled-out.html +++ b/test/integration/fixtures/documents/ms-word-styled-out.html @@ -1,5 +1,5 @@ -

Lorem ipsum dolor sit amet, consectetur adipiscing elit 

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit 

diff --git a/test/integration/fixtures/documents/slack-paragraphs-out.html b/test/integration/fixtures/documents/slack-paragraphs-out.html index 462b64daeeb6a5..7f443cd7bf9e1f 100644 --- a/test/integration/fixtures/documents/slack-paragraphs-out.html +++ b/test/integration/fixtures/documents/slack-paragraphs-out.html @@ -1,5 +1,5 @@ -

test with link
a new line

+

test with link
a new line

diff --git a/test/integration/fixtures/documents/slack-quote-out.html b/test/integration/fixtures/documents/slack-quote-out.html index b5f9f82483d788..6026309133fecc 100644 --- a/test/integration/fixtures/documents/slack-quote-out.html +++ b/test/integration/fixtures/documents/slack-quote-out.html @@ -1,5 +1,5 @@
-

Test with link.

+

Test with link.

\ No newline at end of file diff --git a/test/integration/non-matched-tags-handling.test.js b/test/integration/non-matched-tags-handling.test.js index 67438192f13680..1c3dc1557284ba 100644 --- a/test/integration/non-matched-tags-handling.test.js +++ b/test/integration/non-matched-tags-handling.test.js @@ -19,9 +19,9 @@ describe( 'Handling of non matched tags in block transforms', () => { expect( simplePreformattedResult ).toHaveLength( 1 ); expect( simplePreformattedResult[ 0 ].name ).toBe( 'core/paragraph' ); - expect( simplePreformattedResult[ 0 ].attributes.content ).toBe( - 'Pre' - ); + expect( + simplePreformattedResult[ 0 ].attributes.content.valueOf() + ).toBe( 'Pre' ); const codeResult = pasteHandler( { HTML: '
code
', From 39cbba36bd4fb090a4ae7625c7ebd3d01fd5fcda Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 14 Nov 2023 19:37:10 +0200 Subject: [PATCH 02/21] Polish constructor + docs --- .../rich-text/get-rich-text-values.js | 6 +- packages/blocks/src/api/matchers.js | 3 +- packages/blocks/src/api/utils.js | 4 +- .../src/footnotes/get-footnotes-order.js | 15 +- packages/core-data/src/footnotes/index.js | 6 +- packages/rich-text/README.md | 5 +- packages/rich-text/src/component/index.js | 18 ++- packages/rich-text/src/create.js | 152 ++++++++++++------ 8 files changed, 130 insertions(+), 79 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/get-rich-text-values.js b/packages/block-editor/src/components/rich-text/get-rich-text-values.js index 88e6013bca4ddd..facef6bb2e2131 100644 --- a/packages/block-editor/src/components/rich-text/get-rich-text-values.js +++ b/packages/block-editor/src/components/rich-text/get-rich-text-values.js @@ -96,9 +96,5 @@ export function getRichTextValues( blocks = [] ) { const values = []; addValuesForBlocks( values, blocks ); getBlockProps.skipFilters = false; - return values.map( ( value ) => - value instanceof RichTextData - ? value - : new RichTextData( { html: value } ) - ); + return values.map( ( value ) => new RichTextData( value ) ); } diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index a6ae94efe20630..b3c6a829d02a8c 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -48,8 +48,7 @@ export function html( selector, multilineTag ) { } export const richText = ( selector ) => ( el ) => { - return new RichTextData( { - element: selector ? el.querySelector( selector ) : el, + return new RichTextData( selector ? el.querySelector( selector ) : el, { preserveWhiteSpace: false, } ); }; diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 35096f6e68240f..a79b7cf7b65b91 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -283,9 +283,7 @@ export function __experimentalSanitizeBlockAttributes( name, attributes ) { if ( value instanceof RichTextData ) { accumulator[ key ] = value; } else if ( typeof value === 'string' ) { - accumulator[ key ] = new RichTextData( { - html: value, - } ); + accumulator[ key ] = new RichTextData( value ); } } else if ( schema.type === 'string' && diff --git a/packages/core-data/src/footnotes/get-footnotes-order.js b/packages/core-data/src/footnotes/get-footnotes-order.js index 169ca44264b2d5..fcaeae660ec1aa 100644 --- a/packages/core-data/src/footnotes/get-footnotes-order.js +++ b/packages/core-data/src/footnotes/get-footnotes-order.js @@ -1,8 +1,3 @@ -/** - * WordPress dependencies - */ -import { create } from '@wordpress/rich-text'; - /** * Internal dependencies */ @@ -19,13 +14,11 @@ function getBlockFootnotesOrder( block ) { } // replacements is a sparse array, use forEach to skip empty slots. - create( { html: value } ).replacements.forEach( - ( { type, attributes } ) => { - if ( type === 'core/footnote' ) { - order.push( attributes[ 'data-fn' ] ); - } + value.replacements.forEach( ( { type, attributes } ) => { + if ( type === 'core/footnote' ) { + order.push( attributes[ 'data-fn' ] ); } - ); + } ); } cache.set( block, order ); } diff --git a/packages/core-data/src/footnotes/index.js b/packages/core-data/src/footnotes/index.js index c95aa048168194..6668a2c060bb29 100644 --- a/packages/core-data/src/footnotes/index.js +++ b/packages/core-data/src/footnotes/index.js @@ -61,7 +61,7 @@ export function updateFootnotesFromMeta( blocks, meta ) { continue; } - const richTextValue = create( { html: value } ); + const richTextValue = new RichTextData( value ); richTextValue.replacements.forEach( ( replacement ) => { if ( replacement.type === 'core/footnote' ) { @@ -79,9 +79,9 @@ export function updateFootnotesFromMeta( blocks, meta ) { } ); if ( typeof value === 'string' ) { - attributes[ key ] = toHTMLString( { value: richTextValue } ); + attributes[ key ] = richTextValue.toString(); } else { - attributes[ key ] = new RichTextData( richTextValue ); + attributes[ key ] = richTextValue; } } diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 4f99cc33413fe9..550e145c232f33 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -175,7 +175,6 @@ _Parameters_ - _$1.html_ `[string]`: HTML to create value from. - _$1.range_ `[Range]`: Range to create value from. - _$1.\_\_unstableIsEditableTree_ `[boolean]`: -- _$1.preserveWhiteSpace_ `[boolean]`: _Returns_ @@ -358,7 +357,9 @@ _Returns_ ### RichTextData -Undocumented declaration. +The RichTextData class is used to instantiate a wrapper around rich text values, with methods that can be used to transform or manipulate the data. + +Create an emtpy instance: `new RichTextData()`. Create one from an html string: `new RichTextData( 'hello' )`. Create one from a DOM wrapper element: `new RichTextData( document.createElement( 'p' ) )`. Create Create one from a rich text value: `new RichTextData( { text: '...', formats: [ ... ] } )`. ### RichTextValue diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index f9251b0aa83dc9..6efe22960b57d9 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -70,12 +70,18 @@ export function useRichText( { function setRecordFromProps() { _value.current = value; - record.current = create( { - html: - typeof value !== 'string' || preserveWhiteSpace - ? value - : collapseWhiteSpace( value ), - } ); + record.current = + value instanceof RichTextData + ? { + text: value.text, + formats: value.formats, + replacements: value.replacements, + } + : create( { + html: preserveWhiteSpace + ? value + : collapseWhiteSpace( value ), + } ); if ( disableFormats ) { record.current.formats = Array( value.length ); record.current.replacements = Array( value.length ); diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 1d18c59a946525..e4af786b910d9e 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -97,26 +97,117 @@ function toFormat( { tagName, attributes } ) { }; } +function collapseWhiteSpaceElement( el, isRoot = true ) { + Array.from( el.childNodes ).forEach( ( node ) => { + if ( node.nodeType === node.TEXT_NODE ) { + node.nodeValue = node.nodeValue + .replace( /[\n\t\r\f]+/g, ' ' ) + .replace( / {2,}/g, ' ' ); + } else if ( node.nodeType === node.ELEMENT_NODE ) { + collapseWhiteSpaceElement( node, false ); + } + } ); + + if ( + el.firstChild && + el.firstChild.nodeType === el.TEXT_NODE && + el.firstChild.nodeValue.startsWith( ' ' ) + ) { + el.firstChild.nodeValue = el.firstChild.nodeValue.slice( 1 ); + } + + if ( + isRoot && + el.lastChild && + el.lastChild.nodeType === el.TEXT_NODE && + el.lastChild.nodeValue.endsWith( ' ' ) + ) { + el.lastChild.nodeValue = el.lastChild.nodeValue.slice( 0, -1 ); + } +} + +// Ideally we use a private property. const RichTextInternalData = Symbol( 'RichTextInternalData' ); +/** + * The RichTextData class is used to instantiate a wrapper around rich text + * values, with methods that can be used to transform or manipulate the data. + * + * Create an emtpy instance: `new RichTextData()`. + * Create one from an html string: `new RichTextData( 'hello' )`. + * Create one from a DOM wrapper element: + * `new RichTextData( document.createElement( 'p' ) )`. + * Create + * Create one from a rich text value: + * `new RichTextData( { text: '...', formats: [ ... ] } )`. + * + * @todo Add methods to manipulate the data, such as slice etc. + */ export class RichTextData { - constructor( init ) { - this[ RichTextInternalData ] = init?.formats ? init : create( init ); - } - get html() { - return toHTMLString( { value: this[ RichTextInternalData ] } ); + constructor( init, options = {} ) { + if ( ! init ) { + init = createEmptyValue(); + } else if ( typeof init === 'string' ) { + init = create( { html: init } ); + } else if ( init.nodeType && init.nodeType === init.ELEMENT_NODE ) { + init.normalize(); + const { preserveWhiteSpace = false } = options; + if ( preserveWhiteSpace === false ) { + collapseWhiteSpaceElement( init ); + } + init = create( { element: init } ); + } else if ( init.text ) { + init = { + text: init.text, + formats: init.formats, + replacements: init.replacements, + }; + + if ( ! init.formats ) { + init.formats = Array( init.text.length ); + } else if ( init.formats.length !== init.text.length ) { + throw new Error( + 'RichTextData: `formats` and `text` must be of the same length.' + ); + } + + if ( ! init.replacements ) { + init.replacements = Array( init.text.length ); + } else if ( init.replacements.length !== init.text.length ) { + throw new Error( + 'RichTextData: `replacements` and `text` must be of the same length.' + ); + } + } else { + init = createEmptyValue(); + } + + // Setting text, formats, and replacements as enumerable properties + // unfortunately visualises these in the e2e tests. As long as the class + // instance doesn't have any enumerable properties, it will be + // visualised as a string. + Object.defineProperty( this, RichTextInternalData, { value: init } ); } valueOf() { - return this.html; + return toHTMLString( { value: this[ RichTextInternalData ] } ); } toString() { - return this.html; + return this.valueOf(); } toJSON() { - return this.html; + return this.valueOf(); } get length() { - return this[ RichTextInternalData ].text.length; + return this.text.length; + } + get formats() { + return this[ RichTextInternalData ].formats; + } + get replacements() { + return this[ RichTextInternalData ].replacements; + } + get text() { + return this[ RichTextInternalData ].text; } } @@ -152,7 +243,6 @@ export class RichTextData { * @param {string} [$1.html] HTML to create value from. * @param {Range} [$1.range] Range to create value from. * @param {boolean} [$1.__unstableIsEditableTree] - * @param {boolean} [$1.preserveWhiteSpace] * @return {RichTextValue} A rich text value. */ export function create( { @@ -161,11 +251,13 @@ export function create( { html, range, __unstableIsEditableTree: isEditableTree, - preserveWhiteSpace, } = {} ) { - // Ideally we use the instance internally as well. if ( html instanceof RichTextData ) { - return { ...html[ RichTextInternalData ] }; + return { + text: html.text, + formats: html.formats, + replacements: html.replacements, + }; } if ( typeof text === 'string' && text.length > 0 ) { @@ -186,40 +278,6 @@ export function create( { return createEmptyValue(); } - function collapseWhiteSpaceElement( el, isRoot = true ) { - Array.from( el.childNodes ).forEach( ( node ) => { - if ( node.nodeType === node.TEXT_NODE ) { - node.nodeValue = node.nodeValue - .replace( /[\n\t\r]+/g, ' ' ) - .replace( / {2,}/g, ' ' ); - } else if ( node.nodeType === node.ELEMENT_NODE ) { - collapseWhiteSpaceElement( node, false ); - } - } ); - - if ( - el.firstChild && - el.firstChild.nodeType === el.TEXT_NODE && - el.firstChild.nodeValue.startsWith( ' ' ) - ) { - el.firstChild.nodeValue = el.firstChild.nodeValue.slice( 1 ); - } - - if ( - isRoot && - el.lastChild && - el.lastChild.nodeType === el.TEXT_NODE && - el.lastChild.nodeValue.endsWith( ' ' ) - ) { - el.lastChild.nodeValue = el.lastChild.nodeValue.slice( 0, -1 ); - } - } - - if ( element && preserveWhiteSpace === false ) { - element.normalize(); - collapseWhiteSpaceElement( element ); - } - return createFromElement( { element, range, From 587c9971a3323bf23bf91a93e30406204a6236de Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 14 Nov 2023 20:20:13 +0200 Subject: [PATCH 03/21] wip --- .../src/components/rich-text/native/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 130e8be10238a8..951d52ece6d694 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -191,7 +191,7 @@ export class RichText extends Component { selectionEnd: end, colorPalette, } = this.props; - const { value = '' } = this.props; + const { value } = this.props; const currentValue = this.formatToValue( value ); const { formats, replacements, text } = currentValue; From dbd30102acbaa503e38a3a5a8b5852c64d6e0cc3 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 15 Nov 2023 16:48:37 +0200 Subject: [PATCH 04/21] Add to heading and list --- packages/block-library/src/heading/block.json | 5 ++--- packages/block-library/src/list-item/block.json | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index dfd5bb72b63314..72cc67caddd9ea 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -12,10 +12,9 @@ "type": "string" }, "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "h1,h2,h3,h4,h5,h6", - "default": "", "__experimentalRole": "content" }, "level": { diff --git a/packages/block-library/src/list-item/block.json b/packages/block-library/src/list-item/block.json index 07797be8623a51..06997c2ac23f8e 100644 --- a/packages/block-library/src/list-item/block.json +++ b/packages/block-library/src/list-item/block.json @@ -12,10 +12,9 @@ "type": "string" }, "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "li", - "default": "", "__experimentalRole": "content" } }, From 898fc8d30eb7be18f22d47621b26b59b1f47ba1f Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 27 Nov 2023 15:48:24 +0100 Subject: [PATCH 05/21] Add to all blocks --- packages/block-library/src/audio/block.json | 4 ++-- packages/block-library/src/button/block.json | 4 ++-- packages/block-library/src/code/block.json | 4 ++-- packages/block-library/src/code/save.js | 2 +- packages/block-library/src/details/block.json | 4 ++-- packages/block-library/src/embed/block.json | 4 ++-- packages/block-library/src/file/block.json | 8 ++++---- packages/block-library/src/file/save.js | 4 +++- .../block-library/src/form-input/block.json | 4 ++-- packages/block-library/src/gallery/block.json | 8 ++++---- packages/block-library/src/image/block.json | 4 ++-- .../block-library/src/preformatted/block.json | 5 ++--- .../block-library/src/pullquote/block.json | 9 ++++----- packages/block-library/src/quote/block.json | 5 ++--- packages/block-library/src/table/block.json | 19 +++++++++---------- packages/block-library/src/verse/block.json | 5 ++--- packages/block-library/src/video/block.json | 4 ++-- packages/blocks/src/api/matchers.js | 4 ++-- .../src/api/parser/get-block-attributes.js | 5 ++++- .../api/raw-handling/test/paste-handler.js | 4 ++-- .../blocks/core__gallery__deprecated-1.json | 2 ++ .../documents/ms-word-online-out.html | 14 +++++++------- 22 files changed, 64 insertions(+), 62 deletions(-) diff --git a/packages/block-library/src/audio/block.json b/packages/block-library/src/audio/block.json index a4740e304451ce..04df268a74a630 100644 --- a/packages/block-library/src/audio/block.json +++ b/packages/block-library/src/audio/block.json @@ -16,8 +16,8 @@ "__experimentalRole": "content" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index eec327b4ca48e4..3c232700a876e6 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -36,8 +36,8 @@ "__experimentalRole": "content" }, "text": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "a,button", "__experimentalRole": "content" }, diff --git a/packages/block-library/src/code/block.json b/packages/block-library/src/code/block.json index 80df74b5062b56..bd5db3c918b963 100644 --- a/packages/block-library/src/code/block.json +++ b/packages/block-library/src/code/block.json @@ -8,8 +8,8 @@ "textdomain": "default", "attributes": { "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "code", "__unstablePreserveWhiteSpace": true } diff --git a/packages/block-library/src/code/save.js b/packages/block-library/src/code/save.js index 7dd355f3855a86..fc00d084b54744 100644 --- a/packages/block-library/src/code/save.js +++ b/packages/block-library/src/code/save.js @@ -13,7 +13,7 @@ export default function save( { attributes } ) {
 			
 		
); diff --git a/packages/block-library/src/details/block.json b/packages/block-library/src/details/block.json index d449d42e1e10c4..a71d3af2a5ed39 100644 --- a/packages/block-library/src/details/block.json +++ b/packages/block-library/src/details/block.json @@ -13,8 +13,8 @@ "default": false }, "summary": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "summary" } }, diff --git a/packages/block-library/src/embed/block.json b/packages/block-library/src/embed/block.json index 9ca54db871db19..5aac8bbd6b8cab 100644 --- a/packages/block-library/src/embed/block.json +++ b/packages/block-library/src/embed/block.json @@ -12,8 +12,8 @@ "__experimentalRole": "content" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index 0cc20b3f501e9b..9dc6677e4adce3 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -21,8 +21,8 @@ "attribute": "id" }, "fileName": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "a:not([download])" }, "textLinkHref": { @@ -42,8 +42,8 @@ "default": true }, "downloadButtonText": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "a[download]" }, "displayPreview": { diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js index 6d0684ac76b8ef..5ec09221f4159b 100644 --- a/packages/block-library/src/file/save.js +++ b/packages/block-library/src/file/save.js @@ -25,7 +25,9 @@ export default function save( { attributes } ) { previewHeight, } = attributes; - const pdfEmbedLabel = RichText.isEmpty( fileName ) ? 'PDF embed' : fileName; + const pdfEmbedLabel = RichText.isEmpty( fileName ) + ? 'PDF embed' + : fileName.toString(); const hasFilename = ! RichText.isEmpty( fileName ); diff --git a/packages/block-library/src/form-input/block.json b/packages/block-library/src/form-input/block.json index 067b7ac69430c4..53aa0be6744cb9 100644 --- a/packages/block-library/src/form-input/block.json +++ b/packages/block-library/src/form-input/block.json @@ -19,10 +19,10 @@ "type": "string" }, "label": { - "type": "string", + "type": "rich-text", "default": "Label", "selector": ".wp-block-form-input__label-content", - "source": "html", + "source": "rich-text", "__experimentalRole": "content" }, "inlineLabel": { diff --git a/packages/block-library/src/gallery/block.json b/packages/block-library/src/gallery/block.json index 0867989af4ec7e..fad92aed59bf77 100644 --- a/packages/block-library/src/gallery/block.json +++ b/packages/block-library/src/gallery/block.json @@ -46,8 +46,8 @@ "attribute": "data-id" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": ".blocks-gallery-item__caption" } } @@ -72,8 +72,8 @@ "maximum": 8 }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": ".blocks-gallery-caption" }, "imageCrop": { diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index cfe91a71ff4f97..c5191e3dd86543 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -25,8 +25,8 @@ "__experimentalRole": "content" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/packages/block-library/src/preformatted/block.json b/packages/block-library/src/preformatted/block.json index ec6ea839385eb2..def870e7ad2fb7 100644 --- a/packages/block-library/src/preformatted/block.json +++ b/packages/block-library/src/preformatted/block.json @@ -8,10 +8,9 @@ "textdomain": "default", "attributes": { "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "pre", - "default": "", "__unstablePreserveWhiteSpace": true, "__experimentalRole": "content" } diff --git a/packages/block-library/src/pullquote/block.json b/packages/block-library/src/pullquote/block.json index 1d6c74dbc4ae04..7fc81d5683bd19 100644 --- a/packages/block-library/src/pullquote/block.json +++ b/packages/block-library/src/pullquote/block.json @@ -8,16 +8,15 @@ "textdomain": "default", "attributes": { "value": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "p", "__experimentalRole": "content" }, "citation": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "cite", - "default": "", "__experimentalRole": "content" }, "textAlign": { diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json index 7ed406c0d20965..9deca000efe06b 100644 --- a/packages/block-library/src/quote/block.json +++ b/packages/block-library/src/quote/block.json @@ -17,10 +17,9 @@ "__experimentalRole": "content" }, "citation": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "cite", - "default": "", "__experimentalRole": "content" }, "align": { diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index d1139d6c55addf..470886a1247fe1 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -12,10 +12,9 @@ "default": false }, "caption": { - "type": "string", - "source": "html", - "selector": "figcaption", - "default": "" + "type": "rich-text", + "source": "rich-text", + "selector": "figcaption" }, "head": { "type": "array", @@ -30,8 +29,8 @@ "selector": "td,th", "query": { "content": { - "type": "string", - "source": "html" + "type": "rich-text", + "source": "rich-text" }, "tag": { "type": "string", @@ -75,8 +74,8 @@ "selector": "td,th", "query": { "content": { - "type": "string", - "source": "html" + "type": "rich-text", + "source": "rich-text" }, "tag": { "type": "string", @@ -120,8 +119,8 @@ "selector": "td,th", "query": { "content": { - "type": "string", - "source": "html" + "type": "rich-text", + "source": "rich-text" }, "tag": { "type": "string", diff --git a/packages/block-library/src/verse/block.json b/packages/block-library/src/verse/block.json index fa0bc30798212e..846a1dc99caafc 100644 --- a/packages/block-library/src/verse/block.json +++ b/packages/block-library/src/verse/block.json @@ -9,10 +9,9 @@ "textdomain": "default", "attributes": { "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "pre", - "default": "", "__unstablePreserveWhiteSpace": true, "__experimentalRole": "content" }, diff --git a/packages/block-library/src/video/block.json b/packages/block-library/src/video/block.json index debe6f20fe53f7..5d4680f39e79a8 100644 --- a/packages/block-library/src/video/block.json +++ b/packages/block-library/src/video/block.json @@ -15,8 +15,8 @@ "attribute": "autoplay" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index b3c6a829d02a8c..9297fc22467d52 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -47,8 +47,8 @@ export function html( selector, multilineTag ) { }; } -export const richText = ( selector ) => ( el ) => { +export const richText = ( selector, preserveWhiteSpace ) => ( el ) => { return new RichTextData( selector ? el.querySelector( selector ) : el, { - preserveWhiteSpace: false, + preserveWhiteSpace, } ); }; diff --git a/packages/blocks/src/api/parser/get-block-attributes.js b/packages/blocks/src/api/parser/get-block-attributes.js index 1d687cedecc63c..24faae73704636 100644 --- a/packages/blocks/src/api/parser/get-block-attributes.js +++ b/packages/blocks/src/api/parser/get-block-attributes.js @@ -226,7 +226,10 @@ export const matcherFromSource = memoize( ( sourceConfig ) => { case 'text': return text( sourceConfig.selector ); case 'rich-text': - return richText( sourceConfig.selector ); + return richText( + sourceConfig.selector, + sourceConfig.__unstablePreserveWhiteSpace + ); case 'children': return children( sourceConfig.selector ); case 'node': diff --git a/packages/blocks/src/api/raw-handling/test/paste-handler.js b/packages/blocks/src/api/raw-handling/test/paste-handler.js index 6938ad0d9c4081..9b3dad39a0a5b8 100644 --- a/packages/blocks/src/api/raw-handling/test/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/test/paste-handler.js @@ -73,9 +73,9 @@ describe( 'pasteHandler', () => { expect( console ).toHaveLogged(); + delete result.attributes.caption; expect( result.attributes ).toEqual( { hasFixedLayout: false, - caption: '', head: [ { cells: [ @@ -113,9 +113,9 @@ describe( 'pasteHandler', () => { expect( console ).toHaveLogged(); + delete result.attributes.caption; expect( result.attributes ).toEqual( { hasFixedLayout: false, - caption: '', head: [ { cells: [ diff --git a/test/integration/fixtures/blocks/core__gallery__deprecated-1.json b/test/integration/fixtures/blocks/core__gallery__deprecated-1.json index 9e15ee7f1c7149..bd6108a97230a3 100644 --- a/test/integration/fixtures/blocks/core__gallery__deprecated-1.json +++ b/test/integration/fixtures/blocks/core__gallery__deprecated-1.json @@ -16,6 +16,7 @@ "attributes": { "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", "alt": "title", + "caption": "", "linkDestination": "none" }, "innerBlocks": [] @@ -26,6 +27,7 @@ "attributes": { "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", "alt": "title", + "caption": "", "linkDestination": "none" }, "innerBlocks": [] diff --git a/test/integration/fixtures/documents/ms-word-online-out.html b/test/integration/fixtures/documents/ms-word-online-out.html index 1016918e9fe387..6170fa4e632b22 100644 --- a/test/integration/fixtures/documents/ms-word-online-out.html +++ b/test/integration/fixtures/documents/ms-word-online-out.html @@ -8,33 +8,33 @@
    -
  • +
  • -
  • Bulleted 
  • +
  • Bulleted 
  • -
  • Indented 
  • +
  • Indented 
  • -
  • List 
  • +
  • List 
    -
  1. One 
  2. +
  3. One 
  4. -
  5. Two 
  6. +
  7. Two 
  8. -
  9. Three 
  10. +
  11. Three 
From 582c4308a1eca2d5d1d2e5262a8ca05de9e2cb78 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 27 Nov 2023 16:45:29 +0100 Subject: [PATCH 06/21] Store original HTML --- packages/blocks/src/api/matchers.js | 4 +++- packages/rich-text/src/create.js | 8 +++++++- test/integration/fixtures/blocks/core__cover.json | 2 +- .../fixtures/blocks/core__cover.serialized.html | 4 +++- .../fixtures/blocks/core__cover__deprecated-6.json | 2 +- .../blocks/core__cover__deprecated-6.serialized.html | 4 +++- .../fixtures/blocks/core__cover__deprecated-7.json | 2 +- .../blocks/core__cover__deprecated-7.serialized.html | 4 +++- .../fixtures/blocks/core__cover__deprecated-8.json | 2 +- .../blocks/core__cover__deprecated-8.serialized.html | 4 +++- .../fixtures/blocks/core__cover__gradient-image.json | 2 +- .../blocks/core__cover__gradient-image.serialized.html | 2 +- .../fixtures/blocks/core__cover__video-overlay.json | 2 +- .../blocks/core__cover__video-overlay.serialized.html | 4 +++- test/integration/fixtures/blocks/core__cover__video.json | 2 +- .../fixtures/blocks/core__cover__video.serialized.html | 4 +++- .../blocks/core__text__converts-to-paragraph.json | 2 +- .../core__text__converts-to-paragraph.serialized.html | 2 +- .../fixtures/documents/ms-word-online-out.html | 6 +++--- .../fixtures/documents/ms-word-styled-out.html | 2 +- .../fixtures/documents/slack-paragraphs-out.html | 2 +- test/integration/fixtures/documents/slack-quote-out.html | 2 +- test/integration/non-matched-tags-handling.test.js | 2 +- 23 files changed, 45 insertions(+), 25 deletions(-) diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index 9297fc22467d52..741d774ebd14e9 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -48,7 +48,9 @@ export function html( selector, multilineTag ) { } export const richText = ( selector, preserveWhiteSpace ) => ( el ) => { - return new RichTextData( selector ? el.querySelector( selector ) : el, { + const target = selector ? el.querySelector( selector ) : el; + return new RichTextData( target, { preserveWhiteSpace, + originalHTML: target?.innerHTML, } ); }; diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index e4af786b910d9e..3762637682e8eb 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -187,9 +187,15 @@ export class RichTextData { // instance doesn't have any enumerable properties, it will be // visualised as a string. Object.defineProperty( this, RichTextInternalData, { value: init } ); + Object.defineProperty( this, 'originalHTML', { + value: options.originalHTML, + } ); } valueOf() { - return toHTMLString( { value: this[ RichTextInternalData ] } ); + return ( + this.originalHTML || + toHTMLString( { value: this[ RichTextInternalData ] } ) + ); } toString() { return this.valueOf(); diff --git a/test/integration/fixtures/blocks/core__cover.json b/test/integration/fixtures/blocks/core__cover.json index a16cd2fe7ea79c..a7ed06e153012d 100644 --- a/test/integration/fixtures/blocks/core__cover.json +++ b/test/integration/fixtures/blocks/core__cover.json @@ -19,7 +19,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Guten Berg!", + "content": "\n\t\t\tGuten Berg!\n\t\t", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover.serialized.html b/test/integration/fixtures/blocks/core__cover.serialized.html index e518c8042f9325..b7eeb8cf4b85d8 100644 --- a/test/integration/fixtures/blocks/core__cover.serialized.html +++ b/test/integration/fixtures/blocks/core__cover.serialized.html @@ -1,5 +1,7 @@
-

Guten Berg!

+

+ Guten Berg! +

diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-6.json b/test/integration/fixtures/blocks/core__cover__deprecated-6.json index 2889f8a324ac1d..62bea629e8efe6 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-6.json +++ b/test/integration/fixtures/blocks/core__cover__deprecated-6.json @@ -16,7 +16,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Guten Berg!", + "content": "\n\t\t\tGuten Berg!\n\t\t", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html b/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html index 458471eae5d49a..657c10cc928964 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__deprecated-6.serialized.html @@ -1,6 +1,8 @@
-

Guten Berg!

+

+ Guten Berg! +

diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-7.json b/test/integration/fixtures/blocks/core__cover__deprecated-7.json index 8997b7a88ea318..1ebd864bd38301 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-7.json +++ b/test/integration/fixtures/blocks/core__cover__deprecated-7.json @@ -17,7 +17,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Guten Berg!", + "content": "\n\t\t\tGuten Berg!\n\t\t", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html b/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html index 17dba6228cad5e..baa8e32e7e8238 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__deprecated-7.serialized.html @@ -1,5 +1,7 @@
-

Guten Berg!

+

+ Guten Berg! +

diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-8.json b/test/integration/fixtures/blocks/core__cover__deprecated-8.json index 14252b71c00cd2..9d5142e60fdd53 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-8.json +++ b/test/integration/fixtures/blocks/core__cover__deprecated-8.json @@ -18,7 +18,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Guten Berg!", + "content": "\n Guten Berg!\n ", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html b/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html index e518c8042f9325..fd99791a8e5247 100644 --- a/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__deprecated-8.serialized.html @@ -1,5 +1,7 @@
-

Guten Berg!

+

+ Guten Berg! +

diff --git a/test/integration/fixtures/blocks/core__cover__gradient-image.json b/test/integration/fixtures/blocks/core__cover__gradient-image.json index 25edbb780646ac..5be874e5064346 100644 --- a/test/integration/fixtures/blocks/core__cover__gradient-image.json +++ b/test/integration/fixtures/blocks/core__cover__gradient-image.json @@ -20,7 +20,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Cover!", + "content": " Cover! ", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html b/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html index 32a435e10c2008..135949383f712c 100644 --- a/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__gradient-image.serialized.html @@ -1,5 +1,5 @@
-

Cover!

+

Cover!

diff --git a/test/integration/fixtures/blocks/core__cover__video-overlay.json b/test/integration/fixtures/blocks/core__cover__video-overlay.json index 9360369e88c756..7916e3f7a20b6e 100644 --- a/test/integration/fixtures/blocks/core__cover__video-overlay.json +++ b/test/integration/fixtures/blocks/core__cover__video-overlay.json @@ -21,7 +21,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Guten Berg!", + "content": "\n\t\t\tGuten Berg!\n\t\t", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html b/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html index dd3cf49b976bc9..3cbf472fe84c4a 100644 --- a/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__video-overlay.serialized.html @@ -1,5 +1,7 @@
-

Guten Berg!

+

+ Guten Berg! +

diff --git a/test/integration/fixtures/blocks/core__cover__video.json b/test/integration/fixtures/blocks/core__cover__video.json index 9ccddf0955cd12..698239ef191838 100644 --- a/test/integration/fixtures/blocks/core__cover__video.json +++ b/test/integration/fixtures/blocks/core__cover__video.json @@ -19,7 +19,7 @@ "isValid": true, "attributes": { "align": "center", - "content": "Guten Berg!", + "content": "\n\t\t\tGuten Berg!\n\t\t", "dropCap": false, "placeholder": "Write title…", "fontSize": "large" diff --git a/test/integration/fixtures/blocks/core__cover__video.serialized.html b/test/integration/fixtures/blocks/core__cover__video.serialized.html index 80fe15b5b5c80f..0fb6cbf9e1266e 100644 --- a/test/integration/fixtures/blocks/core__cover__video.serialized.html +++ b/test/integration/fixtures/blocks/core__cover__video.serialized.html @@ -1,5 +1,7 @@
-

Guten Berg!

+

+ Guten Berg! +

diff --git a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json index ed2dec4ffc59f4..2e4cd5c6444d47 100644 --- a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json +++ b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.json @@ -3,7 +3,7 @@ "name": "core/paragraph", "isValid": true, "attributes": { - "content": "This is an old-style text block. Changed to paragraph in #2135.", + "content": "This is an old-style text block. Changed to paragraph in #2135.", "dropCap": false }, "innerBlocks": [] diff --git a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html index 7a11c004984d1f..61fd699dace637 100644 --- a/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html +++ b/test/integration/fixtures/blocks/core__text__converts-to-paragraph.serialized.html @@ -1,3 +1,3 @@ -

This is an old-style text block. Changed to paragraph in #2135.

+

This is an old-style text block. Changed to paragraph in #2135.

diff --git a/test/integration/fixtures/documents/ms-word-online-out.html b/test/integration/fixtures/documents/ms-word-online-out.html index 6170fa4e632b22..8187b598f9a91a 100644 --- a/test/integration/fixtures/documents/ms-word-online-out.html +++ b/test/integration/fixtures/documents/ms-word-online-out.html @@ -1,9 +1,9 @@ -

This is a heading 

+

This is a heading 

-

This is a paragraph with a link

+

This is a paragraph with a link

@@ -43,7 +43,7 @@ -

An image: 

+

An image: 

diff --git a/test/integration/fixtures/documents/ms-word-styled-out.html b/test/integration/fixtures/documents/ms-word-styled-out.html index 8107bab7aa1d25..277d8d37bbc630 100644 --- a/test/integration/fixtures/documents/ms-word-styled-out.html +++ b/test/integration/fixtures/documents/ms-word-styled-out.html @@ -1,5 +1,5 @@ -

Lorem ipsum dolor sit amet, consectetur adipiscing elit 

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit 

diff --git a/test/integration/fixtures/documents/slack-paragraphs-out.html b/test/integration/fixtures/documents/slack-paragraphs-out.html index 7f443cd7bf9e1f..462b64daeeb6a5 100644 --- a/test/integration/fixtures/documents/slack-paragraphs-out.html +++ b/test/integration/fixtures/documents/slack-paragraphs-out.html @@ -1,5 +1,5 @@ -

test with link
a new line

+

test with link
a new line

diff --git a/test/integration/fixtures/documents/slack-quote-out.html b/test/integration/fixtures/documents/slack-quote-out.html index 6026309133fecc..b5f9f82483d788 100644 --- a/test/integration/fixtures/documents/slack-quote-out.html +++ b/test/integration/fixtures/documents/slack-quote-out.html @@ -1,5 +1,5 @@
-

Test with link.

+

Test with link.

\ No newline at end of file diff --git a/test/integration/non-matched-tags-handling.test.js b/test/integration/non-matched-tags-handling.test.js index 1c3dc1557284ba..451a628c329775 100644 --- a/test/integration/non-matched-tags-handling.test.js +++ b/test/integration/non-matched-tags-handling.test.js @@ -30,7 +30,7 @@ describe( 'Handling of non matched tags in block transforms', () => { expect( codeResult ).toHaveLength( 1 ); expect( codeResult[ 0 ].name ).toBe( 'core/code' ); - expect( codeResult[ 0 ].attributes.content ).toBe( 'code' ); + expect( codeResult[ 0 ].attributes.content.valueOf() ).toBe( 'code' ); expect( console ).toHaveLogged(); } ); } ); From 18cca172c3c8c042738f4bfbbee9be06a46cf48d Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 27 Nov 2023 17:31:46 +0100 Subject: [PATCH 07/21] Fix e2e --- packages/block-library/src/utils/remove-anchor-tag.js | 2 +- ...ld-copy-only-partial-selection-of-text-blocks-2-chromium.txt | 2 +- ...artial-selection-with-other-blocks-in-between-2-chromium.txt | 2 +- ...e-should-cut-partial-selection-of-text-blocks-2-chromium.txt | 2 +- ...artial-selection-with-other-blocks-in-between-2-chromium.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/utils/remove-anchor-tag.js b/packages/block-library/src/utils/remove-anchor-tag.js index 31d1877082f50d..72e11e47e92ba9 100644 --- a/packages/block-library/src/utils/remove-anchor-tag.js +++ b/packages/block-library/src/utils/remove-anchor-tag.js @@ -6,5 +6,5 @@ * @return {string} The value with anchor tags removed. */ export default function removeAnchorTag( value ) { - return value.replace( /<\/?a[^>]*>/g, '' ); + return value.toString().replace( /<\/?a[^>]*>/g, '' ); } diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt index f23061be5765a5..18b145e715a96e 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-only-partial-selection-of-text-blocks-2-chromium.txt @@ -3,7 +3,7 @@ -

B

+

B

diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt index 039fd06b6a410b..98ec917dd6da5a 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-copy-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt @@ -7,7 +7,7 @@ -

B

+

B

diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt index b6d9703a212cf4..75d374dd0deb42 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-partial-selection-of-text-blocks-2-chromium.txt @@ -3,7 +3,7 @@ -

B

+

B

diff --git a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt index 90011a21d7eb71..e862b0f6116960 100644 --- a/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt +++ b/test/e2e/specs/editor/various/__snapshots__/Copy-cut-paste-should-cut-paste-partial-selection-with-other-blocks-in-between-2-chromium.txt @@ -7,7 +7,7 @@ -

B

+

B

From 48666cfe3e22c46c20e064a0eabaaf01cda36d2d Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 27 Nov 2023 17:39:04 +0100 Subject: [PATCH 08/21] Update native e2e snaps --- .../preformatted/test/__snapshots__/transforms.native.js.snap | 2 +- .../src/verse/test/__snapshots__/transforms.native.js.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap index e39fc8b92b2401..81adef5b228388 100644 --- a/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/preformatted/test/__snapshots__/transforms.native.js.snap @@ -26,6 +26,6 @@ exports[`Preformatted block transforms to Group block 1`] = ` exports[`Preformatted block transforms to Paragraph block 1`] = ` " -

Some preformatted text...
And more!

+

Some preformatted text...
And more!

" `; diff --git a/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap index 46e536a0f19f36..5570a6cf5d67d9 100644 --- a/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/verse/test/__snapshots__/transforms.native.js.snap @@ -20,6 +20,6 @@ exports[`Verse block transforms to Group block 1`] = ` exports[`Verse block transforms to Paragraph block 1`] = ` " -

Come
Home.

+

Come
Home.

" `; From 54c53da0770f45128715a51936fb4671e4fd5eda Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 28 Nov 2023 23:11:32 +0100 Subject: [PATCH 09/21] Add static from* methods --- .../src/components/rich-text/content.js | 2 +- .../rich-text/get-rich-text-values.js | 6 +- packages/blocks/src/api/matchers.js | 5 +- packages/blocks/src/api/utils.js | 5 +- packages/core-data/src/footnotes/index.js | 16 ++-- packages/rich-text/README.md | 9 +- packages/rich-text/src/create.js | 85 ++++++++----------- 7 files changed, 61 insertions(+), 67 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/content.js b/packages/block-editor/src/components/rich-text/content.js index cb13dd01213418..fbc5be07f9c935 100644 --- a/packages/block-editor/src/components/rich-text/content.js +++ b/packages/block-editor/src/components/rich-text/content.js @@ -29,7 +29,7 @@ export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { value = `<${ MultilineTag }>`; } - const content = { value?.valueOf() }; + const content = { value?.toString() }; if ( Tag ) { const { format, ...restProps } = props; diff --git a/packages/block-editor/src/components/rich-text/get-rich-text-values.js b/packages/block-editor/src/components/rich-text/get-rich-text-values.js index facef6bb2e2131..ee2bc638269308 100644 --- a/packages/block-editor/src/components/rich-text/get-rich-text-values.js +++ b/packages/block-editor/src/components/rich-text/get-rich-text-values.js @@ -96,5 +96,9 @@ export function getRichTextValues( blocks = [] ) { const values = []; addValuesForBlocks( values, blocks ); getBlockProps.skipFilters = false; - return values.map( ( value ) => new RichTextData( value ) ); + return values.map( ( value ) => + value instanceof RichTextData + ? value + : RichTextData.fromHTMLString( value ) + ); } diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index 741d774ebd14e9..0145235d927181 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -49,8 +49,5 @@ export function html( selector, multilineTag ) { export const richText = ( selector, preserveWhiteSpace ) => ( el ) => { const target = selector ? el.querySelector( selector ) : el; - return new RichTextData( target, { - preserveWhiteSpace, - originalHTML: target?.innerHTML, - } ); + return RichTextData.fromHTMLElement( target, { preserveWhiteSpace } ); }; diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index a79b7cf7b65b91..60a94117b36e23 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -283,13 +283,14 @@ export function __experimentalSanitizeBlockAttributes( name, attributes ) { if ( value instanceof RichTextData ) { accumulator[ key ] = value; } else if ( typeof value === 'string' ) { - accumulator[ key ] = new RichTextData( value ); + accumulator[ key ] = + RichTextData.fromHTMLString( value ); } } else if ( schema.type === 'string' && value instanceof RichTextData ) { - accumulator[ key ] = value.toString(); + accumulator[ key ] = value.toHTMLString(); } else { accumulator[ key ] = value; } diff --git a/packages/core-data/src/footnotes/index.js b/packages/core-data/src/footnotes/index.js index 6668a2c060bb29..d4bcf7d3f62b00 100644 --- a/packages/core-data/src/footnotes/index.js +++ b/packages/core-data/src/footnotes/index.js @@ -53,7 +53,7 @@ export function updateFootnotesFromMeta( blocks, meta ) { continue; } - // To do, remove support for string values. + // To do, remove support for string values? if ( typeof value !== 'string' && ! ( value instanceof RichTextData ) @@ -61,7 +61,10 @@ export function updateFootnotesFromMeta( blocks, meta ) { continue; } - const richTextValue = new RichTextData( value ); + const richTextValue = + typeof value === 'string' + ? RichTextData.fromHTMLElement( value ) + : value; richTextValue.replacements.forEach( ( replacement ) => { if ( replacement.type === 'core/footnote' ) { @@ -78,11 +81,10 @@ export function updateFootnotesFromMeta( blocks, meta ) { } } ); - if ( typeof value === 'string' ) { - attributes[ key ] = richTextValue.toString(); - } else { - attributes[ key ] = richTextValue; - } + attributes[ key ] = + typeof value === 'string' + ? richTextValue.toHTMLString() + : richTextValue; } return attributes; diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 550e145c232f33..90fd15e1c905c5 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -359,7 +359,14 @@ _Returns_ The RichTextData class is used to instantiate a wrapper around rich text values, with methods that can be used to transform or manipulate the data. -Create an emtpy instance: `new RichTextData()`. Create one from an html string: `new RichTextData( 'hello' )`. Create one from a DOM wrapper element: `new RichTextData( document.createElement( 'p' ) )`. Create Create one from a rich text value: `new RichTextData( { text: '...', formats: [ ... ] } )`. +- Create an emtpy instance: `new RichTextData()`. +- Create one from an html string: `RichTextData.fromHTMLString( +'hello' )`. +- Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( +document.querySelector( 'p' ) )`. +- Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. +- Create one from a rich text value: `new RichTextData( { text: '...', +formats: [ ... ] } )`. ### RichTextValue diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 3762637682e8eb..7c21cb0cbf9858 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -133,75 +133,58 @@ const RichTextInternalData = Symbol( 'RichTextInternalData' ); * The RichTextData class is used to instantiate a wrapper around rich text * values, with methods that can be used to transform or manipulate the data. * - * Create an emtpy instance: `new RichTextData()`. - * Create one from an html string: `new RichTextData( 'hello' )`. - * Create one from a DOM wrapper element: - * `new RichTextData( document.createElement( 'p' ) )`. - * Create - * Create one from a rich text value: - * `new RichTextData( { text: '...', formats: [ ... ] } )`. + * - Create an emtpy instance: `new RichTextData()`. + * - Create one from an html string: `RichTextData.fromHTMLString( + * 'hello' )`. + * - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( + * document.querySelector( 'p' ) )`. + * - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. + * - Create one from a rich text value: `new RichTextData( { text: '...', + * formats: [ ... ] } )`. * - * @todo Add methods to manipulate the data, such as slice etc. + * @todo Add methods to manipulate the data, such as applyFormat, slice etc. */ export class RichTextData { - constructor( init, options = {} ) { - if ( ! init ) { - init = createEmptyValue(); - } else if ( typeof init === 'string' ) { - init = create( { html: init } ); - } else if ( init.nodeType && init.nodeType === init.ELEMENT_NODE ) { - init.normalize(); - const { preserveWhiteSpace = false } = options; - if ( preserveWhiteSpace === false ) { - collapseWhiteSpaceElement( init ); - } - init = create( { element: init } ); - } else if ( init.text ) { - init = { - text: init.text, - formats: init.formats, - replacements: init.replacements, - }; - - if ( ! init.formats ) { - init.formats = Array( init.text.length ); - } else if ( init.formats.length !== init.text.length ) { - throw new Error( - 'RichTextData: `formats` and `text` must be of the same length.' - ); - } - - if ( ! init.replacements ) { - init.replacements = Array( init.text.length ); - } else if ( init.replacements.length !== init.text.length ) { - throw new Error( - 'RichTextData: `replacements` and `text` must be of the same length.' - ); - } - } else { - init = createEmptyValue(); + static fromHTMLString( html ) { + return new RichTextData( create( { html } ) ); + } + static fromHTMLElement( htmlElement, options = {} ) { + const element = htmlElement.cloneNode( true ); + element.normalize(); + const { preserveWhiteSpace = false } = options; + if ( preserveWhiteSpace === false ) { + collapseWhiteSpaceElement( element ); } - + const richTextData = new RichTextData( create( { element } ) ); + Object.defineProperty( richTextData, 'originalHTML', { + value: htmlElement.innerHTML, + } ); + return richTextData; + } + static fromPlainText( text ) { + return new RichTextData( create( { text } ) ); + } + constructor( init = createEmptyValue() ) { // Setting text, formats, and replacements as enumerable properties // unfortunately visualises these in the e2e tests. As long as the class // instance doesn't have any enumerable properties, it will be // visualised as a string. Object.defineProperty( this, RichTextInternalData, { value: init } ); - Object.defineProperty( this, 'originalHTML', { - value: options.originalHTML, - } ); } - valueOf() { + toHTMLString() { return ( this.originalHTML || toHTMLString( { value: this[ RichTextInternalData ] } ) ); } + valueOf() { + return this.toHTMLString(); + } toString() { - return this.valueOf(); + return this.toHTMLString(); } toJSON() { - return this.valueOf(); + return this.toHTMLString(); } get length() { return this.text.length; From 9df8b45d7cba8c174c4f203d605fd17e49322316 Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 28 Nov 2023 23:28:45 +0100 Subject: [PATCH 10/21] Fix matcher selector returning null --- packages/blocks/src/api/matchers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index 0145235d927181..74bb642af6aee6 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -49,5 +49,7 @@ export function html( selector, multilineTag ) { export const richText = ( selector, preserveWhiteSpace ) => ( el ) => { const target = selector ? el.querySelector( selector ) : el; - return RichTextData.fromHTMLElement( target, { preserveWhiteSpace } ); + return target + ? RichTextData.fromHTMLElement( target, { preserveWhiteSpace } ) + : new RichTextData(); }; From 4f52b03f9b9a5b21c9300fd2763903c3bf845793 Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 30 Nov 2023 13:27:17 +0100 Subject: [PATCH 11/21] Correct typo + add empty method --- packages/blocks/src/api/matchers.js | 2 +- packages/core-data/src/footnotes/index.js | 2 +- packages/rich-text/src/create.js | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/blocks/src/api/matchers.js b/packages/blocks/src/api/matchers.js index 74bb642af6aee6..950f1539440a0b 100644 --- a/packages/blocks/src/api/matchers.js +++ b/packages/blocks/src/api/matchers.js @@ -51,5 +51,5 @@ export const richText = ( selector, preserveWhiteSpace ) => ( el ) => { const target = selector ? el.querySelector( selector ) : el; return target ? RichTextData.fromHTMLElement( target, { preserveWhiteSpace } ) - : new RichTextData(); + : RichTextData.empty(); }; diff --git a/packages/core-data/src/footnotes/index.js b/packages/core-data/src/footnotes/index.js index d4bcf7d3f62b00..9458290f9cb40b 100644 --- a/packages/core-data/src/footnotes/index.js +++ b/packages/core-data/src/footnotes/index.js @@ -63,7 +63,7 @@ export function updateFootnotesFromMeta( blocks, meta ) { const richTextValue = typeof value === 'string' - ? RichTextData.fromHTMLElement( value ) + ? RichTextData.fromHTMLString( value ) : value; richTextValue.replacements.forEach( ( replacement ) => { diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 7c21cb0cbf9858..4466caf6bb48b9 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -145,6 +145,9 @@ const RichTextInternalData = Symbol( 'RichTextInternalData' ); * @todo Add methods to manipulate the data, such as applyFormat, slice etc. */ export class RichTextData { + static empty() { + return new RichTextData(); + } static fromHTMLString( html ) { return new RichTextData( create( { html } ) ); } From b6474a30ec0fba7a1a67c3d8bf8046d3cc73b7fd Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 30 Nov 2023 13:37:40 +0100 Subject: [PATCH 12/21] Add toPlainText --- packages/block-library/src/file/save.js | 2 +- packages/rich-text/src/create.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js index 5ec09221f4159b..05ed6afd5bc920 100644 --- a/packages/block-library/src/file/save.js +++ b/packages/block-library/src/file/save.js @@ -27,7 +27,7 @@ export default function save( { attributes } ) { const pdfEmbedLabel = RichText.isEmpty( fileName ) ? 'PDF embed' - : fileName.toString(); + : fileName.toPlainText(); const hasFilename = ! RichText.isEmpty( fileName ); diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 4466caf6bb48b9..6ad3d24c40c9ac 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -11,6 +11,7 @@ import { createElement } from './create-element'; import { mergePair } from './concat'; import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters'; import { toHTMLString } from './to-html-string'; +import { getTextContent } from './get-text-content'; /** @typedef {import('./types').RichTextValue} RichTextValue */ @@ -148,6 +149,9 @@ export class RichTextData { static empty() { return new RichTextData(); } + static fromPlainText( text ) { + return new RichTextData( create( { text } ) ); + } static fromHTMLString( html ) { return new RichTextData( create( { html } ) ); } @@ -164,9 +168,6 @@ export class RichTextData { } ); return richTextData; } - static fromPlainText( text ) { - return new RichTextData( create( { text } ) ); - } constructor( init = createEmptyValue() ) { // Setting text, formats, and replacements as enumerable properties // unfortunately visualises these in the e2e tests. As long as the class @@ -174,6 +175,11 @@ export class RichTextData { // visualised as a string. Object.defineProperty( this, RichTextInternalData, { value: init } ); } + toPlainText() { + return getTextContent( this[ RichTextInternalData ] ); + } + // We could expose `toHTMLElement` at some point as well, but we'd only use + // it internally. toHTMLString() { return ( this.originalHTML || From b37300dfa7a146c2b7f471e65dd9749e3309564a Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 30 Nov 2023 14:15:54 +0100 Subject: [PATCH 13/21] Comment on toString --- packages/block-editor/src/components/rich-text/content.js | 7 ++++++- .../src/components/rich-text/use-input-rules.js | 3 +++ packages/block-editor/src/utils/selection.js | 2 ++ packages/block-library/src/code/save.js | 3 +++ packages/block-library/src/utils/remove-anchor-tag.js | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/content.js b/packages/block-editor/src/components/rich-text/content.js index fbc5be07f9c935..d11013a5cf1f25 100644 --- a/packages/block-editor/src/components/rich-text/content.js +++ b/packages/block-editor/src/components/rich-text/content.js @@ -4,6 +4,7 @@ import { RawHTML } from '@wordpress/element'; import { children as childrenSource } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies @@ -29,7 +30,11 @@ export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { value = `<${ MultilineTag }>`; } - const content = { value?.toString() }; + const content = ( + + { value instanceof RichTextData ? value.toHTMLString() : value } + + ); if ( Tag ) { const { format, ...restProps } = props; diff --git a/packages/block-editor/src/components/rich-text/use-input-rules.js b/packages/block-editor/src/components/rich-text/use-input-rules.js index 607165d2ead62d..5640a85f5f2695 100644 --- a/packages/block-editor/src/components/rich-text/use-input-rules.js +++ b/packages/block-editor/src/components/rich-text/use-input-rules.js @@ -29,6 +29,9 @@ function findSelection( blocks ) { blocks[ i ].attributes[ attributeKey ] = blocks[ i ].attributes[ attributeKey ] + // To do: refactor this to use rich text's selection instead, so + // we no longer have to use on this hack inserting a special + // character. .toString() .replace( START_OF_SELECTED_AREA, '' ); return [ blocks[ i ].clientId, attributeKey, 0, 0 ]; diff --git a/packages/block-editor/src/utils/selection.js b/packages/block-editor/src/utils/selection.js index 733ec8f1c37192..4e971485838791 100644 --- a/packages/block-editor/src/utils/selection.js +++ b/packages/block-editor/src/utils/selection.js @@ -25,6 +25,8 @@ export function retrieveSelectedAttribute( blockAttributes ) { const value = blockAttributes[ name ]; return ( ( typeof value === 'string' || value instanceof RichTextData ) && + // To do: refactor this to use rich text's selection instead, so we + // no longer have to use on this hack inserting a special character. value.toString().indexOf( START_OF_SELECTED_AREA ) !== -1 ); } ); diff --git a/packages/block-library/src/code/save.js b/packages/block-library/src/code/save.js index fc00d084b54744..5bb9f68767b5ec 100644 --- a/packages/block-library/src/code/save.js +++ b/packages/block-library/src/code/save.js @@ -13,6 +13,9 @@ export default function save( { attributes } ) {
 			
 		
diff --git a/packages/block-library/src/utils/remove-anchor-tag.js b/packages/block-library/src/utils/remove-anchor-tag.js index 72e11e47e92ba9..82e7b03423648d 100644 --- a/packages/block-library/src/utils/remove-anchor-tag.js +++ b/packages/block-library/src/utils/remove-anchor-tag.js @@ -6,5 +6,6 @@ * @return {string} The value with anchor tags removed. */ export default function removeAnchorTag( value ) { + // To do: Refactor this to use rich text's removeFormat instead. return value.toString().replace( /<\/?a[^>]*>/g, '' ); } From 18b4d995f48b570f1f3f60170a18746b8c9919fa Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 1 Dec 2023 22:34:30 +0200 Subject: [PATCH 14/21] Improve whitespace collapse perf --- packages/rich-text/src/create.js | 57 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 6ad3d24c40c9ac..2dc6f7adcbc73f 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -98,33 +98,37 @@ function toFormat( { tagName, attributes } ) { }; } -function collapseWhiteSpaceElement( el, isRoot = true ) { - Array.from( el.childNodes ).forEach( ( node ) => { +function collapseWhiteSpaceElement( element, isRoot = true ) { + const clone = element.cloneNode( true ); + clone.normalize(); + Array.from( clone.childNodes ).forEach( ( node, i, nodes ) => { if ( node.nodeType === node.TEXT_NODE ) { - node.nodeValue = node.nodeValue - .replace( /[\n\t\r\f]+/g, ' ' ) - .replace( / {2,}/g, ' ' ); + let newNodeValue = node.nodeValue; + + if ( /[\n\t\r\f]/.test( newNodeValue ) ) { + newNodeValue = newNodeValue.replace( /[\n\t\r\f]+/g, ' ' ); + } + + if ( newNodeValue.indexOf( ' ' ) !== -1 ) { + newNodeValue = newNodeValue.replace( / {2,}/g, ' ' ); + } + + if ( i === 0 && newNodeValue.startsWith( ' ' ) ) { + newNodeValue = newNodeValue.slice( 1 ); + } else if ( + isRoot && + i === nodes.length - 1 && + newNodeValue.endsWith( ' ' ) + ) { + newNodeValue = newNodeValue.slice( 0, -1 ); + } + + node.nodeValue = newNodeValue; } else if ( node.nodeType === node.ELEMENT_NODE ) { collapseWhiteSpaceElement( node, false ); } } ); - - if ( - el.firstChild && - el.firstChild.nodeType === el.TEXT_NODE && - el.firstChild.nodeValue.startsWith( ' ' ) - ) { - el.firstChild.nodeValue = el.firstChild.nodeValue.slice( 1 ); - } - - if ( - isRoot && - el.lastChild && - el.lastChild.nodeType === el.TEXT_NODE && - el.lastChild.nodeValue.endsWith( ' ' ) - ) { - el.lastChild.nodeValue = el.lastChild.nodeValue.slice( 0, -1 ); - } + return clone; } // Ideally we use a private property. @@ -156,12 +160,11 @@ export class RichTextData { return new RichTextData( create( { html } ) ); } static fromHTMLElement( htmlElement, options = {} ) { - const element = htmlElement.cloneNode( true ); - element.normalize(); const { preserveWhiteSpace = false } = options; - if ( preserveWhiteSpace === false ) { - collapseWhiteSpaceElement( element ); - } + const element = + preserveWhiteSpace === false + ? collapseWhiteSpaceElement( htmlElement ) + : htmlElement; const richTextData = new RichTextData( create( { element } ) ); Object.defineProperty( richTextData, 'originalHTML', { value: htmlElement.innerHTML, From 2134ee221a17b45bcaa8fe055aadb269071eb3dc Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 1 Dec 2023 23:45:04 +0200 Subject: [PATCH 15/21] pasteHandler should always return string --- .../src/components/rich-text/use-paste-handler.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 5baf5858701219..1302e2d0dce469 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 @@ -8,7 +8,7 @@ import { findTransform, getBlockTransforms, } from '@wordpress/blocks'; -import { isEmpty, insert, create, RichTextData } from '@wordpress/rich-text'; +import { isEmpty, insert, create } from '@wordpress/rich-text'; import { isURL } from '@wordpress/url'; /** @@ -134,10 +134,7 @@ export function usePasteHandler( props ) { tagName, } ); - if ( - typeof content === 'string' || - content instanceof RichTextData - ) { + if ( typeof content === 'string' ) { const transformed = formatTypes.reduce( ( accumlator, { __unstablePasteRule } ) => { // Only allow one transform. From 7816e5c77210d636c6576709abb1f2ca8ef88074 Mon Sep 17 00:00:00 2001 From: Ella Date: Sat, 2 Dec 2023 00:19:03 +0200 Subject: [PATCH 16/21] file block: revert to trunk's behaviour of setting HTML as a label --- packages/block-library/src/file/save.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js index 05ed6afd5bc920..5ec09221f4159b 100644 --- a/packages/block-library/src/file/save.js +++ b/packages/block-library/src/file/save.js @@ -27,7 +27,7 @@ export default function save( { attributes } ) { const pdfEmbedLabel = RichText.isEmpty( fileName ) ? 'PDF embed' - : fileName.toPlainText(); + : fileName.toString(); const hasFilename = ! RichText.isEmpty( fileName ); From 3074fe731918dfcaa208f294e938ddddbdff8e81 Mon Sep 17 00:00:00 2001 From: Ella Date: Sat, 2 Dec 2023 00:39:21 +0200 Subject: [PATCH 17/21] Rewrite RichText.Content --- .../src/components/rich-text/content.js | 40 ++++++++++--------- packages/block-library/src/file/save.js | 4 +- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/content.js b/packages/block-editor/src/components/rich-text/content.js index d11013a5cf1f25..ff6e7be27d2f7f 100644 --- a/packages/block-editor/src/components/rich-text/content.js +++ b/packages/block-editor/src/components/rich-text/content.js @@ -4,7 +4,11 @@ import { RawHTML } from '@wordpress/element'; import { children as childrenSource } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; -import { RichTextData } from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import RichText from './'; /** * Internal dependencies @@ -12,34 +16,32 @@ import { RichTextData } from '@wordpress/rich-text'; import { getMultilineTag } from './utils'; export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { - // Handle deprecated `children` and `node` sources. - if ( Array.isArray( value ) ) { + const MultilineTag = getMultilineTag( multiline ); + + if ( RichText.isEmpty( value ) ) { + value = MultilineTag ? : null; + } else 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( value ); - } - - const MultilineTag = getMultilineTag( multiline ); - - if ( ! value && MultilineTag ) { - value = `<${ MultilineTag }>`; + value = { childrenSource.toHTML( value ) }; + } else if ( typeof value === 'string' ) { + // To do: deprecate. + value = { value }; + } else { + // To do: create a toReactComponent method on RichTextData, which we + // might in the future also use for the editable tree. See + // https://github.com/WordPress/gutenberg/pull/41655. + value = { value.toHTMLString() }; } - const content = ( - - { value instanceof RichTextData ? value.toHTMLString() : value } - - ); - if ( Tag ) { const { format, ...restProps } = props; - return { content }; + return { value }; } - return content; + return value; }; diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js index 5ec09221f4159b..f5eb1ce3c2b14e 100644 --- a/packages/block-library/src/file/save.js +++ b/packages/block-library/src/file/save.js @@ -27,7 +27,9 @@ export default function save( { attributes } ) { const pdfEmbedLabel = RichText.isEmpty( fileName ) ? 'PDF embed' - : fileName.toString(); + : // To do: use toPlainText, but we need ensure it's RichTextData. See + // https://github.com/WordPress/gutenberg/pull/56710. + fileName.toString(); const hasFilename = ! RichText.isEmpty( fileName ); From 039f197a60468b3cc19892ad18e026aa6b8aa3fd Mon Sep 17 00:00:00 2001 From: Ella Date: Sat, 2 Dec 2023 00:47:54 +0200 Subject: [PATCH 18/21] RichText.Content clean up --- .../src/components/rich-text/content.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/content.js b/packages/block-editor/src/components/rich-text/content.js index ff6e7be27d2f7f..92e150fb174edb 100644 --- a/packages/block-editor/src/components/rich-text/content.js +++ b/packages/block-editor/src/components/rich-text/content.js @@ -15,10 +15,15 @@ import RichText from './'; */ import { getMultilineTag } from './utils'; -export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { - const MultilineTag = getMultilineTag( multiline ); - +export function Content( { + value, + tagName: Tag, + multiline, + format, + ...props +} ) { if ( RichText.isEmpty( value ) ) { + const MultilineTag = getMultilineTag( multiline ); value = MultilineTag ? : null; } else if ( Array.isArray( value ) ) { deprecated( 'wp.blockEditor.RichText value prop as children type', { @@ -38,10 +43,5 @@ export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { value = { value.toHTMLString() }; } - if ( Tag ) { - const { format, ...restProps } = props; - return { value }; - } - - return value; -}; + return Tag ? { value } : value; +} From 91ea1e014bf538414396109d3beef599f641754a Mon Sep 17 00:00:00 2001 From: Ella Date: Sat, 2 Dec 2023 12:07:24 +0200 Subject: [PATCH 19/21] Clean up --- packages/rich-text/src/component/index.js | 52 +++++++--------- packages/rich-text/src/create.js | 73 +++++++++++------------ 2 files changed, 58 insertions(+), 67 deletions(-) diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 6efe22960b57d9..84c5be33c4b670 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -8,7 +8,7 @@ import { useRegistry } from '@wordpress/data'; /** * Internal dependencies */ -import { collapseWhiteSpace, create, RichTextData } from '../create'; +import { create, RichTextData } from '../create'; import { apply } from '../to-dom'; import { toHTMLString } from '../to-html-string'; import { useDefaultStyle } from './use-default-style'; @@ -70,18 +70,18 @@ export function useRichText( { function setRecordFromProps() { _value.current = value; - record.current = - value instanceof RichTextData - ? { - text: value.text, - formats: value.formats, - replacements: value.replacements, - } - : create( { - html: preserveWhiteSpace - ? value - : collapseWhiteSpace( value ), - } ); + if ( ! value?.length ) { + record.current = RichTextData.empty(); + } else if ( typeof value === 'string' ) { + record.current = RichTextData.fromHTMLString( value, { + preserveWhiteSpace, + } ); + } + record.current = { + text: value.text, + formats: value.formats, + replacements: value.replacements, + }; if ( disableFormats ) { record.current.formats = Array( value.length ); record.current.replacements = Array( value.length ); @@ -123,24 +123,16 @@ export function useRichText( { if ( disableFormats ) { _value.current = newRecord.text; - } else if ( typeof value === 'string' ) { - _value.current = toHTMLString( { - value: __unstableBeforeSerialize - ? { - ...newRecord, - formats: __unstableBeforeSerialize( newRecord ), - } - : newRecord, - } ); } else { - _value.current = new RichTextData( - __unstableBeforeSerialize - ? { - ...newRecord, - formats: __unstableBeforeSerialize( newRecord ), - } - : newRecord - ); + const newFormas = __unstableBeforeSerialize + ? __unstableBeforeSerialize( newRecord ) + : newRecord.formats; + newRecord = { ...newRecord, formats: newFormas }; + if ( typeof value === 'string' ) { + _value.current = toHTMLString( { value: newRecord } ); + } else { + _value.current = new RichTextData( newRecord ); + } } const { start, end, formats, text } = newRecord; diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 2dc6f7adcbc73f..e230313e137c9c 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -98,39 +98,6 @@ function toFormat( { tagName, attributes } ) { }; } -function collapseWhiteSpaceElement( element, isRoot = true ) { - const clone = element.cloneNode( true ); - clone.normalize(); - Array.from( clone.childNodes ).forEach( ( node, i, nodes ) => { - if ( node.nodeType === node.TEXT_NODE ) { - let newNodeValue = node.nodeValue; - - if ( /[\n\t\r\f]/.test( newNodeValue ) ) { - newNodeValue = newNodeValue.replace( /[\n\t\r\f]+/g, ' ' ); - } - - if ( newNodeValue.indexOf( ' ' ) !== -1 ) { - newNodeValue = newNodeValue.replace( / {2,}/g, ' ' ); - } - - if ( i === 0 && newNodeValue.startsWith( ' ' ) ) { - newNodeValue = newNodeValue.slice( 1 ); - } else if ( - isRoot && - i === nodes.length - 1 && - newNodeValue.endsWith( ' ' ) - ) { - newNodeValue = newNodeValue.slice( 0, -1 ); - } - - node.nodeValue = newNodeValue; - } else if ( node.nodeType === node.ELEMENT_NODE ) { - collapseWhiteSpaceElement( node, false ); - } - } ); - return clone; -} - // Ideally we use a private property. const RichTextInternalData = Symbol( 'RichTextInternalData' ); @@ -163,7 +130,7 @@ export class RichTextData { const { preserveWhiteSpace = false } = options; const element = preserveWhiteSpace === false - ? collapseWhiteSpaceElement( htmlElement ) + ? collapseWhiteSpace( htmlElement ) : htmlElement; const richTextData = new RichTextData( create( { element } ) ); Object.defineProperty( richTextData, 'originalHTML', { @@ -391,10 +358,42 @@ function filterRange( node, range, filter ) { * @see * https://developer.mozilla.org/en-US/docs/Web/CSS/white-space-collapse#collapsing_of_white_space * - * @param {string} string + * @param {HTMLElement} element + * @param {boolean} isRoot + * + * @return {HTMLElement} New element with collapsed whitespace. */ -export function collapseWhiteSpace( string ) { - return string.replace( /[\n\r\t]+/g, ' ' ); +function collapseWhiteSpace( element, isRoot = true ) { + const clone = element.cloneNode( true ); + clone.normalize(); + Array.from( clone.childNodes ).forEach( ( node, i, nodes ) => { + if ( node.nodeType === node.TEXT_NODE ) { + let newNodeValue = node.nodeValue; + + if ( /[\n\t\r\f]/.test( newNodeValue ) ) { + newNodeValue = newNodeValue.replace( /[\n\t\r\f]+/g, ' ' ); + } + + if ( newNodeValue.indexOf( ' ' ) !== -1 ) { + newNodeValue = newNodeValue.replace( / {2,}/g, ' ' ); + } + + if ( i === 0 && newNodeValue.startsWith( ' ' ) ) { + newNodeValue = newNodeValue.slice( 1 ); + } else if ( + isRoot && + i === nodes.length - 1 && + newNodeValue.endsWith( ' ' ) + ) { + newNodeValue = newNodeValue.slice( 0, -1 ); + } + + node.nodeValue = newNodeValue; + } else if ( node.nodeType === node.ELEMENT_NODE ) { + collapseWhiteSpace( node, false ); + } + } ); + return clone; } /** From 0b20d23543ebad63181ebd31933dff400d8ff2a8 Mon Sep 17 00:00:00 2001 From: Ella Date: Sat, 2 Dec 2023 17:52:46 +0200 Subject: [PATCH 20/21] Fix errors --- packages/rich-text/src/component/index.js | 18 +++++++++--------- packages/rich-text/src/create.js | 7 +++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 84c5be33c4b670..a04406e5a1f6cf 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -70,17 +70,17 @@ export function useRichText( { function setRecordFromProps() { _value.current = value; - if ( ! value?.length ) { - record.current = RichTextData.empty(); - } else if ( typeof value === 'string' ) { - record.current = RichTextData.fromHTMLString( value, { - preserveWhiteSpace, - } ); + record.current = value; + if ( ! ( value instanceof RichTextData ) ) { + record.current = value + ? RichTextData.fromHTMLString( value, { preserveWhiteSpace } ) + : RichTextData.empty(); } + // To do: make rich text internally work with RichTextData. record.current = { - text: value.text, - formats: value.formats, - replacements: value.replacements, + text: record.current.text, + formats: record.current.formats, + replacements: record.current.replacements, }; if ( disableFormats ) { record.current.formats = Array( value.length ); diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index e230313e137c9c..a35fabbd4e2fad 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -128,10 +128,9 @@ export class RichTextData { } static fromHTMLElement( htmlElement, options = {} ) { const { preserveWhiteSpace = false } = options; - const element = - preserveWhiteSpace === false - ? collapseWhiteSpace( htmlElement ) - : htmlElement; + const element = preserveWhiteSpace + ? htmlElement + : collapseWhiteSpace( htmlElement ); const richTextData = new RichTextData( create( { element } ) ); Object.defineProperty( richTextData, 'originalHTML', { value: htmlElement.innerHTML, From bce8a2debc428082d3f98d95c26246c98d075cb1 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 4 Dec 2023 09:58:24 +0200 Subject: [PATCH 21/21] Fix annotation tests --- packages/rich-text/src/component/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index a04406e5a1f6cf..a2b5734d5c2048 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -124,10 +124,10 @@ export function useRichText( { if ( disableFormats ) { _value.current = newRecord.text; } else { - const newFormas = __unstableBeforeSerialize + const newFormats = __unstableBeforeSerialize ? __unstableBeforeSerialize( newRecord ) : newRecord.formats; - newRecord = { ...newRecord, formats: newFormas }; + newRecord = { ...newRecord, formats: newFormats }; if ( typeof value === 'string' ) { _value.current = toHTMLString( { value: newRecord } ); } else { @@ -135,7 +135,7 @@ export function useRichText( { } } - const { start, end, formats, text } = newRecord; + const { start, end, formats, text } = record.current; // Selection must be updated first, so it is recorded in history when // the content change happens.