From dcc35e85522487dcc2054c67ba5405e85de6de5c Mon Sep 17 00:00:00 2001 From: Ella Date: Tue, 18 Jun 2024 17:54:15 +0300 Subject: [PATCH] Convert to blocks: upgrade and remove spans if possible --- .../src/api/raw-handling/anchor-reducer.js | 25 +++++++++++ .../api/raw-handling/empty-span-remover.js | 11 +++++ packages/blocks/src/api/raw-handling/index.js | 4 ++ .../src/api/raw-handling/paste-handler.js | 3 ++ .../raw-handling/phrasing-content-reducer.js | 38 +++++++---------- .../api/raw-handling/test/anchor-reducer.js | 35 ++++++++++++++++ .../test/phrasing-content-reducer.js | 42 ++----------------- .../blocks-raw-handling.test.js.snap | 6 +++ test/integration/blocks-raw-handling.test.js | 5 +++ 9 files changed, 109 insertions(+), 60 deletions(-) create mode 100644 packages/blocks/src/api/raw-handling/anchor-reducer.js create mode 100644 packages/blocks/src/api/raw-handling/empty-span-remover.js create mode 100644 packages/blocks/src/api/raw-handling/test/anchor-reducer.js diff --git a/packages/blocks/src/api/raw-handling/anchor-reducer.js b/packages/blocks/src/api/raw-handling/anchor-reducer.js new file mode 100644 index 0000000000000..ee16b7779a308 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/anchor-reducer.js @@ -0,0 +1,25 @@ +export default function anchorReducer( node ) { + if ( node.nodeName === 'A' ) { + // In jsdom-jscore, 'node.target' can be null. + // TODO: Explore fixing this by patching jsdom-jscore. + if ( node.target && node.target.toLowerCase() === '_blank' ) { + node.rel = 'noreferrer noopener'; + } else { + node.removeAttribute( 'target' ); + node.removeAttribute( 'rel' ); + } + + // Saves anchor elements name attribute as id + if ( node.name && ! node.id ) { + node.id = node.name; + } + + // Keeps id only if there is an internal link pointing to it + if ( + node.id && + ! node.ownerDocument.querySelector( `[href="#${ node.id }"]` ) + ) { + node.removeAttribute( 'id' ); + } + } +} diff --git a/packages/blocks/src/api/raw-handling/empty-span-remover.js b/packages/blocks/src/api/raw-handling/empty-span-remover.js new file mode 100644 index 0000000000000..4914b7b3b4d6a --- /dev/null +++ b/packages/blocks/src/api/raw-handling/empty-span-remover.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { unwrap } from '@wordpress/dom'; + +export default ( node ) => { + // Remove spans with no attributes. + if ( node.nodeName === 'SPAN' && ! node.attributes.length ) { + unwrap( node ); + } +}; diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index 2d1094023ada1..083d4cecae92f 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -14,6 +14,8 @@ import specialCommentConverter from './special-comment-converter'; import listReducer from './list-reducer'; import blockquoteNormaliser from './blockquote-normaliser'; import figureContentReducer from './figure-content-reducer'; +import phrasingContentReducer from './phrasing-content-reducer'; +import emptySpanRemover from './empty-span-remover'; import shortcodeConverter from './shortcode-converter'; import { deepFilterHTML, getBlockContentSchema } from './utils'; @@ -69,9 +71,11 @@ export function rawHandler( { HTML = '' } ) { specialCommentConverter, // Needed to create media blocks. figureContentReducer, + phrasingContentReducer, // Needed to create the quote block, which cannot handle text // without wrapper paragraphs. blockquoteNormaliser( { raw: true } ), + emptySpanRemover, ]; piece = deepFilterHTML( piece, filters, blockContentSchema ); diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index 58bd3f3809a74..d942fe840c074 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -15,6 +15,7 @@ import specialCommentConverter from './special-comment-converter'; import commentRemover from './comment-remover'; import isInlineContent from './is-inline-content'; import phrasingContentReducer from './phrasing-content-reducer'; +import anchorReducer from './anchor-reducer'; import headRemover from './head-remover'; import msListConverter from './ms-list-converter'; import msListIgnore from './ms-list-ignore'; @@ -48,6 +49,7 @@ function filterInlineHTML( HTML ) { googleDocsUIDRemover, msListIgnore, phrasingContentReducer, + anchorReducer, commentRemover, ] ); HTML = removeInvalidHTML( HTML, getPhrasingContentSchema( 'paste' ), { @@ -193,6 +195,7 @@ export function pasteHandler( { listReducer, imageCorrector, phrasingContentReducer, + anchorReducer, specialCommentConverter, commentRemover, iframeRemover, diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index c72a5ffaf6771..30537c3ba6aae 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -3,6 +3,13 @@ */ import { wrap, replaceTag } from '@wordpress/dom'; +function removeStyle( node, property ) { + node.style[ property ] = ''; + if ( ! node.style.length ) { + node.removeAttribute( 'style' ); + } +} + export default function phrasingContentReducer( node, doc ) { // In jsdom-jscore, 'node.style' can be null. // TODO: Explore fixing this by patching jsdom-jscore. @@ -15,11 +22,17 @@ export default function phrasingContentReducer( node, doc ) { verticalAlign, } = node.style; + if ( fontWeight === 'normal' || fontWeight === '400' ) { + removeStyle( node, 'fontWeight' ); + } + if ( fontWeight === 'bold' || fontWeight === '700' ) { + removeStyle( node, 'fontWeight' ); wrap( doc.createElement( 'strong' ), node ); } if ( fontStyle === 'italic' ) { + removeStyle( node, 'fontStyle' ); wrap( doc.createElement( 'em' ), node ); } @@ -30,39 +43,20 @@ export default function phrasingContentReducer( node, doc ) { textDecorationLine === 'line-through' || textDecoration.includes( 'line-through' ) ) { + removeStyle( node, 'textDecoration' ); wrap( doc.createElement( 's' ), node ); } if ( verticalAlign === 'super' ) { + removeStyle( node, 'verticalAlign' ); wrap( doc.createElement( 'sup' ), node ); } else if ( verticalAlign === 'sub' ) { + removeStyle( node, 'verticalAlign' ); wrap( doc.createElement( 'sub' ), node ); } } else if ( node.nodeName === 'B' ) { node = replaceTag( node, 'strong' ); } else if ( node.nodeName === 'I' ) { node = replaceTag( node, 'em' ); - } else if ( node.nodeName === 'A' ) { - // In jsdom-jscore, 'node.target' can be null. - // TODO: Explore fixing this by patching jsdom-jscore. - if ( node.target && node.target.toLowerCase() === '_blank' ) { - node.rel = 'noreferrer noopener'; - } else { - node.removeAttribute( 'target' ); - node.removeAttribute( 'rel' ); - } - - // Saves anchor elements name attribute as id - if ( node.name && ! node.id ) { - node.id = node.name; - } - - // Keeps id only if there is an internal link pointing to it - if ( - node.id && - ! node.ownerDocument.querySelector( `[href="#${ node.id }"]` ) - ) { - node.removeAttribute( 'id' ); - } } } diff --git a/packages/blocks/src/api/raw-handling/test/anchor-reducer.js b/packages/blocks/src/api/raw-handling/test/anchor-reducer.js new file mode 100644 index 0000000000000..9076a32c16e54 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/test/anchor-reducer.js @@ -0,0 +1,35 @@ +/** + * Internal dependencies + */ +import anchorReducer from '../anchor-reducer'; +import { deepFilterHTML } from '../utils'; + +describe( 'anchorReducer', () => { + it( 'should normalise the rel attribute', () => { + const input = + 'WordPress'; + const output = + 'WordPress'; + expect( deepFilterHTML( input, [ anchorReducer ], {} ) ).toEqual( + output + ); + } ); + + it( 'should only allow target="_blank"', () => { + const input = + 'WordPress'; + const output = 'WordPress'; + expect( deepFilterHTML( input, [ anchorReducer ], {} ) ).toEqual( + output + ); + } ); + + it( 'should remove the rel attribute when target is not set', () => { + const input = + 'WordPress'; + const output = 'WordPress'; + expect( deepFilterHTML( input, [ anchorReducer ], {} ) ).toEqual( + output + ); + } ); +} ); diff --git a/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js index d73935429d461..b52837e434bba 100644 --- a/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js @@ -12,9 +12,7 @@ describe( 'phrasingContentReducer', () => { [ phrasingContentReducer ], {} ) - ).toEqual( - 'test' - ); + ).toEqual( 'test' ); } ); it( 'should transform numeric font weight', () => { @@ -24,9 +22,7 @@ describe( 'phrasingContentReducer', () => { [ phrasingContentReducer ], {} ) - ).toEqual( - 'test' - ); + ).toEqual( 'test' ); } ); it( 'should transform font style', () => { @@ -36,7 +32,7 @@ describe( 'phrasingContentReducer', () => { [ phrasingContentReducer ], {} ) - ).toEqual( 'test' ); + ).toEqual( 'test' ); } ); it( 'should transform nested formatting', () => { @@ -46,36 +42,6 @@ describe( 'phrasingContentReducer', () => { [ phrasingContentReducer ], {} ) - ).toEqual( - 'test' - ); - } ); - - it( 'should normalise the rel attribute', () => { - const input = - 'WordPress'; - const output = - 'WordPress'; - expect( - deepFilterHTML( input, [ phrasingContentReducer ], {} ) - ).toEqual( output ); - } ); - - it( 'should only allow target="_blank"', () => { - const input = - 'WordPress'; - const output = 'WordPress'; - expect( - deepFilterHTML( input, [ phrasingContentReducer ], {} ) - ).toEqual( output ); - } ); - - it( 'should remove the rel attribute when target is not set', () => { - const input = - 'WordPress'; - const output = 'WordPress'; - expect( - deepFilterHTML( input, [ phrasingContentReducer ], {} ) - ).toEqual( output ); + ).toEqual( 'test' ); } ); } ); diff --git a/test/integration/__snapshots__/blocks-raw-handling.test.js.snap b/test/integration/__snapshots__/blocks-raw-handling.test.js.snap index 2ceadd67d4d1f..b768c2eea5cd3 100644 --- a/test/integration/__snapshots__/blocks-raw-handling.test.js.snap +++ b/test/integration/__snapshots__/blocks-raw-handling.test.js.snap @@ -218,3 +218,9 @@ exports[`rawHandler should preserve all paragraphs 1`] = `

" `; + +exports[`rawHandler should remove convertable spans 1`] = ` +" +

a

+" +`; diff --git a/test/integration/blocks-raw-handling.test.js b/test/integration/blocks-raw-handling.test.js index 70af14f329046..a29d2eb2e6d2d 100644 --- a/test/integration/blocks-raw-handling.test.js +++ b/test/integration/blocks-raw-handling.test.js @@ -569,4 +569,9 @@ describe( 'rawHandler', () => {

`; expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); + + it( 'should remove convertable spans', () => { + const HTML = `a`; + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); } );