From b76d9f0311019c7d866ce1d9db92140e483887bd Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:12:44 +0100 Subject: [PATCH] Rich text: preserve comments (#62128) Co-authored-by: ellatrix Co-authored-by: lysyjan Co-authored-by: youknowriad Co-authored-by: RolfKyburz Co-authored-by: strarsis Co-authored-by: danielbachhuber --- .../src/components/rich-text/content.scss | 19 +++++-- packages/rich-text/src/create.js | 28 ++++++++++ .../src/test/__snapshots__/to-dom.js.snap | 30 +++++++++++ packages/rich-text/src/test/helpers/index.js | 52 +++++++++++++++++++ packages/rich-text/src/to-dom.js | 12 +++-- packages/rich-text/src/to-html-string.js | 9 ++++ packages/rich-text/src/to-tree.js | 15 +++++- 7 files changed, 157 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/content.scss b/packages/block-editor/src/components/rich-text/content.scss index 28dcd931a7f5d..67e14b0882d7e 100644 --- a/packages/block-editor/src/components/rich-text/content.scss +++ b/packages/block-editor/src/components/rich-text/content.scss @@ -13,10 +13,6 @@ &:focus { // Removes outline added by the browser. outline: none; - - [data-rich-text-format-boundary] { - border-radius: $radius-small; - } } } @@ -34,3 +30,18 @@ figcaption.block-editor-rich-text__editable [data-rich-text-placeholder]::before background: rgb(255, 255, 0); } } + +[data-rich-text-comment], +[data-rich-text-format-boundary] { + border-radius: $radius-small; +} + +[data-rich-text-comment] { + background-color: currentColor; + + span { + filter: invert(100%); + color: currentColor; + padding: 0 2px; + } +} diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 898bdfa73b330..d683836d9e2fa 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -469,6 +469,34 @@ function createFromElement( { element, range, isEditableTree } ) { continue; } + if ( + node.nodeType === node.COMMENT_NODE || + ( node.nodeType === node.ELEMENT_NODE && + node.tagName === 'SPAN' && + node.hasAttribute( 'data-rich-text-comment' ) ) + ) { + const value = { + formats: [ , ], + replacements: [ + { + type: '#comment', + attributes: { + 'data-rich-text-comment': + node.nodeType === node.COMMENT_NODE + ? node.nodeValue + : node.getAttribute( + 'data-rich-text-comment' + ), + }, + }, + ], + text: OBJECT_REPLACEMENT_CHARACTER, + }; + accumulateSelection( accumulator, node, range, value ); + mergePair( accumulator, value ); + continue; + } + if ( node.nodeType !== node.ELEMENT_NODE ) { continue; } diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index 0daf48aa9a1c3..0e2ac57bea370 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -272,6 +272,21 @@ exports[`recordToDom should not error with overlapping formats (2) 1`] = ` `; +exports[`recordToDom should preserve comments 1`] = ` + + + + + comment + + + + +`; + exports[`recordToDom should preserve emoji 1`] = ` 🍒 @@ -289,6 +304,21 @@ exports[`recordToDom should preserve emoji in formatting 1`] = ` `; +exports[`recordToDom should preserve funky comments 1`] = ` + + + + + /funky + + + + +`; + exports[`recordToDom should preserve non breaking space 1`] = ` test  test diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index f246ab956db3a..7658ede7e3773 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -551,6 +551,58 @@ export const spec = [ text: '\ufffc', }, }, + { + description: 'should preserve comments', + html: '', + createRange: ( element ) => ( { + startOffset: 0, + startContainer: element, + endOffset: 1, + endContainer: element, + } ), + startPath: [ 0, 0 ], + endPath: [ 2, 0 ], + record: { + start: 0, + end: 1, + formats: [ , ], + replacements: [ + { + attributes: { + 'data-rich-text-comment': 'comment', + }, + type: '#comment', + }, + ], + text: '\ufffc', + }, + }, + { + description: 'should preserve funky comments', + html: '', + createRange: ( element ) => ( { + startOffset: 0, + startContainer: element, + endOffset: 1, + endContainer: element, + } ), + startPath: [ 0, 0 ], + endPath: [ 2, 0 ], + record: { + start: 0, + end: 1, + formats: [ , ], + replacements: [ + { + attributes: { + 'data-rich-text-comment': '/funky', + }, + type: '#comment', + }, + ], + text: '\ufffc', + }, + }, ]; export const specWithRegistration = [ diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index e7288e4ba1633..ac8308c7274b5 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -68,10 +68,16 @@ function append( element, child ) { const { type, attributes } = child; if ( type ) { - child = element.ownerDocument.createElement( type ); + if ( type === '#comment' ) { + child = element.ownerDocument.createComment( + attributes[ 'data-rich-text-comment' ] + ); + } else { + child = element.ownerDocument.createElement( type ); - for ( const key in attributes ) { - child.setAttribute( key, attributes[ key ] ); + for ( const key in attributes ) { + child.setAttribute( key, attributes[ key ] ); + } } } diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index f770dfdefc128..a4c12b4c47f00 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -88,6 +88,15 @@ function remove( object ) { } function createElementHTML( { type, attributes, object, children } ) { + if ( type === '#comment' ) { + // We can't restore the original comment delimiters, because once parsed + // into DOM nodes, we don't have the information. But in the future we + // could allow comment handlers to specify custom delimiters, for + // example `` for Bits, where `comment-content` + // would be `/{bit-name}` or `__{translatable-string}` (TBD). + return ``; + } + let attributeString = ''; for ( const key in attributes ) { diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 46671c951bc09..0e3caad4f70c8 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -229,7 +229,20 @@ export function toTree( { const { type, attributes, innerHTML } = replacement; const formatType = getFormatType( type ); - if ( ! isEditableTree && type === 'script' ) { + if ( isEditableTree && type === '#comment' ) { + pointer = append( getParent( pointer ), { + type: 'span', + attributes: { + contenteditable: 'false', + 'data-rich-text-comment': + attributes[ 'data-rich-text-comment' ], + }, + } ); + append( + append( pointer, { type: 'span' } ), + attributes[ 'data-rich-text-comment' ].trim() + ); + } else if ( ! isEditableTree && type === 'script' ) { pointer = append( getParent( pointer ), fromFormat( {