From ab20a14ffc4d3e88f06ee167546152006c7e7d5b Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 12 Dec 2017 12:39:25 +0000 Subject: [PATCH 1/3] Implemented isInlineForTag mechanism This mechanism allows treating elements as inline if they are being pasted in specific tags. So for example in heading blocks we can interpret headings as inline content and on lists we can interpret lists as inline content. --- blocks/api/raw-handling/index.js | 7 ++-- blocks/api/raw-handling/is-inline-content.js | 10 +++--- blocks/api/raw-handling/utils.js | 35 ++++++++++++++++++-- blocks/editable/index.js | 1 + 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/blocks/api/raw-handling/index.js b/blocks/api/raw-handling/index.js index 6e3019bcced42..9bdcffe308116 100644 --- a/blocks/api/raw-handling/index.js +++ b/blocks/api/raw-handling/index.js @@ -34,9 +34,10 @@ import shortcodeConverter from './shortcode-converter'; * * 'AUTO': Decide based on the content passed. * * 'INLINE': Always handle as inline content, and return string. * * 'BLOCKS': Always handle as blocks, and return array of blocks. + * @param {Array} [options.tagName] The tag into which content will be inserted. * @return {Array|String} A list of blocks or a string, depending on `handlerMode`. */ -export default function rawHandler( { HTML, plainText = '', mode = 'AUTO' } ) { +export default function rawHandler( { HTML, plainText = '', mode = 'AUTO', tagName } ) { // First of all, strip any meta tags. HTML = HTML.replace( /]+>/, '' ); @@ -65,7 +66,7 @@ export default function rawHandler( { HTML, plainText = '', mode = 'AUTO' } ) { const hasShortcodes = pieces.length > 1; // True if mode is auto, no shortcode is included and HTML verifies the isInlineContent condition - const isAutoModeInline = mode === 'AUTO' && isInlineContent( HTML ) && ! hasShortcodes; + const isAutoModeInline = mode === 'AUTO' && isInlineContent( HTML, tagName ) && ! hasShortcodes; // Return filtered HTML if condition is true if ( mode === 'INLINE' || isAutoModeInline ) { @@ -74,7 +75,7 @@ export default function rawHandler( { HTML, plainText = '', mode = 'AUTO' } ) { formattingTransformer, stripAttributes, commentRemover, - createUnwrapper( ( node ) => ! isInline( node ) ), + createUnwrapper( ( node ) => ! isInline( node, tagName ) ), ] ); // Allows us to ask for this information when we get a report. diff --git a/blocks/api/raw-handling/is-inline-content.js b/blocks/api/raw-handling/is-inline-content.js index df7ec3183f509..e53ab5a142417 100644 --- a/blocks/api/raw-handling/is-inline-content.js +++ b/blocks/api/raw-handling/is-inline-content.js @@ -3,19 +3,19 @@ */ import { isInline, isDoubleBR } from './utils'; -export default function( HTML ) { +export default function( HTML, tagName ) { const doc = document.implementation.createHTMLDocument( '' ); doc.body.innerHTML = HTML; const nodes = Array.from( doc.body.children ); - return ! nodes.some( isDoubleBR ) && deepCheck( nodes ); + return ! nodes.some( isDoubleBR ) && deepCheck( nodes, tagName ); } -function deepCheck( nodes ) { +function deepCheck( nodes, tagName ) { return nodes.every( ( node ) => { - return ( 'SPAN' === node.nodeName || isInline( node ) ) && - deepCheck( Array.from( node.children ) ); + return ( 'SPAN' === node.nodeName || isInline( node, tagName ) ) && + deepCheck( Array.from( node.children ), tagName ); } ); } diff --git a/blocks/api/raw-handling/utils.js b/blocks/api/raw-handling/utils.js index 1d17199c97bad..94a1f0dce1142 100644 --- a/blocks/api/raw-handling/utils.js +++ b/blocks/api/raw-handling/utils.js @@ -1,8 +1,21 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + /** * Browser dependencies */ const { ELEMENT_NODE, TEXT_NODE } = window.Node; +/** + * An array of tag groups used by isInlineForTag function. + * If tagName and nodeName are present in the same group, the node should be treated as inline. + * @type {Array} + */ +const inlineWhitelistTagGroups = [ +]; + const inlineWhitelist = { strong: {}, em: {}, @@ -63,8 +76,26 @@ export function isAttributeWhitelisted( tag, attribute ) { ); } -export function isInline( node ) { - return !! inlineWhitelist[ node.nodeName.toLowerCase() ]; +/** + * Checks if nodeName should be treated as inline when being added to tagName. + * This happens if nodeName and tagName are in the same group defined in inlineWhitelistTagGroups. + * + * @param {String} nodeName Node name. + * @param {String} tagName Tag name. + * @return {Boolean} True if nodeName is inline in the context of tagName and false otherwise. + */ +function isInlineForTag( nodeName, tagName ) { + if ( ! tagName || ! nodeName ) { + return false; + } + return inlineWhitelistTagGroups.some( tagGroup => + includes( tagGroup, nodeName ) && includes( tagGroup, tagName ) + ); +} + +export function isInline( node, tagName ) { + const nodeName = node.nodeName.toLowerCase(); + return !! inlineWhitelist[ nodeName ] || isInlineForTag( nodeName, tagName ); } export function isInlineWrapper( node ) { diff --git a/blocks/editable/index.js b/blocks/editable/index.js index 2307cf992b7bb..13bd6af1d6776 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -352,6 +352,7 @@ export default class Editable extends Component { HTML: this.pastedContent || HTML, plainText: this.pastedPlainText, mode, + tagName: this.props.tagName, } ); if ( typeof content === 'string' ) { From adbe44eccbffe96fd68613d6a30ff3420b1918f2 Mon Sep 17 00:00:00 2001 From: Jorge Date: Mon, 18 Dec 2017 10:57:04 +0000 Subject: [PATCH 2/3] Added tag groups for headings and lists to the inlineWhitelistTagGroups mechanism. This improves the behaviour of paste in already existing list and heading blocks. --- blocks/api/raw-handling/utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blocks/api/raw-handling/utils.js b/blocks/api/raw-handling/utils.js index 94a1f0dce1142..38689919f2c99 100644 --- a/blocks/api/raw-handling/utils.js +++ b/blocks/api/raw-handling/utils.js @@ -14,6 +14,8 @@ const { ELEMENT_NODE, TEXT_NODE } = window.Node; * @type {Array} */ const inlineWhitelistTagGroups = [ + [ 'ul', 'li', 'ol' ], + [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], ]; const inlineWhitelist = { From f7367ca54d16c7d5ca534d13bff04576bbe72cbc Mon Sep 17 00:00:00 2001 From: Jorge Date: Mon, 18 Dec 2017 10:58:44 +0000 Subject: [PATCH 3/3] Added test cases to isInlineContent to take into account the new isInlineForTag mechanism. --- blocks/api/raw-handling/test/is-inline-content.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/blocks/api/raw-handling/test/is-inline-content.js b/blocks/api/raw-handling/test/is-inline-content.js index 8511166339b50..56aa123eb72fc 100644 --- a/blocks/api/raw-handling/test/is-inline-content.js +++ b/blocks/api/raw-handling/test/is-inline-content.js @@ -12,6 +12,7 @@ describe( 'stripWrappers', () => { it( 'should be inline content', () => { equal( isInlineContent( 'test' ), true ); equal( isInlineContent( 'test' ), true ); + equal( isInlineContent( '
  • test
  • ', 'ul' ), true ); } ); it( 'should not be inline content', () => { @@ -19,5 +20,8 @@ describe( 'stripWrappers', () => { equal( isInlineContent( 'test
    test
    ' ), false ); equal( isInlineContent( 'test

    test' ), false ); equal( isInlineContent( '
    test
    ' ), false ); + equal( isInlineContent( '
  • test
  • ', 'p' ), false ); + equal( isInlineContent( '
  • test
  • ', 'h1' ), false ); + equal( isInlineContent( '

    test

    ', 'li' ), false ); } ); } );