Skip to content

Commit

Permalink
Add UI for unregistered block types
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonpayton committed Aug 3, 2018
1 parent 3823721 commit 59811a0
Show file tree
Hide file tree
Showing 21 changed files with 402 additions and 47 deletions.
9 changes: 7 additions & 2 deletions core-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import {
registerBlockType,
setDefaultBlockName,
setUnknownTypeHandlerName,
setUnstructuredTypeHandlerName,
setUnregisteredTypeHandlerName,
} from '@wordpress/blocks';

/**
Expand All @@ -31,6 +32,7 @@ import * as html from './html';
import * as latestComments from './latest-comments';
import * as latestPosts from './latest-posts';
import * as list from './list';
import * as missing from './missing';
import * as more from './more';
import * as nextpage from './nextpage';
import * as preformatted from './preformatted';
Expand Down Expand Up @@ -74,6 +76,7 @@ export const registerCoreBlocks = () => {
html,
latestComments,
latestPosts,
missing,
more,
nextpage,
preformatted,
Expand All @@ -91,5 +94,7 @@ export const registerCoreBlocks = () => {
} );

setDefaultBlockName( paragraph.name );
setUnknownTypeHandlerName( freeform.name );
setUnstructuredTypeHandlerName( freeform.name );
// TODO: Consider renaming "Unregistered" to missing
setUnregisteredTypeHandlerName( missing.name );
};
5 changes: 5 additions & 0 deletions core-blocks/missing/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.editor-block-list__block[data-type="core/missing"] {
.editor-warning {
position: static;
}
}
45 changes: 45 additions & 0 deletions core-blocks/missing/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { RawHTML } from '@wordpress/element';

/**
* Internal dependencies.
*/
import MissingBlockWarning from './missing-block-warning';
import './editor.scss';

export const name = 'core/missing';

export const settings = {
name,
category: 'common',
title: __( 'Missing Block' ),

supports: {
className: false,
customClassName: false,
inserter: false,
html: false,
preserveOriginalContent: true,
},

attributes: {
originalName: {
type: 'string',
},
originalUndelimitedContent: {
type: 'string',
},
originalContent: {
type: 'string',
source: 'html',
},
},

edit: MissingBlockWarning,
save( { attributes } ) {
return <RawHTML>{ attributes.originalContent }</RawHTML>;
},
};
62 changes: 62 additions & 0 deletions core-blocks/missing/missing-block-warning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { getBlockType, createBlock } from '@wordpress/blocks';
import { withSelect, withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { Warning } from '@wordpress/editor';

export const name = 'core/unknown';

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

const actions = [];
let messageHTML;
if ( hasContent && hasHTMLBlock ) {
actions.push(
<Button key="convert" onClick={ convertToHTML } isLarge isPrimary>
{ __( 'HTML Block' ) }
</Button>
);
messageHTML = sprintf(
__( 'Your site doesn\'t include support for the <code>%s</code> block. You can leave the block intact, convert its content to a Custom HTML block, or remove it entirely.' ),
originalName
);
} else {
messageHTML = sprintf(
__( 'Your site doesn\'t include support for the <code>%s</code> block. You can leave the block intact or remove it entirely.' ),
originalName
);
}

return (
<Warning actions={ actions }>
<span dangerouslySetInnerHTML={ { __html: messageHTML } } />
</Warning>
);
}

export default compose( [
withSelect( ( select, { clientId } ) => {
const { getBlock } = select( 'core/editor' );
return {
block: getBlock( clientId ),
};
} ),
withDispatch( ( dispatch, { block, attributes } ) => {
const { replaceBlock } = dispatch( 'core/editor' );
return {
convertToHTML() {
replaceBlock( block.clientId, createBlock( 'core/html', {
content: attributes.originalUndelimitedContent,
} ) );
},
};
} ),
] )( MissingBlockWarning );

