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

Parser: Apply raw transforms to fallback content #2617

Closed
wants to merge 2 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
52 changes: 51 additions & 1 deletion blocks/api/factory.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
import createSelector from 'rememo';
import uuid from 'uuid/v4';
import {
get,
Expand All @@ -9,12 +10,14 @@ import {
findIndex,
isObjectLike,
find,
filter,
} from 'lodash';

/**
* Internal dependencies
*/
import { getBlockType } from './registration';
import { getBlockType, getBlockTypes, getUnknownTypeHandlerName } from './registration';
import { getBlockAttributes } from './parser';

/**
* Returns a block object given its type and attributes.
Expand Down Expand Up @@ -53,6 +56,53 @@ export function createBlock( name, blockAttributes = {} ) {
};
}

const getBlockTypeTransforms = createSelector( ( blockTypes, type ) => {
return blockTypes.reduce( ( result, blockType ) => {
if ( blockType.transforms ) {
result = [
...result,
...filter( blockType.transforms.from, { type } )
.map( ( transform ) => [ blockType, transform ] ),
];
}

return result;
}, [] );
} );

export function createBlocksFromMarkup( html ) {
// Assign markup as body of sandboxed document
const doc = document.implementation.createHTMLDocument( '' );
doc.body.innerHTML = html;

const rawTransforms = getBlockTypeTransforms( getBlockTypes(), 'raw' );

// For each node in the document, check whether a block type with a raw
// transform matches the node
return [ ...doc.body.children ].map( ( node ) => {
for ( let i = 0; i < rawTransforms.length; i++ ) {
const [ blockType, transform ] = rawTransforms[ i ];
if ( ! transform.isMatch( node ) ) {
continue;
}

// Return with matched block, extracting attributes from the node
// as defined by the block attributes schema
return createBlock(
blockType.name,
transform.getAttributes
? transform.getAttributes( node )
: getBlockAttributes( blockType, node.outerHTML )
);
}

// Assuming no match, use fallback block handler
return createBlock( getUnknownTypeHandlerName(), {
content: node.outerHTML,
} );
} );
}

/**
* Switch a block into one or more blocks of the new block type.
*
Expand Down
61 changes: 33 additions & 28 deletions blocks/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { mapValues, reduce, pickBy } from 'lodash';
*/
import { parse as grammarParse } from './post.pegjs';
import { getBlockType, getUnknownTypeHandlerName } from './registration';
import { createBlock } from './factory';
import { createBlocksFromMarkup, createBlock } from './factory';
import { isValidBlock } from './validation';
import { getCommentDelimitedContent } from './serializer';

Expand Down Expand Up @@ -164,9 +164,10 @@ export function getBlockAttributes( blockType, rawContent, attributes ) {
* @param {?Object} attributes Attributes obtained from block delimiters
* @return {?Object} An initialized block object (if possible)
*/
export function createBlockWithFallback( name, rawContent, attributes ) {
export function createBlocksWithFallback( name, rawContent, attributes ) {
// Use type from block content, otherwise find unknown handler.
name = name || getUnknownTypeHandlerName();
const fallbackBlockName = getUnknownTypeHandlerName();
name = name || fallbackBlockName;

// Convert 'core/text' blocks in existing content to the new
// 'core/paragraph'.
Expand All @@ -176,39 +177,46 @@ export function createBlockWithFallback( name, rawContent, attributes ) {

// Try finding type for known block name, else fall back again.
let blockType = getBlockType( name );
const fallbackBlock = getUnknownTypeHandlerName();
if ( ! blockType ) {
// If detected as a block which is not registered, preserve comment
// delimiters in content of unknown type handler.
if ( name ) {
rawContent = getCommentDelimitedContent( name, attributes, rawContent );
}

name = fallbackBlock;
name = fallbackBlockName;
blockType = getBlockType( name );
} else if ( blockType.name === fallbackBlockName ) {
// Here we can assume that fallback block is inferred by lack of block
// demarcations, meaning we should apply legacy editor formatting
rawContent = window.wp.oldEditor.autop( rawContent );
}

// 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 ) ) {
// 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 )
);

// 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 );
if ( ! block.isValid ) {
block.originalContent = rawContent;
}
if ( ! blockType ) {
return [];
}

return block;
if ( blockType.name === fallbackBlockName ) {
return createBlocksFromMarkup( rawContent );
}

// Gradually convert all blocks to this new format, then remove the
// string serialization.
const block = createBlock(
name,
getBlockAttributes( blockType, 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 );
if ( ! block.isValid ) {
block.originalContent = rawContent;
}

return [ block ];
}

/**
Expand All @@ -220,11 +228,8 @@ export function createBlockWithFallback( name, rawContent, attributes ) {
export function parseWithGrammar( content ) {
return grammarParse( content ).reduce( ( memo, blockNode ) => {
const { blockName, rawContent, attrs } = blockNode;
const block = createBlockWithFallback( blockName, rawContent.trim(), attrs );
if ( block ) {
memo.push( block );
}
return memo;
const blocks = createBlocksWithFallback( blockName, rawContent.trim(), attrs );
return memo.concat( blocks );
}, [] );
}

Expand Down
45 changes: 3 additions & 42 deletions blocks/api/paste/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
/**
* External dependencies
*/
import { find, get } from 'lodash';

/**
* Internal dependencies
*/
import { createBlock } from '../factory';
import { getBlockTypes, getUnknownTypeHandlerName } from '../registration';
import { getBlockAttributes, parseWithGrammar } from '../parser';
import { createBlocksFromMarkup } from '../factory';
import { parseWithGrammar } from '../parser';
import normaliseBlocks from './normalise-blocks';
import stripAttributes from './strip-attributes';
import commentRemover from './comment-remover';
Expand Down Expand Up @@ -62,38 +56,5 @@ export default function( { content: HTML, inline } ) {
// Allows us to ask for this information when we get a report.
window.console.log( 'Processed HTML piece:\n\n', HTML );

const doc = document.implementation.createHTMLDocument( '' );

doc.body.innerHTML = HTML;

return Array.from( doc.body.children ).map( ( node ) => {
const block = getBlockTypes().reduce( ( acc, blockType ) => {
if ( acc ) {
return acc;
}

const transformsFrom = get( blockType, 'transforms.from', [] );
const transform = find( transformsFrom, ( { type } ) => type === 'raw' );

if ( ! transform || ! transform.isMatch( node ) ) {
return acc;
}

return createBlock(
blockType.name,
getBlockAttributes(
blockType,
node.outerHTML
)
);
}, null );

if ( block ) {
return block;
}

return createBlock( getUnknownTypeHandlerName(), {
content: node.outerHTML,
} );
} );
return createBlocksFromMarkup( HTML );
}
13 changes: 8 additions & 5 deletions blocks/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
/**
* External dependencies
*/
import { isFunction, some } from 'lodash';
import { isFunction, some, omit } from 'lodash';
import createSelector from 'rememo';

/**
* WordPress dependencies
Expand All @@ -15,7 +16,7 @@ import { getCategories } from './categories';
*
* @type {Object}
*/
const blocks = {};
let blocks = {};

const categories = getCategories();

Expand Down Expand Up @@ -93,7 +94,7 @@ export function registerBlockType( name, settings ) {
return;
}
const block = Object.assign( { name }, settings );
blocks[ name ] = block;
blocks = { ...blocks, [ name ]: block };
return block;
}

Expand All @@ -112,7 +113,7 @@ export function unregisterBlockType( name ) {
return;
}
const oldBlock = blocks[ name ];
delete blocks[ name ];
blocks = omit( blocks, name );
return oldBlock;
}

Expand Down Expand Up @@ -163,11 +164,13 @@ export function getBlockType( name ) {
return blocks[ name ];
}

const _memoizedGetBlockTypes = createSelector( Object.values );

/**
* Returns all registered blocks.
*
* @return {Array} Block settings
*/
export function getBlockTypes() {
return Object.values( blocks );
return _memoizedGetBlockTypes( blocks );
}
27 changes: 27 additions & 0 deletions blocks/library/gallery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ const linkOptions = [
{ value: 'none', label: __( 'None' ) },
];

/**
* Regular expression matching a gallery shortcode.
*
* @type {RegExp}
*/
const SHORTCODE_REGEXP = window.wp.shortcode.regexp( 'gallery' );

function defaultColumnsNumber( attributes ) {
return Math.min( 3, attributes.images.length );
}
Expand Down Expand Up @@ -67,6 +74,26 @@ registerBlockType( 'core/gallery', {
},
},

transforms: {
from: [
{
type: 'raw',
isMatch: ( node ) => SHORTCODE_REGEXP.test( node.textContent ),
getAttributes( node ) {
const { shortcode } = window.wp.shortcode.next( 'gallery', node.textContent );
let { ids } = shortcode.attrs.named;
if ( ! ids || ! ( ids = ids.trim() ) ) {
return {};
}

return {
images: ids.split( ',' ).map( ( id ) => ( { id } ) ),
};
},
},
],
},

getEditWrapperProps( attributes ) {
const { align } = attributes;
if ( 'left' === align || 'right' === align || 'wide' === align || 'full' === align ) {
Expand Down
20 changes: 10 additions & 10 deletions blocks/library/paragraph/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ registerBlockType( 'core/paragraph', {
},

transforms: {
from: [
{
type: 'raw',
isMatch: ( node ) => (
node.nodeName === 'P' &&
// Do not allow embedded content.
! node.querySelector( 'audio, canvas, embed, iframe, img, math, object, svg, video' )
),
},
],
// from: [
// {
// type: 'raw',
// isMatch: ( node ) => (
// node.nodeName === 'P' &&
// // Do not allow embedded content.
// ! node.querySelector( 'audio, canvas, embed, iframe, img, math, object, svg, video' )
// ),
// },
// ],
},

merge( attributes, attributesToMerge ) {
Expand Down
14 changes: 13 additions & 1 deletion lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,19 @@ function gutenberg_register_scripts_and_styles() {
wp_register_script(
'wp-blocks',
gutenberg_url( 'blocks/build/index.js' ),
array( 'wp-element', 'wp-components', 'wp-utils', 'wp-i18n', 'tinymce-latest', 'tinymce-latest-lists', 'tinymce-latest-paste', 'tinymce-latest-table', 'media-views', 'media-models' ),
array(
'wp-element',
'wp-components',
'wp-utils',
'wp-i18n',
'tinymce-latest',
'tinymce-latest-lists',
'tinymce-latest-paste',
'tinymce-latest-table',
'media-views',
'media-models',
'shortcode',
),
filemtime( gutenberg_dir_path() . 'blocks/build/index.js' )
);
wp_add_inline_script(
Expand Down