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

Parsing: Declare all block attributes #714

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
4 changes: 1 addition & 3 deletions blocks/api/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/**
* External dependencies
*/
import * as query from './query';

export { query };
export { default as query } from './query';
export { createBlock, switchToBlockType } from './factory';
export { default as parse } from './parser';
export { default as serialize } from './serializer';
Expand Down
54 changes: 17 additions & 37 deletions blocks/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 { escape, unescape, pickBy } from 'lodash';
import { escape, unescape, reduce } from 'lodash';

/**
* Internal dependencies
Expand All @@ -11,32 +11,6 @@ import { parse as grammarParse } from './post.pegjs';
import { getBlockSettings, getUnknownTypeHandler } from './registration';
import { createBlock } from './factory';

/**
* Returns the block attributes parsed from raw content.
*
* @param {String} rawContent Raw block content
* @param {Object} blockSettings Block settings
* @return {Object} Block attributes
*/
export function parseBlockAttributes( rawContent, blockSettings ) {
const { attributes } = blockSettings;
if ( 'function' === typeof attributes ) {
return attributes( rawContent );
} else if ( attributes ) {
// Matchers are implemented as functions that receive a DOM node from
// which to select data. Use of the DOM is incidental and we shouldn't
// guarantee a contract that this be provided, else block implementers
// may feel compelled to use the node. Instead, matchers are intended
// as a generic interface to query data from any tree shape. Here we
// pick only matchers which include an internal flag.
const knownMatchers = pickBy( attributes, '_wpBlocksKnownMatcher' );

return hpqParse( rawContent, knownMatchers );
}

return {};
}

/**
* Returns the block attributes of a registered block node given its settings.
*
Expand All @@ -46,17 +20,23 @@ export function parseBlockAttributes( rawContent, blockSettings ) {
* @return {Object} All block attributes
*/
export function getBlockAttributes( blockSettings, rawContent, attributes ) {
// Merge any attributes from comment delimiters with block implementation
attributes = attributes || {};
if ( blockSettings ) {
attributes = {
...attributes,
...blockSettings.defaultAttributes,
...parseBlockAttributes( rawContent, blockSettings ),
};
}
// The blockSettings.attributes contains the definition of each attribute
// depending on its "source", we retrieve its value from the comment attribute
// or by parsing the block content
const computedAttributes = reduce( blockSettings.attributes, ( memo, attribute, key ) => {
if ( attribute.source === 'metadata' ) {
memo[ key ] = attributes[ attribute.name || key ];
} else if ( attribute.source === 'content' ) {
memo[ key ] = hpqParse( rawContent, attribute.parse );
}

return memo;
}, {} );

return attributes;
return {
...blockSettings.defaultAttributes,
...computedAttributes,
};
}

/**
Expand Down
72 changes: 50 additions & 22 deletions blocks/api/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,16 @@
* External dependencies
*/
import { nodeListToReact } from 'dom-react';
import { flow } from 'lodash';
import {
attr as originalAttr,
prop as originalProp,
html as originalHtml,
text as originalText,
query as originalQuery
} from 'hpq';
import { reduce } from 'lodash';

/**
* Given a matcher function creator, returns a new function which applies an
* internal flag to the created matcher.
*
* @param {Function} fn Original matcher function creator
* @return {Function} Modified matcher function creator
*/
function withKnownMatcherFlag( fn ) {
return flow( fn, ( matcher ) => {
matcher._wpBlocksKnownMatcher = true;
return matcher;
} );
}

