Skip to content

Commit

Permalink
Paste: don't unwrap inline images (#19064)
Browse files Browse the repository at this point in the history
* Paste: don't unwrap inline images

* Add align center
  • Loading branch information
ellatrix authored Dec 11, 2019
1 parent 5c1fca9 commit 989132f
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 28 deletions.
42 changes: 28 additions & 14 deletions packages/blocks/src/api/raw-handling/figure-content-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { has } from 'lodash';
/**
* Internal dependencies
*/
import { isPhrasingContent } from './phrasing-content';
import { isTextContent } from './phrasing-content';

/**
* Whether or not the given node is figure content.
Expand All @@ -21,7 +21,7 @@ function isFigureContent( node, schema ) {

// We are looking for tags that can be a child of the figure tag, excluding
// `figcaption` and any phrasing content.
if ( tag === 'figcaption' || isPhrasingContent( node ) ) {
if ( tag === 'figcaption' || isTextContent( node ) ) {
return false;
}

Expand All @@ -42,6 +42,18 @@ function canHaveAnchor( node, schema ) {
return has( schema, [ 'figure', 'children', 'a', 'children', tag ] );
}

/**
* Wraps the given element in a figure element.
*
* @param {Element} element The element to wrap.
* @param {Element} beforeElement The element before which to place the figure.
*/
function wrapFigureContent( element, beforeElement = element ) {
const figure = element.ownerDocument.createElement( 'figure' );
beforeElement.parentNode.insertBefore( figure, beforeElement );
figure.appendChild( element );
}

/**
* This filter takes figure content out of paragraphs, wraps it in a figure
* element, and moves any anchors with it if needed.
Expand Down Expand Up @@ -70,19 +82,21 @@ export default function( node, doc, schema ) {
nodeToInsert = node.parentNode;
}

let wrapper = nodeToInsert;

while ( wrapper && wrapper.nodeName !== 'P' ) {
wrapper = wrapper.parentElement;
}

const figure = doc.createElement( 'figure' );
const wrapper = nodeToInsert.closest( 'p,div' );

// If wrapped in a paragraph or div, only extract if it's aligned or if
// there is no text content.
// Otherwise, if directly at the root, wrap in a figure element.
if ( wrapper ) {
wrapper.parentNode.insertBefore( figure, wrapper );
} else {
nodeToInsert.parentNode.insertBefore( figure, nodeToInsert );
if (
node.classList.contains( 'alignright' ) ||
node.classList.contains( 'alignleft' ) ||
node.classList.contains( 'aligncenter' ) ||
! wrapper.textContent.trim()
) {
wrapFigureContent( nodeToInsert, wrapper );
}
} else if ( nodeToInsert.parentNode.nodeName === 'BODY' ) {
wrapFigureContent( nodeToInsert );
}

figure.appendChild( nodeToInsert );
}
4 changes: 2 additions & 2 deletions packages/blocks/src/api/raw-handling/is-inline-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { difference } from 'lodash';
/**
* Internal dependencies
*/
import { isPhrasingContent } from './phrasing-content';
import { isTextContent } from './phrasing-content';

