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

Framework: Extract the block editor from the editor chrome to make it reusable #2065

Closed
wants to merge 3 commits into from
Closed
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
40 changes: 11 additions & 29 deletions blocks/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,22 @@
import uuid from 'uuid/v4';
import { get, castArray, findIndex, isObjectLike, find } from 'lodash';

/**
* Internal dependencies
*/
import { getBlockType } from './registration';

/**
* Returns a block object given its type and attributes.
*
* @param {String} name Block name
* @param {String} blockType Block type
* @param {Object} attributes Block attributes
* @return {Object} Block object
*/
export function createBlock( name, attributes = {} ) {
// Get the type definition associated with a registered block.
const blockType = getBlockType( name );

export function createBlock( blockType, attributes = {} ) {
// Do we need this? What purpose does it have?
let defaultAttributes;
if ( blockType ) {
defaultAttributes = blockType.defaultAttributes;
}
const defaultAttributes = blockType.defaultAttributes;

// Blocks are stored with a unique ID, the assigned type name,
// and the block attributes.
return {
uid: uuid(),
name,
name: blockType.name,
isValid: true,
attributes: {
...defaultAttributes,
Expand All @@ -42,19 +31,18 @@ export function createBlock( name, attributes = {} ) {
/**
* Switch a block into one or more blocks of the new block type.
*
* @param {Object} block Block object
* @param {string} name Block name
* @return {Array} Block object
* @param {Object} block Block object
* @param {string} sourceType Source Block type
* @param {string} destinationType Destination Block type
* @return {Array} Block object
*/
export function switchToBlockType( block, name ) {
export function switchToBlockType( block, sourceType, destinationType ) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this change needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because we don't rely on the globally registered blocks anymore to ensure genericity of the block editor.

By the way, #2182 is an attempt to split the current PR into smaller steps. It's intended as the first iteration to achieve this POC.

// Find the right transformation by giving priority to the "to"
// transformation.
const destinationType = getBlockType( name );
const sourceType = getBlockType( block.name );
const transformationsFrom = get( destinationType, 'transforms.from', [] );
const transformationsTo = get( sourceType, 'transforms.to', [] );
const transformation =
find( transformationsTo, t => t.blocks.indexOf( name ) !== -1 ) ||
find( transformationsTo, t => t.blocks.indexOf( destinationType.name ) !== -1 ) ||
find( transformationsFrom, t => t.blocks.indexOf( block.name ) !== -1 );

// Stop if there is no valid transformation. (How did we get here?)
Expand All @@ -74,13 +62,7 @@ export function switchToBlockType( block, name ) {
// with an array instead.
transformationResults = castArray( transformationResults );

// Ensure that every block object returned by the transformation has a
// valid block type.
if ( transformationResults.some( ( result ) => ! getBlockType( result.name ) ) ) {
return null;
}

const firstSwitchedBlock = findIndex( transformationResults, ( result ) => result.name === name );
const firstSwitchedBlock = findIndex( transformationResults, ( result ) => result.name === destinationType.name );

// Ensure that at least one block object returned by the transformation has
// the expected "destination" block type.
Expand Down
46 changes: 21 additions & 25 deletions blocks/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
* External dependencies
*/
import { parse as hpqParse } from 'hpq';
import { pickBy } from 'lodash';
import { pickBy, find } from 'lodash';

/**
* Internal dependencies
*/
import { parse as grammarParse } from './post.pegjs';
import { getBlockType, getUnknownTypeHandler } from './registration';
import { createBlock } from './factory';
import { getBeautifulContent, getSaveContent } from './serializer';

Expand Down Expand Up @@ -63,38 +62,30 @@ export function getBlockAttributes( blockType, rawContent, attributes ) {
/**
* Creates a block with fallback to the unknown type handler.
*
* @param {?String} name Block type name
* @param {String} rawContent Raw block content
* @param {?Object} attributes Attributes obtained from block delimiters
* @return {?Object} An initialized block object (if possible)
* @param {Object} blockType Block type name
* @param {Object} fallbackBlockType Fallback Block type name
* @param {String} rawContent Raw block content
* @param {?Object} attributes Attributes obtained from block delimiters
* @return {?Object} An initialized block object (if possible)
*/
export function createBlockWithFallback( name, rawContent, attributes ) {
// Use type from block content, otherwise find unknown handler.
name = name || getUnknownTypeHandler();

// Try finding type for known block name, else fall back again.
let blockType = getBlockType( name );
const fallbackBlock = getUnknownTypeHandler();
if ( ! blockType ) {
name = fallbackBlock;
blockType = getBlockType( name );
}
export function createBlockWithFallback( blockType, fallbackBlockType, rawContent, attributes ) {
const parsedBlockType = blockType || fallbackBlockType;

// Include in set only if type were determined.
// TODO do we ever expect there to not be an unknown type handler?
if ( blockType && ( rawContent || name !== fallbackBlock ) ) {
if ( parsedBlockType && ( rawContent || parsedBlockType !== fallbackBlockType ) ) {
// TODO allow blocks to opt-in to receiving a tree instead of a string.
// Gradually convert all blocks to this new format, then remove the
// string serialization.
const block = createBlock(
name,
getBlockAttributes( blockType, rawContent, attributes )
parsedBlockType,
getBlockAttributes( parsedBlockType, rawContent, attributes )
);

// Validate that the parsed block is valid, meaning that if we were to
// reserialize it given the assumed attributes, the markup matches the
// original value. Otherwise, preserve original to avoid destruction.
block.isValid = isValidBlock( rawContent, blockType, block.attributes );
block.isValid = isValidBlock( rawContent, parsedBlockType, block.attributes );
if ( ! block.isValid ) {
block.originalContent = rawContent;
}
Expand Down Expand Up @@ -138,13 +129,18 @@ export function isValidBlock( rawContent, blockType, attributes ) {
/**
* Parses the post content with a PegJS grammar and returns a list of blocks.
*
* @param {String} content The post content
* @return {Array} Block list
* @param {String} content The post content
* @param {Array} blockTypes Block Types
* @param {String} fallbackBlockName Fallback Block Name
* @return {Array} Block list
*/
export function parseWithGrammar( content ) {
export function parseWithGrammar( content, blockTypes, fallbackBlockName ) {
const getBlockType = ( name ) => find( blockTypes, ( bt ) => bt.name === name );
const fallbackBlockType = getBlockType( fallbackBlockName );
return grammarParse( content ).reduce( ( memo, blockNode ) => {
const { blockName, rawContent, attrs } = blockNode;
const block = createBlockWithFallback( blockName, rawContent.trim(), attrs );
const blockType = getBlockType( blockName );
const block = createBlockWithFallback( blockType, fallbackBlockType, rawContent.trim(), attrs );
if ( block ) {
memo.push( block );
}
Expand Down
10 changes: 6 additions & 4 deletions blocks/api/paste.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ export function normaliseToBlockLevelNodes( nodes ) {
}

export default function( nodes ) {
const blockTypes = getBlockTypes();
const unknownTypeBlockType = find( blockTypes, ( bt ) => bt.name === getUnknownTypeHandler() );
return normaliseToBlockLevelNodes( nodes ).map( ( node ) => {
const block = getBlockTypes().reduce( ( acc, blockType ) => {
const block = blockTypes.reduce( ( acc, blockType ) => {
if ( acc ) {
return acc;
}
Expand All @@ -83,17 +85,17 @@ export default function( nodes ) {
return acc;
}

const { name, defaultAttributes = [] } = blockType;
const { defaultAttributes = [] } = blockType;
const attributes = parseBlockAttributes( node.outerHTML, transform.attributes );

return createBlock( name, { ...defaultAttributes, ...attributes } );
return createBlock( blockType, { ...defaultAttributes, ...attributes } );
}, null );

if ( block ) {
return block;
}

return createBlock( getUnknownTypeHandler(), {
return createBlock( unknownTypeBlockType, {
content: node.outerHTML,
} );
} );
Expand Down
20 changes: 12 additions & 8 deletions blocks/api/serializer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { isEmpty, reduce, isObject } from 'lodash';
import { isEmpty, reduce, isObject, find } from 'lodash';
import { html as beautifyHtml } from 'js-beautify';
import classnames from 'classnames';

Expand All @@ -13,7 +13,6 @@ import { Component, createElement, renderToString, cloneElement, Children } from
/**
* Internal dependencies
*/
import { getBlockType } from './registration';
import { parseBlockAttributes } from './parser';

/**
Expand Down Expand Up @@ -125,9 +124,8 @@ export function getBeautifulContent( content ) {
} );
}

export function serializeBlock( block ) {
export function serializeBlock( block, blockType ) {
const blockName = block.name;
const blockType = getBlockType( blockName );

let saveContent;
if ( block.isValid ) {
Expand Down Expand Up @@ -162,9 +160,15 @@ export function serializeBlock( block ) {
/**
* Takes a block list and returns the serialized post content.
*
* @param {Array} blocks Block list
* @return {String} The post content
* @param {Array} blocks Block list
* @param {Array} blockTypes Block Types
* @return {String} The post content
*/
export default function serialize( blocks ) {
return blocks.map( serializeBlock ).join( '\n\n' );
export default function serialize( blocks, blockTypes ) {
const getBlockType = ( name ) => find( blockTypes, ( bt ) => bt.name === name );
return blocks
.map( ( block ) =>
serializeBlock( block, getBlockType( block.name ) )
)
.join( '\n\n' );
}
4 changes: 2 additions & 2 deletions blocks/editable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'element-closest';
* WordPress dependencies
*/
import { createElement, Component, renderToString } from 'element';
import { parse, pasteHandler } from '../api';
import { parse, pasteHandler, getBlockTypes, getUnknownTypeHandler } from '../api';
import { BACKSPACE, DELETE, ENTER } from 'utils/keycodes';

/**
Expand Down Expand Up @@ -145,7 +145,7 @@ export default class Editable extends Component {

// Internal paste, so parse.
if ( childNodes.some( isBlockDelimiter ) ) {
blocks = parse( event.node.innerHTML.replace( /<meta[^>]+>/, '' ) );
blocks = parse( getBlockTypes(), getUnknownTypeHandler(), event.node.innerHTML.replace( /<meta[^>]+>/, '' ) );
// External paste with block level content, so attempt to assign
// blocks.
} else if ( childNodes.some( isBlockPart ) ) {
Expand Down
7 changes: 4 additions & 3 deletions blocks/library/heading/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { isObject } from 'lodash';
import { isObject, find } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -113,8 +113,9 @@ registerBlockType( 'core/heading', {
};
},

edit( { attributes, setAttributes, focus, setFocus, mergeBlocks, insertBlocksAfter } ) {
edit( { attributes, setAttributes, focus, setFocus, mergeBlocks, insertBlocksAfter, editorConfig } ) {
const { align, content, nodeName, placeholder } = attributes;
const getBlockType = ( name ) => find( editorConfig.blockTypes, ( bt ) => bt.name === name );

return [
focus && (
Expand Down Expand Up @@ -170,7 +171,7 @@ registerBlockType( 'core/heading', {
setAttributes( { content: before } );
insertBlocksAfter( [
...blocks,
createBlock( 'core/text', { content: after } ),
createBlock( getBlockType( 'core/text' ), { content: after } ),
] );
} }
style={ { textAlign: align } }
Expand Down
10 changes: 8 additions & 2 deletions blocks/library/text/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import { find } from 'lodash';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -53,9 +58,10 @@ registerBlockType( 'core/text', {
};
},

edit( { attributes, setAttributes, insertBlocksAfter, focus, setFocus, mergeBlocks, onReplace } ) {
edit( { attributes, setAttributes, insertBlocksAfter, focus, setFocus, mergeBlocks, onReplace, editorConfig } ) {
const { align, content, dropCap, placeholder } = attributes;
const toggleDropCap = () => setAttributes( { dropCap: ! dropCap } );
const getBlockType = ( name ) => find( editorConfig.blockTypes, ( bt ) => bt.name === name );
return [
focus && (
<BlockControls key="controls">
Expand Down Expand Up @@ -95,7 +101,7 @@ registerBlockType( 'core/text', {
setAttributes( { content: before } );
insertBlocksAfter( [
...blocks,
createBlock( 'core/text', { content: after } ),
createBlock( getBlockType( 'core/text' ), { content: after } ),
] );
} }
onMerge={ mergeBlocks }
Expand Down
File renamed without changes.
Loading