Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better inline paste #2497

Merged
merged 7 commits into from
Aug 22, 2017
Merged
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
12 changes: 12 additions & 0 deletions blocks/api/paste/comment-remover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Browser dependencies
*/
const { COMMENT_NODE } = window.Node;

export default function( node ) {
if ( node.nodeType !== COMMENT_NODE ) {
return;
}

node.parentNode.removeChild( node );
}
28 changes: 28 additions & 0 deletions blocks/api/paste/create-unwrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Browser dependencies
*/
const { ELEMENT_NODE } = window.Node;

function unwrap( node ) {
const parent = node.parentNode;

while ( node.firstChild ) {
parent.insertBefore( node.firstChild, node );
}

parent.removeChild( node );
}

export default function( predicate ) {
return ( node ) => {
if ( node.nodeType !== ELEMENT_NODE ) {
return;
}

if ( ! predicate( node ) ) {
return;
}

unwrap( node );
};
}
44 changes: 35 additions & 9 deletions blocks/api/paste/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,59 @@
/**
* External dependencies
*/
import { find, get, flow } from 'lodash';
import { find, get } from 'lodash';

/**
* Internal dependencies
*/
import { createBlock } from '../factory';
import { getBlockTypes, getUnknownTypeHandlerName } from '../registration';
import { getBlockAttributes } from '../parser';
import { getBlockAttributes, parseWithGrammar } from '../parser';
import normaliseBlocks from './normalise-blocks';
import stripAttributes from './strip-attributes';
import stripWrappers from './strip-wrappers';
import commentRemover from './comment-remover';
import createUnwrapper from './create-unwrapper';
import isInlineContent from './is-inline-content';
import { deepFilter, isInline, isSpan, isWrapper } from './utils';