export const attr = withKnownMatcherFlag( originalAttr );
export const prop = withKnownMatcherFlag( originalProp );
export const html = withKnownMatcherFlag( originalHtml );
export const text = withKnownMatcherFlag( originalText );
export const query = withKnownMatcherFlag( originalQuery );
export const children = withKnownMatcherFlag( ( selector ) => {
export const originalChildren = ( selector ) => {
return ( node ) => {
let match = node;

Expand All @@ -44,4 +25,51 @@ export const children = withKnownMatcherFlag( ( selector ) => {

return [];
};
} );
};

const addDescriptor = ( description ) => ( memo ) => {
return Object.assign( memo, description );
};

// Source descriptors
// Each one of these functions defines how to retrieve the attribute value
//
// - the descriptor sets "source: content" and a parse function for attributes parsed from block content
// - the descriptor sets "source: metadata" and an attribute name for attributes stored in the block comment
const attr = ( ...args ) => addDescriptor( { source: 'content', parse: originalAttr( ...args ) } );
const prop = ( ...args ) => addDescriptor( { source: 'content', parse: originalProp( ...args ) } );
const html = ( ...args ) => addDescriptor( { source: 'content', parse: originalHtml( ...args ) } );
const text = ( ...args ) => addDescriptor( { source: 'content', parse: originalText( ...args ) } );
const children = ( ...args ) => addDescriptor( { source: 'content', parse: originalChildren( ...args ) } );
const metadata = ( name ) => addDescriptor( { source: 'metadata', name } );
const query = ( selector, descriptor ) => {
return addDescriptor( {
source: 'content',
parse: originalQuery( selector, descriptor.__description.parse )
} );
};

/**
* Takes an argument description and returns a chainable API to describe the current attribute
*
* @param {?Object} description The argument description
*
* @return {Object} descriptors chainable API
*/
const getChainableAPI = ( description ) => {
return reduce( { attr, prop, html, text, query, children, metadata }, ( memo, fct, key ) => {
const wrappedFct = ( ...args ) => {
const accumulator = fct( ...args );
const newDescription = accumulator( description || {} );
return {
...getChainableAPI( newDescription ),
__description: newDescription
};
};

memo[ key ] = wrappedFct;
return memo;
}, {} );
};

export default getChainableAPI();
13 changes: 12 additions & 1 deletion blocks/api/registration.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import { reduce } from 'lodash';

/* eslint-disable no-console */

/**
Expand Down Expand Up @@ -43,7 +48,13 @@ export function registerBlock( slug, settings ) {
);
return;
}
const block = Object.assign( { slug }, settings );

const attributes = reduce( settings ? settings.attributes : {}, ( memo, value, key ) => {
memo[ key ] = value.__description;
return memo;
}, {} );

const block = Object.assign( {}, settings, { slug, attributes } );
blocks[ slug ] = block;
return block;
}
Expand Down
33 changes: 12 additions & 21 deletions blocks/api/serializer.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/**
* External dependencies
*/
import { difference } from 'lodash';
import { reduce } from 'lodash';
import { html as beautifyHtml } from 'js-beautify';

/**
* Internal dependencies
*/
import { getBlockSettings } from './registration';
import { parseBlockAttributes } from './parser';

/**
* Given a block's save render implementation and attributes, returns the
Expand Down Expand Up @@ -37,29 +36,21 @@ export function getSaveContent( save, attributes ) {
}

/**
* Returns comment attributes as serialized string, determined by subset of
* difference between actual attributes of a block and those expected based
* on its settings.
* Returns comment attributes as serialized string
*
* @param {Object} realAttributes Actual block attributes
* @param {Object} expectedAttributes Expected block attributes
* @return {string} Comment attributes
* @param {Object} settings Block settings
* @param {Object} attributes Block attributes
* @return {string} Comment attributes
*/
export function getCommentAttributes( realAttributes, expectedAttributes ) {
// Find difference and build into object subset of attributes.
const keys = difference(
Object.keys( realAttributes ),
Object.keys( expectedAttributes )
);

export function getCommentAttributes( settings, attributes ) {
// Serialize the comment attributes
return keys.reduce( ( memo, key ) => {
const value = realAttributes[ key ];
if ( undefined === value ) {
return memo;
return reduce( settings.attributes, ( memo, attribute, key ) => {
const value = attributes[ key ];
if ( attribute.source === 'metadata' && value !== undefined ) {
return memo + `${ attribute.name || key }="${ value }" `;
}

return memo + `${ key }="${ value }" `;
return memo;
}, '' );
}

Expand All @@ -84,8 +75,8 @@ export default function serialize( blocks ) {
blockType +
' ' +
getCommentAttributes(
settings,
block.attributes,
parseBlockAttributes( saveContent, settings )
) +
'-->' +
( saveContent ? '\n' + beautifyHtml( saveContent, beautifyOptions ) + '\n' : '' ) +
Expand Down
Loading