Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/blocks/src/api/raw-handling/test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,44 @@ describe( 'isPlain', () => {
expect( isPlain( '<ul><li>test</li></ul>' ) ).toBe( false );
expect( isPlain( '<article>test</article>' ) ).toBe( false );
} );

it( 'should return true for a single div wrapper with no semantic attributes', () => {
expect( isPlain( '<div>test</div>' ) ).toBe( true );
expect( isPlain( '<div style="color: red;">test</div>' ) ).toBe( true );
} );

it( 'should return true for nested div/span wrappers with only styles', () => {
expect(
isPlain(
'<div style="color: red;"><div><span style="color: blue;">test</span></div><br><div><span>more</span></div></div>'
)
).toBe( true );
} );

[ 'div', 'span' ].forEach( ( tag ) => {
it( `should return false for ${ tag } wrappers with semantic attributes`, () => {
[
'class="box"',
'id="content"',
'role="alert"',
'data-token="abc"',
'aria-label="content"',
].forEach( ( attr ) => {
expect( isPlain( `<${ tag } ${ attr }>test</${ tag }>` ) ).toBe(
false
);
} );
} );
} );

it( 'should return false when a descendant has semantic attributes', () => {
expect( isPlain( '<div><div class="box">test</div></div>' ) ).toBe(
false
);
expect(
isPlain( '<div><span data-token="abc">test</span></div>' )
).toBe( false );
} );
} );

describe( 'getBlockContentSchema', () => {
Expand Down
51 changes: 44 additions & 7 deletions packages/blocks/src/api/raw-handling/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,43 @@ export function getBlockContentSchema( context?: string ) {
return getBlockContentSchemaFromTransforms( getRawTransforms(), context );
}

/**
* Checks whether the given element is a non-semantic wrapper. A non-semantic
* wrapper is a `<span>` or `<div>` element that has no `class`, `id`, `role`,
* `data-*`, or `aria-*` attribute. Plugins and themes commonly use those
* attributes to drive custom raw transforms (e.g. converting
* `<div class="box">` to a custom block), so wrappers carrying them must be
* preserved instead of being unwrapped. Other attributes (e.g. `style`,
* `title`, `lang`, `dir`) are ignored for this check.
*
* @param element The element to check.
*
* @return Whether the element is a non-semantic wrapper.
*/
function isNonSemanticWrapper( element: Element ) {
const { tagName } = element;
if ( tagName !== 'SPAN' && tagName !== 'DIV' ) {
return false;
}
for ( const { name } of element.attributes ) {
if (
name === 'class' ||
name === 'id' ||
name === 'role' ||
name.startsWith( 'data-' ) ||
name.startsWith( 'aria-' )
) {
return false;
}
}
Comment thread
t-hamano marked this conversation as resolved.
return true;
}

/**
* Checks whether HTML can be considered plain text. That is, it does not contain
* any elements that are not line breaks, or it only contains a single non-semantic
* wrapper element (span) with no semantic child elements.
* any elements that are not line breaks, or it only contains non-semantic
* wrapper elements (span/div without semantic attributes) with no semantic
* child elements.
*
* @param HTML The HTML to check.
*
Expand All @@ -142,17 +175,21 @@ export function isPlain( HTML: string ) {

const wrapper = doc.body.children.item( 0 )!;

if ( ! isNonSemanticWrapper( wrapper ) ) {
return false;
}

const descendants = wrapper.getElementsByTagName( '*' );
for ( let i = 0; i < descendants.length; i++ ) {
if ( descendants.item( i )!.tagName !== 'BR' ) {
const descendant = descendants.item( i )!;
if ( descendant.tagName === 'BR' ) {
continue;
}
if ( ! isNonSemanticWrapper( descendant ) ) {
return false;
}
}

if ( wrapper.tagName !== 'SPAN' ) {
return false;
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ exports[`Blocks raw handling pasteHandler slack-quote 1`] = `"Test with&nbsp;<a

exports[`Blocks raw handling pasteHandler two-images 1`] = `"<img src="http://localhost/wp-content/uploads/2018/01/Dec-08-2017-15-12-24-17-300x137.gif" alt="" width="300" height="137"> <img src="http://localhost/wp-content/uploads/2018/01/Dec-05-2017-17-52-09-9-300x248.gif" alt="" width="300" height="248">"`;

exports[`Blocks raw handling pasteHandler vscode-markdown 1`] = `"Heading 1<br>A paragraph.<br>Heading 2<br>Another paragraph.<br>List Item<br>List Item<br>Heading 3<br>Quote"`;

exports[`Blocks raw handling pasteHandler wordpress 1`] = `"Howdy<br>This is a paragraph.<br>More tag<br><br>Shortcode<br>[gallery ids="1"]<br><img src="block.png" alt=""><br><img src="aligned.png" alt=""> test<br><img src="not-aligned.png" alt=""> test"`;

exports[`Blocks raw handling should correctly handle quotes with mixed content 1`] = `
Expand Down
1 change: 1 addition & 0 deletions test/integration/blocks-raw-handling.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ describe( 'Blocks raw handling', () => {
'two-images',
'markdown',
'grok-markdown',
'vscode-markdown',
'wordpress',
'gutenberg',
'shortcode-matching',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style="color: #f8f8f2;background-color: #272822;font-family: 'MesloLGS NF', Consolas, 'Courier New', monospace;font-weight: normal;font-size: 16px;line-height: 24px;white-space: pre;"><div><span style="color: #a6e22e;font-weight: bold;"># Heading 1</span></div><br><div><span style="color: #f8f8f2;">A paragraph.</span></div><br><div><span style="color: #a6e22e;font-weight: bold;">## Heading 2</span></div><br><div><span style="color: #f8f8f2;">Another paragraph.</span></div><br><div><span style="color: #a6e22e;">-</span><span style="color: #f8f8f2;">List Item</span></div><div><span style="color: #a6e22e;">-</span><span style="color: #f8f8f2;">List Item</span></div><br><div><span style="color: #a6e22e;font-weight: bold;">### Heading 3</span></div><br><div><span style="color: #75715e;font-style: italic;">&gt; Quote</span></div></div>
14 changes: 14 additions & 0 deletions test/integration/fixtures/documents/vscode-markdown-in.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Heading 1

A paragraph.

## Heading 2

Another paragraph.

- List Item
- List Item

### Heading 3

> Quote
35 changes: 35 additions & 0 deletions test/integration/fixtures/documents/vscode-markdown-out.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- wp:heading {"level":1} -->
<h1 class="wp-block-heading">Heading 1</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>A paragraph.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Heading 2</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Another paragraph.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul class="wp-block-list"><!-- wp:list-item -->
<li>List Item</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>List Item</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Heading 3</h3>
<!-- /wp:heading -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>Quote</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
Loading