export default function( HTML ) {
const prepare = flow( [
stripWrappers,
normaliseBlocks,
export default function( { content: HTML, inline } ) {
HTML = HTML.replace( /<meta[^>]+>/, '' );

// Block delimiters detected.
if ( ! inline && HTML.indexOf( '<!-- wp:' ) !== -1 ) {
return parseWithGrammar( HTML );
}

// Inline paste.
if ( inline || isInlineContent( HTML ) ) {
HTML = deepFilter( HTML, [
stripAttributes,
commentRemover,
createUnwrapper( ( node ) => ! isInline( node ) ),
createUnwrapper( ( node ) => isSpan( node ) ),
] );

// Allows us to ask for this information when we get a report.
window.console.log( 'Processed inline HTML:\n\n', HTML );

return HTML;
}

HTML = deepFilter( HTML, [
stripAttributes,
commentRemover,
createUnwrapper( ( node ) => isWrapper( node ) ),
createUnwrapper( ( node ) => isSpan( node ) ),
] );

const preparedHTML = prepare( HTML );
HTML = normaliseBlocks( HTML );

// Allows us to ask for this information when we get a report.
window.console.log( 'Processed HTML piece:\n\n', HTML );

const doc = document.implementation.createHTMLDocument( '' );

doc.body.innerHTML = preparedHTML;
doc.body.innerHTML = HTML;

return Array.from( doc.body.children ).map( ( node ) => {
const block = getBlockTypes().reduce( ( acc, blockType ) => {
Expand Down
14 changes: 14 additions & 0 deletions blocks/api/paste/is-inline-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Internal dependencies
*/
import { isInline, isDoubleBR } from './utils';

export default function( HTML ) {
const doc = document.implementation.createHTMLDocument( '' );

doc.body.innerHTML = HTML;

const nodes = Array.from( doc.body.children );

return nodes.every( isInline ) && ! nodes.some( isDoubleBR );
}
28 changes: 6 additions & 22 deletions blocks/api/paste/normalise-blocks.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
/**
* WordPress dependencies
* Internal dependencies
*/
import { nodetypes } from '@wordpress/utils';
import { isInline } from './utils';

const { ELEMENT_NODE, TEXT_NODE } = nodetypes;

const inlineTags = [
'strong',
'em',
'b',
'i',
'del',
'ins',
'a',
'code',
'abbr',
'time',
'sub',
'sup',
];

function isInline( node ) {
return inlineTags.indexOf( node.nodeName.toLowerCase() ) !== -1;
}
/**
* Browser dependencies
*/
const { ELEMENT_NODE, TEXT_NODE } = window.Node;

export default function( HTML ) {
const decuDoc = document.implementation.createHTMLDocument( '' );
Expand Down
39 changes: 18 additions & 21 deletions blocks/api/paste/strip-attributes.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
/**
* Browser dependencies
*/
const { ELEMENT_NODE } = window.Node;

const attributes = [
'style',
'class',
'id',
];

export default function( HTML ) {
const doc = document.implementation.createHTMLDocument( '' );

doc.body.innerHTML = HTML;

deepAttributeStrip( doc.body.children );
export default function( node ) {
if ( node.nodeType !== ELEMENT_NODE ) {
return;
}

return doc.body.innerHTML;
}

function deepAttributeStrip( nodeList ) {
Array.from( nodeList ).forEach( ( node ) => {
if ( node.hasAttributes() ) {
Array.from( node.attributes ).forEach( ( { name } ) => {
if ( attributes.indexOf( name ) !== -1 ) {
node.removeAttribute( name );
}
if ( ! node.hasAttributes() ) {
return;
}

if ( name.indexOf( 'data-' ) === 0 ) {
node.removeAttribute( name );
}
} );
Array.from( node.attributes ).forEach( ( { name } ) => {
if ( attributes.indexOf( name ) !== -1 ) {
node.removeAttribute( name );
}

deepAttributeStrip( node.children );
if ( name.indexOf( 'data-' ) === 0 ) {
node.removeAttribute( name );
}
} );
}
34 changes: 0 additions & 34 deletions blocks/api/paste/strip-wrappers.js

This file was deleted.

20 changes: 20 additions & 0 deletions blocks/api/paste/test/comment-remover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* External dependencies
*/
import { equal } from 'assert';

/**
* Internal dependencies
*/
import commentRemover from '../comment-remover';
import { deepFilter } from '../utils';

describe( 'stripWrappers', () => {
it( 'should remove comments', () => {
equal( deepFilter( '<!-- test -->', [ commentRemover ] ), '' );
} );

it( 'should deep remove comments', () => {
equal( deepFilter( '<p>test<!-- test --></p>', [ commentRemover ] ), '<p>test</p>' );
} );
} );
34 changes: 34 additions & 0 deletions blocks/api/paste/test/create-unwrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* External dependencies
*/
import { equal } from 'assert';

/**
* Internal dependencies
*/
import createUnwrapper from '../create-unwrapper';
import { deepFilter, isSpan } from '../utils';

const unwrapper = createUnwrapper( ( node ) => isSpan( node ) );

describe( 'stripWrappers', () => {
it( 'should remove spans', () => {
equal( deepFilter( '<span>test</span>', [ unwrapper ] ), 'test' );
} );

it( 'should remove wrapped spans', () => {
equal( deepFilter( '<p><span>test</span></p>', [ unwrapper ] ), '<p>test</p>' );
} );

it( 'should remove spans with attributes', () => {
equal( deepFilter( '<p><span id="test">test</span></p>', [ unwrapper ] ), '<p>test</p>' );
} );

it( 'should remove nested spans', () => {
equal( deepFilter( '<p><span><span>test</span></span></p>', [ unwrapper ] ), '<p>test</p>' );
} );

it( 'should remove spans, but preserve nested structure', () => {
equal( deepFilter( '<p><span><em>test</em> <em>test</em></span></p>', [ unwrapper ] ), '<p><em>test</em> <em>test</em></p>' );
} );
} );
4 changes: 2 additions & 2 deletions blocks/api/paste/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ describe( 'paste', () => {
} );

it( 'should convert recognised pasted content', () => {
const pastedBlock = paste( '<small>test</small>' )[ 0 ];
const pastedBlock = paste( { content: '<small>test</small>' } )[ 0 ];
const block = createBlock( 'test/small', { content: [ 'test' ] } );

equal( pastedBlock.name, block.name );
deepEqual( pastedBlock.attributes, block.attributes );
} );

it( 'should handle unknown pasted content', () => {
const pastedBlock = paste( '<big>test</big>' )[ 0 ];
const pastedBlock = paste( { content: '<big>test</big>' } )[ 0 ];

equal( pastedBlock.name, 'test/unknown' );
equal( pastedBlock.attributes.content, '<big>test</big>' );
Expand Down
21 changes: 21 additions & 0 deletions blocks/api/paste/test/is-inline-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* External dependencies
*/
import { equal } from 'assert';

/**
* Internal dependencies
*/
import isInlineContent from '../is-inline-content';

describe( 'stripWrappers', () => {
it( 'should be inline content', () => {
equal( isInlineContent( '<em>test</em>' ), true );
} );

it( 'should not be inline content', () => {
equal( isInlineContent( '<div>test</div>' ), false );
equal( isInlineContent( '<em>test</em><div>test</div>' ), false );
equal( isInlineContent( 'test<br><br>test' ), false );
} );
} );
11 changes: 6 additions & 5 deletions blocks/api/paste/test/strip-attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ import { equal } from 'assert';
* Internal dependencies
*/
import stripAttributes from '../strip-attributes';
import { deepFilter } from '../utils';

describe( 'stripAttributes', () => {
it( 'should remove attributes', () => {
equal( stripAttributes( '<p class="test">test</p>' ), '<p>test</p>' );
equal( deepFilter( '<p class="test">test</p>', [ stripAttributes ] ), '<p>test</p>' );
} );

it( 'should remove multiple attributes', () => {
equal( stripAttributes( '<p class="test" id="test">test</p>' ), '<p>test</p>' );
equal( deepFilter( '<p class="test" id="test">test</p>', [ stripAttributes ] ), '<p>test</p>' );
} );

it( 'should deep remove attributes', () => {
equal( stripAttributes( '<p class="test">test <em id="test">test</em></p>' ), '<p>test <em>test</em></p>' );
equal( deepFilter( '<p class="test">test <em id="test">test</em></p>', [ stripAttributes ] ), '<p>test <em>test</em></p>' );
} );

it( 'should remove data-* attributes', () => {
equal( stripAttributes( '<p data-reactid="1">test</p>' ), '<p>test</p>' );
equal( deepFilter( '<p data-reactid="1">test</p>', [ stripAttributes ] ), '<p>test</p>' );
} );

it( 'should keep some attributes', () => {
equal( stripAttributes( '<a href="#keep">test</a>' ), '<a href="#keep">test</a>' );
equal( deepFilter( '<a href="#keep">test</a>', [ stripAttributes ] ), '<a href="#keep">test</a>' );
} );
} );
Loading