Skip to content

Commit

Permalink
Extensibility: Define anchor behavior as filtered blocks support
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Nov 2, 2017
1 parent 3ea79aa commit 0a38901
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 139 deletions.
5 changes: 0 additions & 5 deletions blocks/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ export function createBlock( name, blockAttributes = {} ) {
return result;
}, {} );

// Keep the anchor if the block supports it
if ( blockType.supportAnchor && blockAttributes.anchor ) {
attributes.anchor = blockAttributes.anchor;
}

// Keep the className if the block supports it
if ( blockType.className !== false && blockAttributes.className ) {
attributes.className = blockAttributes.className;
Expand Down
7 changes: 1 addition & 6 deletions blocks/api/parser.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { parse as hpqParse, attr } from 'hpq';
import { parse as hpqParse } from 'hpq';
import { mapValues, reduce, pickBy } from 'lodash';

/**
Expand Down Expand Up @@ -148,11 +148,6 @@ export function getBlockAttributes( blockType, rawContent, attributes ) {
return result;
}, {} );

// If the block supports anchor, parse the id
if ( blockType.supportAnchor ) {
blockAttributes.anchor = hpqParse( rawContent, attr( '*', 'id' ) );
}

// If the block supports a custom className parse it
if ( blockType.className !== false && attributes && attributes.className ) {
blockAttributes.className = attributes.className;
Expand Down
11 changes: 9 additions & 2 deletions blocks/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { get, isFunction, some } from 'lodash';
*/
import { getCategories } from './categories';

/**
* Internal dependencies
*/
import { applyFilters } from '../hooks';

/**
* Block settings keyed by block name.
*
Expand Down Expand Up @@ -113,13 +118,15 @@ export function registerBlockType( name, settings ) {
if ( ! settings.icon ) {
settings.icon = 'block-default';
}
const block = blocks[ name ] = {
settings = {
name,
attributes: get( window._wpBlocksAttributes, name ),
...settings,
};

return block;
settings = applyFilters( 'registerBlockType', settings, name );

return blocks[ name ] = settings;
}

/**
Expand Down
24 changes: 0 additions & 24 deletions blocks/api/test/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,30 +57,6 @@ describe( 'block factory', () => {
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',
title: 'test block',
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 );
} );

it( 'should keep the className if the block supports it', () => {
registerBlockType( 'core/test-block', {
attributes: {},
Expand Down
20 changes: 0 additions & 20 deletions blocks/api/test/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,26 +157,6 @@ describe( 'block parser', () => {
} );
} );

it( 'should parse the anchor if the block supports it', () => {
const blockType = {
attributes: {
content: {
type: 'string',
source: text( 'div' ),
},
},
supportAnchor: true,
};

const rawContent = '<div id="chicken">Ribs</div>';
const attrs = {};

expect( getBlockAttributes( blockType, rawContent, attrs ) ).toEqual( {
content: 'Ribs',
anchor: 'chicken',
} );
} );

it( 'should parse the className if the block supports it', () => {
const blockType = {
attributes: {},
Expand Down
14 changes: 0 additions & 14 deletions blocks/api/test/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,6 @@ describe( 'block serializer', () => {

expect( saved ).toBe( '<div>Bananas</div>' );
} );

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( '<div id="my-fruit">Bananas</div>' );
} );
} );

describe( 'component save', () => {
Expand Down
77 changes: 77 additions & 0 deletions blocks/hooks/anchor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* External dependencies
*/
import { assign, get } from 'lodash';

/**
* WordPress dependencies
*/
import { cloneElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { source } from '../api';
import InspectorControls from '../inspector-controls';

/**
* Regular expression matching invalid anchor characters for replacement.
*
* @type {RegExp}
*/
const ANCHOR_REGEX = /[\s#]/g;

export default function anchor( settings ) {
if ( ! get( settings.supports, 'anchor' ) ) {
return settings;
}

// Extend attributes with anchor determined by ID on the first node.
assign( settings.attributes, {
anchor: {
type: 'string',
source: source.attr( '*', 'id' ),
},
} );

// Override the default edit UI to include a new block inspector control
// for assigning the anchor ID
const { edit: Edit } = settings;
settings.edit = function( props ) {
return [
<Edit key="edit" { ...props } />,
props.focus && (
<InspectorControls key="inspector">
<InspectorControls.TextControl
label={ __( 'HTML Anchor' ) }
help={ __( 'Anchors lets you link directly to a section on a page.' ) }
value={ props.attributes.anchor || '' }
onChange={ ( nextValue ) => {
nextValue = nextValue.replace( ANCHOR_REGEX, '-' );

props.setAttributes( {
anchor: nextValue,
} );
} } />
</InspectorControls>
),
];
};

// Override the default block serialization to clone the returned element,
// injecting the attribute ID.
const { save } = settings;
settings.save = function( { attributes } ) {
const { anchor: id } = attributes;

let result = save( ...arguments );
if ( 'string' !== typeof result && id ) {
result = cloneElement( result, { id } );
}

return result;
};

return settings;
}
16 changes: 16 additions & 0 deletions blocks/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* WordPress dependencies
*/
import createHooks from '@wordpress/hooks';

/**
* Internal dependencies
*/
import anchor from './anchor';

const { applyFilters, addFilter } = createHooks();

export { applyFilters };
export { addFilter };

addFilter( 'registerBlockType', 'supports-anchor', anchor );
1 change: 1 addition & 0 deletions blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import './library';
// Blocks are inferred from the HTML source of a post through a parsing mechanism
// and then stored as objects in state, from which it is then rendered for editing.
export * from './api';
export * from './hooks';
export { default as AlignmentToolbar } from './alignment-toolbar';
export { default as BlockAlignmentToolbar } from './block-alignment-toolbar';
export { default as BlockControls } from './block-controls';
Expand Down
4 changes: 3 additions & 1 deletion blocks/library/heading/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ registerBlockType( 'core/heading', {

className: false,

supportAnchor: true,
supports: {
anchor: true,
},

attributes: {
content: {
Expand Down
9 changes: 5 additions & 4 deletions docs/block-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,13 @@ Whether a block can only be used once per post.
useOnce: true,
```

#### supportAnchor (optional)
#### supports (optional)

* **Type:** `Bool`
* **Default:** `false`
* **Type:** `Object`

Optional block extended support features. The following options are supported, and should be specified as a boolean `true` or `false` value:

Anchors let you link directly to a specific block on a page. This property adds a field to define an id for the block and a button to copy the direct link.
- `supportAnchor` (default `false`): Anchors let you link directly to a specific block on a page. This property adds a field to define an id for the block and a button to copy the direct link.

```js
// Add the support for an anchor link.
Expand Down
58 changes: 4 additions & 54 deletions editor/sidebar/block-inspector/advanced-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,30 @@ import { connect } from 'react-redux';
import { Component } from '@wordpress/element';
import { getBlockType, InspectorControls } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { ClipboardButton, Tooltip, PanelBody } from '@wordpress/components';
import { PanelBody } from '@wordpress/components';

/**
* Internal Dependencies
*/
import { updateBlockAttributes } from '../../actions';
import { getSelectedBlock, getCurrentPost } from '../../selectors';
import { filterURLForDisplay } from '../../utils/url';

/**
* Internal constants
*/
const ANCHOR_REGEX = /[\s#]/g;

class BlockInspectorAdvancedControls extends Component {
constructor() {
super( ...arguments );
this.state = {
showCopyConfirmation: false,
};
this.onCopy = this.onCopy.bind( this );

this.setClassName = this.setClassName.bind( this );
this.setAnchor = this.setAnchor.bind( this );
}

setClassName( className ) {
const { selectedBlock, setAttributes } = this.props;
setAttributes( selectedBlock.uid, { className } );
}

setAnchor( anchor ) {
const { selectedBlock, setAttributes } = this.props;
setAttributes( selectedBlock.uid, { anchor: anchor.replace( ANCHOR_REGEX, '-' ) } );
}

componentWillUnmout() {
clearTimeout( this.dismissCopyConfirmation );
}

onCopy() {
this.setState( {
showCopyConfirmation: true,
} );

clearTimeout( this.dismissCopyConfirmation );
this.dismissCopyConfirmation = setTimeout( () => {
this.setState( {
showCopyConfirmation: false,
} );
}, 4000 );
}

render() {
const { selectedBlock, post } = this.props;
const { selectedBlock } = this.props;
const blockType = getBlockType( selectedBlock.name );
if ( false === blockType.className && ! blockType.supportAnchor ) {
if ( false === blockType.className ) {
return null;
}

Expand All @@ -76,24 +44,6 @@ class BlockInspectorAdvancedControls extends Component {
value={ selectedBlock.attributes.className || '' }
onChange={ this.setClassName } />
}
{ blockType.supportAnchor &&
<div>
<InspectorControls.TextControl
label={ __( 'HTML Anchor' ) }
help={ __( 'Anchors lets you link directly to a section on a page.' ) }
value={ selectedBlock.attributes.anchor || '' }
onChange={ this.setAnchor } />
{ !! post.link && !! selectedBlock.attributes.anchor &&
<div className="editor-advanced-controls__anchor">
<ClipboardButton className="button" text={ `${ post.link }#${ selectedBlock.attributes.anchor }` } onCopy={ this.onCopy }>
<Tooltip text={ filterURLForDisplay( `${ post.link }#${ selectedBlock.attributes.anchor }` ) }>
<div>{ this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy Link' ) }</div>
</Tooltip>
</ClipboardButton>
</div>
}
</div>
}
</PanelBody>
);
}
Expand Down
Loading

0 comments on commit 0a38901

Please sign in to comment.