diff --git a/blocks/api/factory.js b/blocks/api/factory.js index 004cfb115c35bf..ed1f03763e894b 100644 --- a/blocks/api/factory.js +++ b/blocks/api/factory.js @@ -19,18 +19,18 @@ import { getBlockType } from './registration'; /** * Returns a block object given its type and attributes. * - * @param {String} name Block name - * @param {Object} attributes Block attributes - * @return {Object} Block object + * @param {String} name Block name + * @param {Object} blockAttributes Block attributes + * @return {Object} Block object */ -export function createBlock( name, attributes = {} ) { +export function createBlock( name, blockAttributes = {} ) { // Get the type definition associated with a registered block. const blockType = getBlockType( name ); // Ensure attributes contains only values defined by block type, and merge // default values for missing attributes. - attributes = reduce( blockType.attributes, ( result, source, key ) => { - const value = attributes[ key ]; + const attributes = reduce( blockType.attributes, ( result, source, key ) => { + const value = blockAttributes[ key ]; if ( undefined !== value ) { result[ key ] = value; } else if ( source.default ) { @@ -39,6 +39,9 @@ export function createBlock( name, attributes = {} ) { return result; }, {} ); + if ( blockType.supportAnchor && blockAttributes.anchor ) { + attributes.anchor = blockAttributes.anchor; + } // Blocks are stored with a unique ID, the assigned type name, // and the block attributes. diff --git a/blocks/api/parser.js b/blocks/api/parser.js index c908414a775974..9c3ff3b513c183 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { parse as hpqParse } from 'hpq'; +import { parse as hpqParse, attr } from 'hpq'; import { mapValues, reduce, pickBy } from 'lodash'; /** @@ -101,7 +101,7 @@ export function getBlockAttributes( blockType, rawContent, attributes ) { blockType.attributes ); - return reduce( blockType.attributes, ( result, source, key ) => { + const blockAttributes = reduce( blockType.attributes, ( result, source, key ) => { let value; if ( sourcedAttributes.hasOwnProperty( key ) ) { value = sourcedAttributes[ key ]; @@ -146,6 +146,13 @@ export function getBlockAttributes( blockType, rawContent, attributes ) { result[ key ] = coercedValue; return result; }, {} ); + + // If the block supports anchor, parse the id + if ( blockType.supportAnchor ) { + blockAttributes.anchor = hpqParse( rawContent, attr( '*', 'id' ) ); + } + + return blockAttributes; } /** diff --git a/blocks/api/serializer.js b/blocks/api/serializer.js index c5aed9e25d3220..feb6ceae9c6d4b 100644 --- a/blocks/api/serializer.js +++ b/blocks/api/serializer.js @@ -50,20 +50,28 @@ export function getSaveContent( blockType, attributes ) { } // Adding a generic classname - const addClassnameToElement = ( element ) => { - if ( ! element || ! isObject( element ) || ! className ) { + const addAdvancedAttributes = ( element ) => { + if ( ! element || ! isObject( element ) ) { return element; } - const updatedClassName = classnames( - className, - element.props.className, - attributes.className - ); + const extraProps = {}; + if ( !! className ) { + const updatedClassName = classnames( + className, + element.props.className, + attributes.className + ); + extraProps.className = updatedClassName; + } + + if ( blockType.supportAnchor && attributes.anchor ) { + extraProps.id = attributes.anchor; + } - return cloneElement( element, { className: updatedClassName } ); + return cloneElement( element, extraProps ); }; - const contentWithClassname = Children.map( rawContent, addClassnameToElement ); + const contentWithClassname = Children.map( rawContent, addAdvancedAttributes ); // Otherwise, infer as element return renderToString( contentWithClassname ); diff --git a/blocks/api/test/factory.js b/blocks/api/test/factory.js index 8d1ea343252a88..a91450e5988830 100644 --- a/blocks/api/test/factory.js +++ b/blocks/api/test/factory.js @@ -54,6 +54,29 @@ describe( 'block factory', () => { expect( block.isValid ).toBe( true ); expect( typeof block.uid ).toBe( 'string' ); } ); + + it( 'should keep the anchor if the block supports it', () => { + registerBlockType( 'core/test-block', { + attributes: { + align: { + type: 'string', + }, + }, + save: noop, + category: 'common', + supportAnchor: true, + } ); + const block = createBlock( 'core/test-block', { + align: 'left', + anchor: 'chicken', + } ); + + expect( block.attributes ).toEqual( { + anchor: 'chicken', + align: 'left', + } ); + expect( block.isValid ).toBe( true ); + } ); } ); describe( 'switchToBlockType()', () => { diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index 5445f57233a8cb..49fb5a82b18ba2 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -155,6 +155,26 @@ describe( 'block parser', () => { topic: 'none', } ); } ); + + it( 'should parse the anchor if the block supports it', () => { + const blockType = { + attributes: { + content: { + type: 'string', + source: text( 'div' ), + }, + }, + supportAnchor: true, + }; + + const rawContent = '
Ribs
'; + const attrs = {}; + + expect( getBlockAttributes( blockType, rawContent, attrs ) ).toEqual( { + content: 'Ribs', + anchor: 'chicken', + } ); + } ); } ); describe( 'createBlockWithFallback', () => { diff --git a/blocks/api/test/serializer.js b/blocks/api/test/serializer.js index 23c3d4b54faacc..9ddba1e639bb8e 100644 --- a/blocks/api/test/serializer.js +++ b/blocks/api/test/serializer.js @@ -112,6 +112,20 @@ describe( 'block serializer', () => { expect( saved ).toBe( '
Bananas
' ); } ); + + it( 'should add an id if the block supports anchors', () => { + const saved = getSaveContent( + { + save: ( { attributes } ) => createElement( 'div', null, attributes.fruit ), + supportAnchor: true, + name: 'myplugin/fruit', + className: false, + }, + { fruit: 'Bananas', anchor: 'my-fruit' } + ); + + expect( saved ).toBe( '
Bananas
' ); + } ); } ); describe( 'component save', () => { diff --git a/blocks/inspector-controls/base-control/index.js b/blocks/inspector-controls/base-control/index.js index 6d671320fb915b..2e21c38f494546 100644 --- a/blocks/inspector-controls/base-control/index.js +++ b/blocks/inspector-controls/base-control/index.js @@ -8,11 +8,12 @@ import classnames from 'classnames'; */ import './style.scss'; -function BaseControl( { id, label, className, children } ) { +function BaseControl( { id, label, help, className, children } ) { return (
{ label && } { children } + { !! help &&

{ help }

}
); } diff --git a/blocks/inspector-controls/base-control/style.scss b/blocks/inspector-controls/base-control/style.scss index 80d89bc4036516..cf7f4c77014fc2 100644 --- a/blocks/inspector-controls/base-control/style.scss +++ b/blocks/inspector-controls/base-control/style.scss @@ -6,3 +6,7 @@ display: block; margin-bottom: 5px; } + +.blocks-base-control__help { + font-style: italic; +} diff --git a/blocks/inspector-controls/checkbox-control/index.js b/blocks/inspector-controls/checkbox-control/index.js index 55ca47b2cd4b28..b05b44f709ee3d 100644 --- a/blocks/inspector-controls/checkbox-control/index.js +++ b/blocks/inspector-controls/checkbox-control/index.js @@ -9,12 +9,12 @@ import { withInstanceId } from '@wordpress/components'; import BaseControl from './../base-control'; import './style.scss'; -function CheckboxControl( { label, heading, checked, instanceId, onChange, ...props } ) { +function CheckboxControl( { label, heading, checked, help, instanceId, onChange, ...props } ) { const id = 'inspector-checkbox-control-' + instanceId; const onChangeValue = ( event ) => onChange( event.target.value ); return ( - +