6 changes: 6 additions & 0 deletions core-blocks/test/fixtures/core__missing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- wp:unregistered/example {"attr1":"One","attr2":"Two"} -->
<p>Testing missing block with some</p>
<div class="wp-some-class">
HTML <span style="color: red;">content</span>
</div>
<!-- /wp:unregistered/example -->
14 changes: 14 additions & 0 deletions core-blocks/test/fixtures/core__missing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"clientId": "_clientId_0",
"name": "core/missing",
"isValid": true,
"attributes": {
"originalContent": "<!-- wp:unregistered/example {\"attr1\":\"One\",\"attr2\":\"Two\"} -->\n<p>Testing missing block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>\n<!-- /wp:unregistered/example -->",
"originalName": "unregistered/example",
"originalUndelimitedContent": "<p>Testing missing block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>"
},
"innerBlocks": [],
"originalContent": "<!-- wp:unregistered/example {\"attr1\":\"One\",\"attr2\":\"Two\"} -->\n<p>Testing missing block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>\n<!-- /wp:unregistered/example -->"
}
]
15 changes: 15 additions & 0 deletions core-blocks/test/fixtures/core__missing.parsed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"blockName": "unregistered/example",
"attrs": {
"attr1": "One",
"attr2": "Two"
},
"innerBlocks": [],
"innerHTML": "\n<p>Testing missing block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>\n"
},
{
"attrs": {},
"innerHTML": "\n"
}
]
6 changes: 6 additions & 0 deletions core-blocks/test/fixtures/core__missing.serialized.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- wp:unregistered/example {"attr1":"One","attr2":"Two"} -->
<p>Testing missing block with some</p>
<div class="wp-some-class">
HTML <span style="color: red;">content</span>
</div>
<!-- /wp:unregistered/example -->
4 changes: 4 additions & 0 deletions packages/blocks/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export {
unregisterBlockType,
setUnknownTypeHandlerName,
getUnknownTypeHandlerName,
setUnstructuredTypeHandlerName,
getUnstructuredTypeHandlerName,
setUnregisteredTypeHandlerName,
getUnregisteredTypeHandlerName,
setDefaultBlockName,
getDefaultBlockName,
getDefaultBlockForPostFormat,
Expand Down
44 changes: 30 additions & 14 deletions packages/blocks/src/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { parse as grammarParse } from '@wordpress/block-serialization-spec-parse
/**
* Internal dependencies
*/
import { getBlockType, getUnknownTypeHandlerName } from './registration';
import {
getBlockType,
getUnknownTypeHandlerName,
getUnstructuredTypeHandlerName,
getUnregisteredTypeHandlerName,
} from './registration';
import { createBlock } from './factory';
import { isValidBlock } from './validation';
import { getCommentDelimitedContent } from './serializer';
Expand Down Expand Up @@ -275,54 +280,65 @@ export function getMigratedBlock( block ) {
* @return {?Object} An initialized block object (if possible).
*/
export function createBlockWithFallback( blockNode ) {
const { blockName: originalName } = blockNode;
let {
blockName: name,
attrs: attributes,
innerBlocks = [],
innerHTML,
} = blockNode;
const fallbackBlock = getUnknownTypeHandlerName();
const unstructuredFallbackBlock = getUnstructuredTypeHandlerName();
const unregisteredFallbackBlock = getUnregisteredTypeHandlerName();
const isUnstructuredFallback = ( name ) => (
name === unstructuredFallbackBlock ||
name === fallbackBlock
);
const isFallbackBlock = ( name ) => (
name === unstructuredFallbackBlock ||
name === unregisteredFallbackBlock ||
name === fallbackBlock
);

attributes = attributes || {};

// Trim content to avoid creation of intermediary freeform segments.
innerHTML = innerHTML.trim();
const originalUndelimitedContent = innerHTML = innerHTML.trim();

// Use type from block content, otherwise find unknown handler.
name = name || getUnknownTypeHandlerName();
let name = originalName || unstructuredFallbackBlock || fallbackBlock;

// Convert 'core/text' blocks in existing content to 'core/paragraph'.
if ( 'core/text' === name || 'core/cover-text' === name ) {
name = 'core/paragraph';
}

// Try finding the type for known block name, else fall back again.
let blockType = getBlockType( name );

const fallbackBlock = getUnknownTypeHandlerName();

// Fallback content may be upgraded from classic editor expecting implicit
// automatic paragraphs, so preserve them. Assumes wpautop is idempotent,
// meaning there are no negative consequences to repeated autop calls.
if ( name === fallbackBlock ) {
if ( isUnstructuredFallback( name ) ) {
innerHTML = autop( innerHTML ).trim();
}

// Try finding the type for known block name, else fall back again.
let blockType = getBlockType( name );

if ( ! blockType ) {
// If detected as a block which is not registered, preserve comment
// delimiters in content of unknown type handler.
// delimiters in content of unregistered type handler.
if ( name ) {
innerHTML = getCommentDelimitedContent( name, attributes, innerHTML );
}

name = fallbackBlock;
name = unregisteredFallbackBlock || fallbackBlock;
attributes = { originalName, originalUndelimitedContent };
blockType = getBlockType( name );
}

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

// Include in set only if type were determined.
if ( ! blockType || ( ! innerHTML && name === fallbackBlock ) ) {
if ( ! blockType || ( ! innerHTML && isFallbackBlock( name ) ) ) {
return;
}

Expand All @@ -336,7 +352,7 @@ export function createBlockWithFallback( blockNode ) {
// provided there are no changes in attributes. The validation procedure thus compares the
// provided source value with the serialized output before there are any modifications to
// the block. When both match, the block is marked as valid.
if ( name !== fallbackBlock ) {
if ( ! isFallbackBlock( name ) ) {
block.isValid = isValidBlock( innerHTML, blockType, block.attributes );
}

Expand Down
43 changes: 43 additions & 0 deletions packages/blocks/src/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ export function unregisterBlockType( name ) {
* @param {string} name Block name.
*/
export function setUnknownTypeHandlerName( name ) {
deprecated( 'setUnknownTypeHandlerName', {
plugin: 'Gutenberg',
version: '3.7',
alternative: 'setUnstructuredTypeHandlerName and setUnregisteredTypeHandlerName',
} );
dispatch( 'core/blocks' ).setFallbackBlockName( name );
}

Expand All @@ -211,6 +216,44 @@ export function getUnknownTypeHandlerName() {
return select( 'core/blocks' ).getFallbackBlockName();
}

/**
* Assigns name of block for handling non-block content.
*
* @param {string} name Block name.
*/
export function setUnstructuredTypeHandlerName( name ) {
dispatch( 'core/blocks' ).setUnstructuredFallbackBlockName( name );
}

/**
* Retrieves name of block handling non-block content, or undefined if no
* handler has been defined.
*
* @return {?string} Blog name.
*/
export function getUnstructuredTypeHandlerName() {
return select( 'core/blocks' ).getUnstructuredFallbackBlockName();
}

/**
* Assigns name of block handling unregistered block types.
*
* @param {string} name Block name.
*/
export function setUnregisteredTypeHandlerName( name ) {
dispatch( 'core/blocks' ).setUnregisteredFallbackBlockName( name );
}

/**
* Retrieves name of block handling unregistered block types, or undefined if no
* handler has been defined.
*
* @return {?string} Blog name.
*/
export function getUnregisteredTypeHandlerName() {
return select( 'core/blocks' ).getUnregisteredFallbackBlockName();
}

/**
* Assigns the default block name.
*
Expand Down
9 changes: 8 additions & 1 deletion packages/blocks/src/api/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import isShallowEqual from '@wordpress/is-shallow-equal';
/**
* Internal dependencies
*/
import { getBlockType, getUnknownTypeHandlerName } from './registration';
import {
getBlockType,
getUnknownTypeHandlerName,
getUnstructuredTypeHandlerName,
getUnregisteredTypeHandlerName,
} from './registration';
import BlockContentProvider from '../block-content-provider';

/**
Expand Down Expand Up @@ -261,6 +266,8 @@ export function serializeBlock( block ) {
const saveAttributes = getCommentAttributes( block.attributes, blockType );

switch ( blockName ) {
case getUnstructuredTypeHandlerName():
case getUnregisteredTypeHandlerName():
case getUnknownTypeHandlerName():
return saveContent;

Expand Down
Loading

0 comments on commit 59811a0

Please sign in to comment.