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

Convert a Classic Block into Multiple Blocks #3179

Merged
merged 1 commit into from
Oct 31, 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
2 changes: 1 addition & 1 deletion blocks/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as source from './source';
export { source };
export { createBlock, switchToBlockType } from './factory';
export { default as parse, getSourcedAttributes } from './parser';
export { default as pasteHandler } from './paste';
export { default as rawHandler } from './raw-handling';
export { default as serialize, getBlockDefaultClassname, getBlockContent } from './serializer';
export { isValidBlock } from './validation';
export { getCategories } from './categories';
Expand Down
12 changes: 10 additions & 2 deletions blocks/api/paste/index.js → blocks/api/raw-handling/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ import inlineContentConverter from './inline-content-converter';
import { deepFilterHTML, isInvalidInline, isNotWhitelisted, isPlain, isInline } from './utils';
import showdown from 'showdown';

export default function( { HTML, plainText, inline } ) {
/**
* 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 {Boolean} [options.inline] Whether to content should be inline or not. Null to auto-detect, false to force blocks, true to force a string.
* @return {Array|String} A list of blocks or a string, depending on the `inline` option.
*/
export default function rawHandler( { HTML, plainText = '', inline = null } ) {
HTML = HTML.replace( /<meta[^>]+>/, '' );

// Block delimiters detected.
Expand Down Expand Up @@ -60,7 +68,7 @@ export default function( { HTML, plainText, inline } ) {
] );

// Inline paste.
if ( inline || isInlineContent( HTML ) ) {
if ( inline || ( inline === null && isInlineContent( HTML ) ) ) {
// Allows us to ask for this information when we get a report.
window.console.log( 'Processed inline HTML:\n\n', HTML );

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Paste
# Raw Handling (Paste)

This folder contains all paste specific logic (filters, converters, normalisers...). Each module is tested on their own, and in addition we have some integration tests for frequently used editors.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { equal, deepEqual } from 'assert';
/**
* Internal dependencies
*/
import paste from '../index';
import rawHandler from '../index';
import { registerBlockType, unregisterBlockType, setUnknownTypeHandlerName } from '../../registration';
import { createBlock } from '../../factory';
import { children, prop } from '../../source';

describe( 'paste', () => {
describe( 'rawHandler', () => {
beforeAll( () => {
registerBlockType( 'test/figure', {
category: 'common',
Expand Down Expand Up @@ -54,29 +54,38 @@ describe( 'paste', () => {
setUnknownTypeHandlerName( undefined );
} );

it( 'should convert recognised pasted content', () => {
const pastedBlock = paste( { HTML: '<figure>test</figure>' } )[ 0 ];
const block = createBlock( 'test/figure', { content: [ 'test' ] } );
it( 'should convert recognised raw content', () => {
const block = rawHandler( { HTML: '<figure>test</figure>' } )[ 0 ];
const { name, attributes } = createBlock( 'test/figure', { content: [ 'test' ] } );

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

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

equal( pastedBlock.name, 'test/unknown' );
equal( pastedBlock.attributes.content, '<figcaption>test</figcaption>' );
equal( block.name, 'test/unknown' );
equal( block.attributes.content, '<figcaption>test</figcaption>' );
} );

it( 'should filter inline content', () => {
const filtered = paste( {
const filtered = rawHandler( {
HTML: '<h2><em>test</em></h2>',
inline: true,
} );

equal( filtered, '<em>test</em>' );
} );

it( 'should always return blocks', () => {
const blocks = rawHandler( {
HTML: 'test',
inline: false,
} );

equal( Array.isArray( blocks ), true );
} );
} );

import './integration';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import path from 'path';
* Internal dependencies
*/
import '../../../../library';
import paste from '../../index';
import rawHandler from '../../index';
import serialize from '../../../serializer';

const types = [
Expand All @@ -20,13 +20,13 @@ const types = [
'ms-word-online',
];

describe( 'paste: integration', () => {
describe( 'raw handling: integration', () => {
types.forEach( ( type ) => {
it( type, () => {
const input = fs.readFileSync( path.join( __dirname, `${ type }-in.html` ), 'utf8' ).trim();
const output = fs.readFileSync( path.join( __dirname, `${ type }-out.html` ), 'utf8' ).trim();
const pasted = paste( { HTML: input } );
const serialized = typeof pasted === 'string' ? pasted : serialize( pasted );
const converted = rawHandler( { HTML: input } );
const serialized = typeof converted === 'string' ? converted : serialize( converted );

equal( output, serialized );
} );
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions blocks/editable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { keycodes } from '@wordpress/utils';
* Internal dependencies
*/
import './style.scss';
import { pasteHandler } from '../api';
import { rawHandler } from '../api';
import FormatToolbar from './format-toolbar';
import TinyMCE from './tinymce';
import { pickAriaProps } from './aria';
Expand Down Expand Up @@ -248,7 +248,7 @@ export default class Editable extends Component {
window.console.log( 'Received HTML:\n\n', event.content );
window.console.log( 'Received plain text:\n\n', this.pastedPlainText );

const content = pasteHandler( {
const content = rawHandler( {
HTML: event.content,
plainText: this.pastedPlainText,
inline: ! this.props.onSplit,
Expand Down
2 changes: 2 additions & 0 deletions editor/block-settings-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import BlockInspectorButton from './block-inspector-button';
import BlockModeToggle from './block-mode-toggle';
import BlockDeleteButton from './block-delete-button';
import { selectBlock } from '../actions';
import UnknownConverter from './unknown-converter';

function BlockSettingsMenu( { uids, onSelect } ) {
const count = uids.length;
Expand Down Expand Up @@ -50,6 +51,7 @@ function BlockSettingsMenu( { uids, onSelect } ) {
<div className="editor-block-settings-menu__content">
<BlockInspectorButton onClick={ onClose } />
{ count === 1 && <BlockModeToggle uid={ uids[ 0 ] } onToggle={ onClose } /> }
{ count === 1 && <UnknownConverter uid={ uids[ 0 ] } /> }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to include this in BlockToolbar (for mobile controls)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure what you mean.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block settings menu is hidden on mobile and these controls are shown in the block toolbar instead here lob/master/editor/block-toolbar/index.js#L143 Thinking we could do the same with this control.

<BlockDeleteButton uids={ uids } />
</div>
) }
Expand Down
50 changes: 50 additions & 0 deletions editor/block-settings-menu/unknown-converter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';
import { getUnknownTypeHandlerName, rawHandler, serialize } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { getBlock } from '../selectors';
import { replaceBlocks } from '../actions';

export function UnknownConverter( { block, convertToBlocks, small } ) {
if ( ! block || getUnknownTypeHandlerName() !== block.name ) {
return null;
}

const label = __( 'Convert to blocks' );

return (
<IconButton
className="editor-block-settings-menu__control"
onClick={ () => convertToBlocks( block ) }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely sure what the best way is here to avoid passing arguments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the desire to avoid passing the argument? If you mean so that the component doesn't need to be aware of a block prop, the only way would probably be overriding react-redux connect's third argument mergeProps for the dispatch to be aware of the block from state, but... probably more work than is really worthwhile.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make the component not know about the block? Don't you have to return null if the block isn't defined (or has an invalid name)?

icon="screenoptions"
label={ small ? label : undefined }
>
{ ! small && label }
</IconButton>
);
}

export default connect(
( state, { uid } ) => ( {
block: getBlock( state, uid ),
} ),
( dispatch, { uid } ) => ( {
Copy link
Member

@gziolo gziolo Oct 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have block already exposed in props. The following should work:

( dispatch, { block, uid } ) => ( {
	convertToBlocks() {
		dispatch( replaceBlocks( uid, rawHandler( {
			HTML: serialize( block ),
			inline: false,
		} ) ) );
	},
} )

Although @aduth might be correct that you would have to use 3rd param mergeProps here.

Yes, 2nd param is ownProps ...

Copy link
Member

@gziolo gziolo Oct 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another funky way to do the same is to wrap this with HOC withBlock :P

const withBlock = component => connect(  ( state, { uid } ) => ( {
	block: getBlock( state, uid ),
} ) )( component );

export default connect(
	null,
	( dispatch, { block, uid } ) => ( {
		convertToBlocks() {
			dispatch( replaceBlocks( uid, rawHandler( {
				HTML: serialize( block ),
				inline: false,
			} ) ) );
		},
	} )
)( withBlock( UnknownConverter ) );

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with keeping as is :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

convertToBlocks( block ) {
dispatch( replaceBlocks( uid, rawHandler( {
HTML: serialize( block ),
inline: false,
} ) ) );
},
} )
)( UnknownConverter );
2 changes: 2 additions & 0 deletions editor/block-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import BlockInspectorButton from '../block-settings-menu/block-inspector-button'
import BlockModeToggle from '../block-settings-menu/block-mode-toggle';
import BlockDeleteButton from '../block-settings-menu/block-delete-button';
import { getBlockMode, getSelectedBlock } from '../selectors';
import UnknownConverter from '../block-settings-menu/unknown-converter';

class BlockToolbar extends Component {
constructor() {
Expand Down Expand Up @@ -77,6 +78,7 @@ class BlockToolbar extends Component {
<BlockMover uids={ [ block.uid ] } />
<BlockInspectorButton small />
<BlockModeToggle uid={ block.uid } small />
<UnknownConverter uid={ block.uid } small />
<BlockDeleteButton uids={ [ block.uid ] } small />
</div>
}
Expand Down