Skip to content

Commit

Permalink
Implement pasting external content with plain and styled text (#13841)
Browse files Browse the repository at this point in the history
* Refactor raw-handler and paste-handler into their own files

* Add undefined and null checks for jsdom-jscore node methods/properties

* Implement createRecord method for converting native content to RichText

* Refactor splitContent and onEnter for common interface with onPaste

* Begin implementing onPaste for plain and styled text

* Fix some lint errors and some invalid references.

* Add undefined check for jsdom-jscore node in phrasing-content-reducer

* Add onReplace method to ParagraphEdit

* Refactor raw-handler back into index.js

* Remove unused parameters from RichText create method
  • Loading branch information
mkevins authored and youknowriad committed Mar 6, 2019
1 parent 3ca7834 commit 705979b
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 186 deletions.
16 changes: 16 additions & 0 deletions packages/block-library/src/paragraph/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ParagraphEdit extends Component {
constructor( props ) {
super( props );
this.splitBlock = this.splitBlock.bind( this );
this.onReplace = this.onReplace.bind( this );

this.state = {
aztecHeight: 0,
Expand Down Expand Up @@ -71,6 +72,20 @@ class ParagraphEdit extends Component {
}
}

onReplace( blocks ) {
const { attributes, onReplace } = this.props;
onReplace( blocks.map( ( block, index ) => (
index === 0 && block.name === name ?
{ ...block,
attributes: {
...attributes,
...block.attributes,
},
} :
block
) ) );
}

render() {
const {
attributes,
Expand Down Expand Up @@ -106,6 +121,7 @@ class ParagraphEdit extends Component {
} }
onSplit={ this.splitBlock }
onMerge={ mergeBlocks }
onReplace={ this.onReplace }
onContentSizeChange={ ( event ) => {
this.setState( { aztecHeight: event.aztecHeight } );
} }
Expand Down
3 changes: 2 additions & 1 deletion packages/blocks/src/api/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
} from './factory';
export {
default as parse,
getBlockAttributes,
parseWithAttributeSchema,
} from './parser';
export {
Expand All @@ -28,5 +29,5 @@ export {
export {
isUnmodifiedDefaultBlock,
} from './utils';
export { getPhrasingContentSchema } from './raw-handling';
export { pasteHandler, getPhrasingContentSchema } from './raw-handling';
export { default as children } from './children';
167 changes: 2 additions & 165 deletions packages/blocks/src/api/raw-handling/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,20 @@ import { flatMap, filter, compact } from 'lodash';
* Internal dependencies
*/
import { createBlock, getBlockTransforms, findTransform } from '../factory';
import { getBlockContent } from '../serializer';
import { getBlockAttributes, parseWithGrammar } from '../parser';
import normaliseBlocks from './normalise-blocks';
import specialCommentConverter from './special-comment-converter';
import isInlineContent from './is-inline-content';
import phrasingContentReducer from './phrasing-content-reducer';
import headRemover from './head-remover';
import msListConverter from './ms-list-converter';
import listReducer from './list-reducer';
import imageCorrector from './image-corrector';
import blockquoteNormaliser from './blockquote-normaliser';
import figureContentReducer from './figure-content-reducer';
import shortcodeConverter from './shortcode-converter';
import markdownConverter from './markdown-converter';
import iframeRemover from './iframe-remover';
import { getPhrasingContentSchema } from './phrasing-content';
import {
deepFilterHTML,
isPlain,
removeInvalidHTML,
getBlockContentSchema,
} from './utils';

/**
* Browser dependencies
*/
const { console } = window;

export { getPhrasingContentSchema };

/**
* Filters HTML to only contain phrasing content.
*
* @param {string} HTML The HTML to filter.
*
* @return {string} HTML only containing phrasing content.
*/
function filterInlineHTML( HTML ) {
HTML = deepFilterHTML( HTML, [ phrasingContentReducer ] );
HTML = removeInvalidHTML( HTML, getPhrasingContentSchema(), { inline: true } );

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

return HTML;
}
export { getPhrasingContentSchema } from './phrasing-content';
export { pasteHandler } from './paste-handler';

function getRawTransformations() {
return filter( getBlockTransforms( 'from' ), { type: 'raw' } )
Expand Down Expand Up @@ -110,137 +78,6 @@ function htmlToBlocks( { html, rawTransforms } ) {
} );
}

/**
* Converts an HTML string to known blocks. Strips everything else.
*
* @param {string} [options.HTML] The HTML to convert.
* @param {string} [options.plainText] Plain text version.
* @param {string} [options.mode] Handle content as blocks or inline content.
* * '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.
* @param {boolean} [options.canUserUseUnfilteredHTML] Whether or not the user can use unfiltered HTML.
*
* @return {Array|string} A list of blocks or a string, depending on `handlerMode`.
*/
export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagName, canUserUseUnfilteredHTML = false } ) {
// First of all, strip any meta tags.
HTML = HTML.replace( /<meta[^>]+>/, '' );

// If we detect block delimiters, parse entirely as blocks.
if ( mode !== 'INLINE' && HTML.indexOf( '<!-- wp:' ) !== -1 ) {
return parseWithGrammar( HTML );
}

// Normalize unicode to use composed characters.
// This is unsupported in IE 11 but it's a nice-to-have feature, not mandatory.
// Not normalizing the content will only affect older browsers and won't
// entirely break the app.
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
// See: https://core.trac.wordpress.org/ticket/30130
// See: https://github.com/WordPress/gutenberg/pull/6983#pullrequestreview-125151075
if ( String.prototype.normalize ) {
HTML = HTML.normalize();
}

// Parse Markdown (and encoded HTML) if:
// * There is a plain text version.
// * There is no HTML version, or it has no formatting.
if ( plainText && ( ! HTML || isPlain( HTML ) ) ) {
HTML = markdownConverter( plainText );

// Switch to inline mode if:
// * The current mode is AUTO.
// * The original plain text had no line breaks.
// * The original plain text was not an HTML paragraph.
// * The converted text is just a paragraph.
if (
mode === 'AUTO' &&
plainText.indexOf( '\n' ) === -1 &&
plainText.indexOf( '<p>' ) !== 0 &&
HTML.indexOf( '<p>' ) === 0
) {
mode = 'INLINE';
}
}

if ( mode === 'INLINE' ) {
return filterInlineHTML( HTML );
}

// An array of HTML strings and block objects. The blocks replace matched
// shortcodes.
const pieces = shortcodeConverter( HTML );

// The call to shortcodeConverter will always return more than one element
// if shortcodes are matched. The reason is when shortcodes are matched
// empty HTML strings are included.
const hasShortcodes = pieces.length > 1;

if ( mode === 'AUTO' && ! hasShortcodes && isInlineContent( HTML, tagName ) ) {
return filterInlineHTML( HTML );
}

const rawTransforms = getRawTransformations();
const phrasingContentSchema = getPhrasingContentSchema();
const blockContentSchema = getBlockContentSchema( rawTransforms );

const blocks = compact( flatMap( pieces, ( piece ) => {
// Already a block from shortcode.
if ( typeof piece !== 'string' ) {
return piece;
}

const filters = [
msListConverter,
headRemover,
listReducer,
imageCorrector,
phrasingContentReducer,
specialCommentConverter,
figureContentReducer,
blockquoteNormaliser,
];

if ( ! canUserUseUnfilteredHTML ) {
// Should run before `figureContentReducer`.
filters.unshift( iframeRemover );
}

const schema = {
...blockContentSchema,
// Keep top-level phrasing content, normalised by `normaliseBlocks`.
...phrasingContentSchema,
};

piece = deepFilterHTML( piece, filters, blockContentSchema );
piece = removeInvalidHTML( piece, schema );
piece = normaliseBlocks( piece );

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

return htmlToBlocks( { html: piece, rawTransforms } );
} ) );

// If we're allowed to return inline content and there is only one block
// and the original plain text content does not have any line breaks, then
// treat it as inline paste.
if ( mode === 'AUTO' && blocks.length === 1 ) {
const trimmedPlainText = plainText.trim();

if ( trimmedPlainText !== '' && trimmedPlainText.indexOf( '\n' ) === -1 ) {
return removeInvalidHTML(
getBlockContent( blocks[ 0 ] ),
phrasingContentSchema
);
}
}

return blocks;
}

/**
* Converts an HTML string to known blocks.
*
Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/raw-handling/index.native.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { getPhrasingContentSchema } from './phrasing-content';
export { pasteHandler } from './paste-handler';
Loading

0 comments on commit 705979b

Please sign in to comment.