/**
* Checks if the given node should be considered inline content, optionally
Expand All @@ -18,7 +18,7 @@ import { isPhrasingContent } from './phrasing-content';
* @return {boolean} True if the node is inline content, false if nohe.
*/
function isInline( node, contextTag ) {
if ( isPhrasingContent( node ) ) {
if ( isTextContent( node ) ) {
return true;
}

Expand Down
42 changes: 39 additions & 3 deletions packages/blocks/src/api/raw-handling/phrasing-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
*/
import { omit, without } from 'lodash';

/**
* All phrasing content elements.
*
* @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content-0
*/

/**
* All text-level semantic elements.
*
* @see https://html.spec.whatwg.org/multipage/text-level-semantics.html
*/
const phrasingContentSchema = {
const textContentSchema = {
strong: {},
em: {},
s: {},
Expand Down Expand Up @@ -46,10 +52,35 @@ const phrasingContentSchema = {
// Recursion is needed.
// Possible: strong > em > strong.
// Impossible: strong > strong.
without( Object.keys( phrasingContentSchema ), '#text', 'br' ).forEach( ( tag ) => {
phrasingContentSchema[ tag ].children = omit( phrasingContentSchema, tag );
without( Object.keys( textContentSchema ), '#text', 'br' ).forEach( ( tag ) => {
textContentSchema[ tag ].children = omit( textContentSchema, tag );
} );

/**
* Embedded content elements.
*
* @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#embedded-content-0
*/
const embeddedContentSchema = {
audio: { attributes: [ 'src', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted' ] },
canvas: { attributes: [ 'width', 'height' ] },
embed: { attributes: [ 'src', 'type', 'width', 'height' ] },
iframe: { attributes: [ 'src', 'srcdoc', 'name', 'sandbox', 'seamless', 'width', 'height' ] },
img: { attributes: [ 'alt', 'src', 'srcset', 'usemap', 'ismap', 'width', 'height' ] },
object: { attributes: [ 'data', 'type', 'name', 'usemap', 'form', 'width', 'height' ] },
video: { attributes: [ 'src', 'poster', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted', 'controls', 'width', 'height' ] },
};

/**
* Phrasing content elements.
*
* @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content-0
*/
const phrasingContentSchema = {
...textContentSchema,
...embeddedContentSchema,
};

/**
* Get schema of possible paths for phrasing content.
*
Expand Down Expand Up @@ -95,3 +126,8 @@ export function isPhrasingContent( node ) {
const tag = node.nodeName.toLowerCase();
return getPhrasingContentSchema().hasOwnProperty( tag ) || tag === 'span';
}

export function isTextContent( node ) {
const tag = node.nodeName.toLowerCase();
return textContentSchema.hasOwnProperty( tag ) || tag === 'span';
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,69 @@ describe( 'figureContentReducer', () => {
},
};

it( 'should move embedded content from paragraph', () => {
const input = '<p><strong>test<img class="one"></strong><img class="two"></p>';
const output = '<figure><img class="one"></figure><figure><img class="two"></figure><p><strong>test</strong></p>';
it( 'should wrap image in figure', () => {
const input = '<img>';
const output = '<figure><img></figure>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );

it( 'should move an anchor with just an image from paragraph', () => {
const input = '<p><a href="#"><img class="one"></a><strong>test</strong></p>';
const output = '<figure><a href="#"><img class="one"></a></figure><p><strong>test</strong></p>';
it( 'should move lone embedded content from paragraph', () => {
const input = '<p><img></p>';
const output = '<figure><img></figure><p></p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );

it( 'should move multiple images', () => {
const input = '<p><a href="#"><img class="one"></a><img class="two"><strong>test</strong></p>';
const output = '<figure><a href="#"><img class="one"></a></figure><figure><img class="two"></figure><p><strong>test</strong></p>';
it( 'should move multiple lone embedded content from paragraph', () => {
const input = '<p><img><img></p>';
const output = '<figure><img></figure><figure><img></figure><p></p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );

it( 'should move aligned embedded content from paragraph (1)', () => {
const input = '<p><img class="alignright">test</p>';
const output = '<figure><img class="alignright"></figure><p>test</p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );

it( 'should move aligned embedded content from paragraph (2)', () => {
const input = '<p>test<img class="alignright"></p>';
const output = '<figure><img class="alignright"></figure><p>test</p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );

it( 'should move aligned embedded content from paragraph (3)', () => {
const input = '<p>test<img class="alignright">test</p>';
const output = '<figure><img class="alignright"></figure><p>testtest</p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );

it( 'should not move embedded content from paragraph (1)', () => {
const input = '<p><img>test</p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( input );
} );

it( 'should not move embedded content from paragraph (2)', () => {
const input = '<p>test<img></p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( input );
} );

it( 'should not move embedded content from paragraph (3)', () => {
const input = '<p>test<img>test</p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( input );
} );

it( 'should move an anchor with an image', () => {
const input = '<p><a href="#"><img class="alignleft"></a>test</p>';
const output = '<figure><a href="#"><img class="alignleft"></a></figure><p>test</p>';

expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output );
} );
Expand Down
3 changes: 3 additions & 0 deletions test/integration/fixtures/wordpress-in.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ <h3>More tag</h3>
<p><!--more--></p>
<h3>Shortcode</h3>
<p>[gallery ids="1"]</p>
<p><img class="alignnone wp-image-5114" src="block.png" alt="" /></p>
<p><img class="alignright wp-image-5114" src="aligned.png" alt="" /> test</p>
<p><img class="alignnone wp-image-5114" src="not-aligned.png" alt="" /> test</p>
16 changes: 16 additions & 0 deletions test/integration/fixtures/wordpress-out.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,19 @@ <h3>Shortcode</h3>
<!-- wp:gallery {"ids":[1],"columns":3,"linkTo":"attachment"} -->
<figure class="wp-block-gallery columns-3 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img data-id="1" class="wp-image-1"/></figure></li></ul></figure>
<!-- /wp:gallery -->

<!-- wp:image {"id":5114} -->
<figure class="wp-block-image"><img src="block.png" alt="" class="wp-image-5114"/></figure>
<!-- /wp:image -->

<!-- wp:image {"align":"right","id":5114} -->
<div class="wp-block-image"><figure class="alignright"><img src="aligned.png" alt="" class="wp-image-5114"/></figure></div>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>test</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><img src="not-aligned.png" alt=""> test</p>
<!-- /wp:paragraph -->

0 comments on commit 989132f

Please sign in to comment.