diff --git a/packages/block-editor/src/components/rich-text/content.scss b/packages/block-editor/src/components/rich-text/content.scss index 6f118479fc6b03..b69527f6607e98 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; - } } } @@ -40,3 +36,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: 2px; +} + +[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 898bdfa73b330e..d683836d9e2fa8 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 0daf48aa9a1c36..0e2ac57bea3704 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`] = `
+ + + + comment + + + + 🍒 @@ -289,6 +304,21 @@ exports[`recordToDom should preserve emoji in formatting 1`] = ` + + + + /funky + + + + test test diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index f246ab956db3a7..7658ede7e37737 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: '/funky>', + 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 e7288e4ba16332..ac8308c7274b58 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 f770dfdefc128a..a4c12b4c47f00d 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 `{comment-content}>` 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 46671c951bc09d..0e3caad4f70c83 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( {