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

Block API: Add parse options to specify custom source handling #16316

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
1 change: 1 addition & 0 deletions packages/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ _Parameters_
- _blockTypeOrName_ `(string|Object)`: Block type or name.
- _innerHTML_ `string`: Raw block content.
- _attributes_ `?Object`: Known block attributes (from delimiters).
- _parseOptions_ `WPBlocksParseOptions`: Parser options.

_Returns_

Expand Down
78 changes: 56 additions & 22 deletions packages/blocks/src/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { parse as hpqParse } from 'hpq';
import { flow, castArray, mapValues, omit, stubFalse } from 'lodash';
import { flow, castArray, mapValues, omit, stubFalse, has } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -25,6 +25,26 @@ import { getCommentDelimitedContent } from './serializer';
import { attr, html, text, query, node, children, prop } from './matchers';
import { normalizeBlockType } from './utils';

/**
* Options for parse.
*
* @property {Object<string,Function>} sources An object defining custom source
Copy link
Member

Choose a reason for hiding this comment

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

Hi @aduth,

the value a function receiving an attribute schema and expected to return the desired value.

Would it make sense for the function to also receive the innerHTML plus the commentAttributes?

Imagining the following scenarios:
- an attribute that is derived from the text in a given HTML element.
- A product block that contains the id of the product as a comment, and we want to query some attributes of that product using an external REST API.

* types for a parse, where the key
* corresponds to the source name,
* the value a function receiving
* an attribute schema and expected
* to return the desired value.
*
* @typedef {Object} WPBlocksParseOptions
*/

/**
* Default options for parse.
*
* @type {WPBlocksParseOptions}
*/
const DEFAULT_PARSE_OPTIONS = {};

/**
* Sources which are guaranteed to return a string value.
*
Expand Down Expand Up @@ -225,14 +245,15 @@ export function parseWithAttributeSchema( innerHTML, attributeSchema ) {
* commentAttributes returns the attribute value depending on its source
* definition of the given attribute key.
*
* @param {string} attributeKey Attribute key.
* @param {Object} attributeSchema Attribute's schema.
* @param {string} innerHTML Block's raw content.
* @param {Object} commentAttributes Block's comment attributes.
* @param {string} attributeKey Attribute key.
* @param {Object} attributeSchema Attribute's schema.
* @param {string} innerHTML Block's raw content.
* @param {Object} commentAttributes Block's comment attributes.
* @param {WPBlocksParseOptions} parseOptions Parser options.
*
* @return {*} Attribute value.
*/
export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, commentAttributes ) {
export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, commentAttributes, parseOptions ) {
const { type, enum: enumSet } = attributeSchema;
let value;

Expand All @@ -241,6 +262,7 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com
case undefined:
value = commentAttributes ? commentAttributes[ attributeKey ] : undefined;
break;

case 'attribute':
case 'property':
case 'html':
Expand All @@ -251,6 +273,11 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com
case 'tag':
value = parseWithAttributeSchema( innerHTML, attributeSchema );
break;

default:
if ( has( parseOptions.sources, attributeSchema.source ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to allow an async retrieval of the value? Imagining cases where external requests are required to retrieve the value. I guess it would increase the overall complexity of the module.

value = parseOptions.sources[ attributeSchema.source ]( attributeSchema );
}
}

if ( ! isValidByType( value, type ) || ! isValidByEnum( value, enumSet ) ) {
Expand All @@ -269,16 +296,18 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com
/**
* Returns the block attributes of a registered block node given its type.
*
* @param {string|Object} blockTypeOrName Block type or name.
* @param {string} innerHTML Raw block content.
* @param {?Object} attributes Known block attributes (from delimiters).
* @param {string|Object} blockTypeOrName Block type or name.
* @param {string} innerHTML Raw block content.
* @param {?Object} attributes Known block attributes (from
* delimiters).
* @param {WPBlocksParseOptions} parseOptions Parser options.
*
* @return {Object} All block attributes.
*/
export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {} ) {
export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {}, parseOptions ) {
const blockType = normalizeBlockType( blockTypeOrName );
const blockAttributes = mapValues( blockType.attributes, ( attributeSchema, attributeKey ) => {
return getBlockAttribute( attributeKey, attributeSchema, innerHTML, attributes );
return getBlockAttribute( attributeKey, attributeSchema, innerHTML, attributes, parseOptions );
} );

return applyFilters(
Expand Down Expand Up @@ -372,11 +401,12 @@ export function getMigratedBlock( block, parsedAttributes ) {
/**
* Creates a block with fallback to the unknown type handler.
*
* @param {Object} blockNode Parsed block node.
* @param {Object} blockNode Parsed block node.
* @param {WPBlocksParseOptions} parseOptions Parser options.
*
* @return {?Object} An initialized block object (if possible).
*/
export function createBlockWithFallback( blockNode ) {
export function createBlockWithFallback( blockNode, parseOptions ) {
const { blockName: originalName } = blockNode;
let {
attrs: attributes,
Expand Down Expand Up @@ -445,7 +475,7 @@ export function createBlockWithFallback( blockNode ) {

let block = createBlock(
name,
getBlockAttributes( blockType, innerHTML, attributes ),
getBlockAttributes( blockType, innerHTML, attributes, parseOptions ),
innerBlocks
);

Expand Down Expand Up @@ -473,14 +503,18 @@ export function createBlockWithFallback( blockNode ) {
*
* @return {Function} An implementation which parses the post content.
*/
const createParse = ( parseImplementation ) =>
( content ) => parseImplementation( content ).reduce( ( memo, blockNode ) => {
const block = createBlockWithFallback( blockNode );
if ( block ) {
memo.push( block );
}
return memo;
}, [] );
function createParse( parseImplementation ) {
return ( content, options = DEFAULT_PARSE_OPTIONS ) => {
return parseImplementation( content ).reduce( ( memo, blockNode ) => {
const block = createBlockWithFallback( blockNode, options );
if ( block ) {
memo.push( block );
}

return memo;
}, [] );
};
}

/**
* Parses the post content with a PegJS grammar and returns a list of blocks.
Expand Down
6 changes: 5 additions & 1 deletion packages/editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ export function* setupEditor( post, edits, template ) {
content = post.content.raw;
}

let blocks = parse( content );
let blocks = parse( content, {
Copy link
Member

Choose a reason for hiding this comment

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

Is there any plan to allow, plugin developers to create custom sources and use them in their blocks?

sources: {
meta: ( schema ) => post.meta[ schema.meta ],
},
} );

// Apply a template for new posts only, if exists.
const isNewPost = post.status === 'auto-draft';
Expand Down