Skip to content

Commit

Permalink
Missing Blocks: Allow extracting contents as blocks
Browse files Browse the repository at this point in the history
Resolves #13391
Alternate approach to #14443

When the editor encounters a block without a registered block type
it transforms that unknown block into a generic "missing block."

A post's author is free to convert that unknown block into a kind
of static view from the original saved HTML of the block.

Unfortunately this model breaks down when the original block
includes nested blocks. Up until this point the editor has failed
to account for those inner blocks.

---

In this patch we're adding a new option to the missing plugin to
allow "extracting" the inner contents. That is, instead of simply
converting to static HTML the extract option splits the inner
contents into a new list of blocks as if simply removing the
outer wrapper.

This approach doesn't completely solve the editor's problem because
parts of the extracted contents may be wrong, but it provides a
reasonable fallback behavior to recover what is possible in the
situation where the editor can't understand a block.

---

 - extracts inner blocks and content when a block is unsupported
 - creates `core/html` blocks out of non-block content inside
   unsupported blocks

 - solve the issue of missing inner block HTML when converting
   to static HTML. this should be resolved in another patch.
 - improve the messaging for unsupported blocks during an edit session
 - fully resolve issues surrounding unsupported blocks
  • Loading branch information
dmsnell committed Apr 19, 2019
1 parent afce486 commit c609256
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 26 deletions.
12 changes: 7 additions & 5 deletions packages/block-library/src/missing/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
"originalName": {
"type": "string"
},
"originalUndelimitedContent": {
"type": "string"
"originalAttributes": {
"type": "object"
},
"originalInnerContent": {
"type": "array"
},
"originalContent": {
"type": "string",
"source": "html"
"originalInnerBlocks": {
"type": "array"
}
}
}
42 changes: 35 additions & 7 deletions packages/block-library/src/missing/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { getBlockType, createBlock } from '@wordpress/blocks';
import { withDispatch } from '@wordpress/data';
import { Warning } from '@wordpress/block-editor';

function MissingBlockWarning( { attributes, convertToHTML } ) {
const { originalName, originalUndelimitedContent } = attributes;
const hasContent = !! originalUndelimitedContent;
function MissingBlockWarning( { attributes, convertToHTML, convertToBlocks } ) {
const { originalName, originalInnerContent } = attributes;
const hasContent = !! originalInnerContent.length;
const hasHTMLBlock = getBlockType( 'core/html' );

const actions = [];
Expand All @@ -25,6 +25,11 @@ function MissingBlockWarning( { attributes, convertToHTML } ) {
{ __( 'Keep as HTML' ) }
</Button>
);
actions.push(
<Button key="convert_to_blocks" onClick={ convertToBlocks } isLarge isPrimary>
{ __( 'Extract contents as blocks' ) }
</Button>
);
} else {
messageHTML = sprintf(
__( 'Your site doesn’t include support for the "%s" block. You can leave this block intact or remove it entirely.' ),
Expand All @@ -37,17 +42,40 @@ function MissingBlockWarning( { attributes, convertToHTML } ) {
<Warning actions={ actions }>
{ messageHTML }
</Warning>
<RawHTML>{ originalUndelimitedContent }</RawHTML>
<RawHTML>{ originalInnerContent.join( '' ) }</RawHTML>
</Fragment>
);
}

const MissingEdit = withDispatch( ( dispatch, { clientId, attributes } ) => {
const { replaceBlock } = dispatch( 'core/block-editor' );
const MissingEdit = withDispatch( ( dispatch, props ) => {
const { clientId, attributes: { originalInnerBlocks: innerBlocks, originalInnerContent: innerContent } } = props;
const { replaceBlock, replaceBlocks } = dispatch( 'core/block-editor' );
return {
convertToBlocks() {
const blocks = [];
let blockIndex = 0;

for ( const content of innerContent ) {
// just an inner block
if ( null === content ) {
blocks.push( innerBlocks[ blockIndex++ ] );
continue;
}

// skip whitespace-only freeform HTML content
// it usually comes in between blocks
if ( ! content.blockName && ! content.trim().length ) {
continue;
}

blocks.push( createBlock( 'core/html', { content } ) );
}

replaceBlocks( clientId, blocks );
},
convertToHTML() {
replaceBlock( clientId, createBlock( 'core/html', {
content: attributes.originalUndelimitedContent,
content: innerContent.join( '' ),
} ) );
},
};
Expand Down
24 changes: 10 additions & 14 deletions packages/blocks/src/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
} from './registration';
import { createBlock } from './factory';
import { isValidBlockContent } from './validation';
import { getCommentDelimitedContent } from './serializer';
import { attr, html, text, query, node, children, prop } from './matchers';
import { normalizeBlockType } from './utils';

Expand Down Expand Up @@ -422,6 +421,7 @@ export function createBlockWithFallback( blockNode ) {
innerBlocks = [],
innerHTML,
} = blockNode;
const { innerContent = [] } = blockNode;
const freeformContentFallbackBlock = getFreeformContentHandlerName();
const unregisteredFallbackBlock = getUnregisteredTypeHandlerName() || freeformContentFallbackBlock;

Expand Down Expand Up @@ -454,24 +454,20 @@ export function createBlockWithFallback( blockNode ) {
// Try finding the type for known block name, else fall back again.
let blockType = getBlockType( name );

if ( ! blockType ) {
// Preserve undelimited content for use by the unregistered type handler.
const originalUndelimitedContent = innerHTML;

// If detected as a block which is not registered, preserve comment
// delimiters in content of unregistered type handler.
if ( name ) {
innerHTML = getCommentDelimitedContent( name, attributes, innerHTML );
}
// Coerce inner blocks from parsed form to canonical form.
innerBlocks = innerBlocks.map( createBlockWithFallback );

if ( ! blockType ) {
name = unregisteredFallbackBlock;
attributes = { originalName, originalUndelimitedContent };
attributes = {
originalName,
originalAttributes: attributes,
originalInnerContent: innerContent,
originalInnerBlocks: innerBlocks,
};
blockType = getBlockType( name );
}

// Coerce inner blocks from parsed form to canonical form.
innerBlocks = innerBlocks.map( createBlockWithFallback );

const isFallbackBlock = (
name === freeformContentFallbackBlock ||
name === unregisteredFallbackBlock
Expand Down

0 comments on commit c609256

Please sign in to comment.