-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Try: Extensibility: Define anchor behavior as filtered blocks support #3318
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,4 +19,5 @@ export { | |
getDefaultBlockName, | ||
getBlockType, | ||
getBlockTypes, | ||
hasBlockSupport, | ||
} from './registration'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
* | ||
|
@@ -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; | ||
} | ||
|
||
/** | ||
|
@@ -196,3 +203,23 @@ export function getBlockType( name ) { | |
export function getBlockTypes() { | ||
return Object.values( blocks ); | ||
} | ||
|
||
/** | ||
* Returns true if the block defines support for a feature, or false otherwise | ||
* | ||
* @param {(String|Object)} nameOrType Block name or type object | ||
* @param {String} feature Feature to test | ||
* @param {Boolean} defaultSupports Whether feature is supported by | ||
* default if not explicitly defined | ||
* @return {Boolean} Whether block supports feature | ||
*/ | ||
export function hasBlockSupport( nameOrType, feature, defaultSupports ) { | ||
const blockType = 'string' === typeof nameOrType ? | ||
getBlockType( nameOrType ) : | ||
nameOrType; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: but I tend to prefer functions with an unchanged signature. My preference would be to always use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It also makes documenting easier when you enforce calling export function hasBlockTypeSupport( type, feature, defaultSupports ) {}
export function hasBlockSupport( name, feature, defaultSupports ) {
return hasBlockTypeSupport( getBlockType( name ), feature, defaultSupports );
} The only challenge in such case is to find proper names for methods :) |
||
|
||
return !! get( blockType, [ | ||
'supports', | ||
feature, | ||
], defaultSupports ); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import { Component, createElement, renderToString, cloneElement, Children } from | |
* Internal dependencies | ||
*/ | ||
import { getBlockType, getUnknownTypeHandlerName } from './registration'; | ||
import { applyFilters } from '../hooks'; | ||
|
||
/** | ||
* Returns the block's default classname from its name | ||
|
@@ -55,7 +56,7 @@ export function getSaveContent( blockType, attributes ) { | |
return element; | ||
} | ||
|
||
const extraProps = {}; | ||
const extraProps = applyFilters( 'getSaveContent.extraProps', {}, blockType, attributes ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one seems to be a very specific. Maybe it should be simply called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think |
||
if ( !! className ) { | ||
const updatedClassName = classnames( | ||
className, | ||
|
@@ -65,10 +66,6 @@ export function getSaveContent( blockType, attributes ) { | |
extraProps.className = updatedClassName; | ||
} | ||
|
||
if ( blockType.supportAnchor && attributes.anchor ) { | ||
extraProps.id = attributes.anchor; | ||
} | ||
|
||
return cloneElement( element, extraProps ); | ||
}; | ||
const contentWithClassname = Children.map( saveContent, addAdvancedAttributes ); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getBlockType } from '../api'; | ||
import { applyFilters } from '../hooks'; | ||
|
||
function BlockEdit( props ) { | ||
const { name, ...editProps } = props; | ||
const blockType = getBlockType( name ); | ||
|
||
if ( ! blockType ) { | ||
return null; | ||
} | ||
|
||
// `edit` and `save` are functions or components describing the markup | ||
// with which a block is displayed. If `blockType` is valid, assign | ||
// them preferencially as the render value for the block. | ||
let Edit; | ||
if ( blockType ) { | ||
Edit = blockType.edit || blockType.save; | ||
} | ||
|
||
return applyFilters( 'BlockEdit', <Edit { ...editProps } />, props ); | ||
} | ||
|
||
export default BlockEdit; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { shallow } from 'enzyme'; | ||
import { noop } from 'lodash'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import BlockEdit from '../'; | ||
import { | ||
registerBlockType, | ||
unregisterBlockType, | ||
getBlockTypes, | ||
} from '../../api'; | ||
|
||
describe( 'BlockEdit', () => { | ||
afterEach( () => { | ||
getBlockTypes().forEach( ( block ) => { | ||
unregisterBlockType( block.name ); | ||
} ); | ||
} ); | ||
|
||
it( 'should return null if block type not defined', () => { | ||
const wrapper = shallow( <BlockEdit name="core/test-block" /> ); | ||
|
||
expect( wrapper.type() ).toBe( null ); | ||
} ); | ||
|
||
it( 'should use edit implementation of block', () => { | ||
const edit = () => <div />; | ||
registerBlockType( 'core/test-block', { | ||
save: noop, | ||
category: 'common', | ||
title: 'block title', | ||
edit, | ||
} ); | ||
|
||
const wrapper = shallow( <BlockEdit name="core/test-block" /> ); | ||
|
||
expect( wrapper.type() ).toBe( edit ); | ||
} ); | ||
|
||
it( 'should use save implementation of block as fallback', () => { | ||
const save = () => <div />; | ||
registerBlockType( 'core/test-block', { | ||
save, | ||
category: 'common', | ||
title: 'block title', | ||
} ); | ||
|
||
const wrapper = shallow( <BlockEdit name="core/test-block" /> ); | ||
|
||
expect( wrapper.type() ).toBe( save ); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we revert the arguments to match the
registerBlockType
function signature? I know it's not convenient for the anchor filter because the name is useless.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's awkward, I agree, but the behavior of filtering is such that we need the second argument to be the original value we expect to be filtered; in this case, the settings. All additional arguments are considered "extra", and in this case the name could prove valuable and is not otherwise made available.
See: https://developer.wordpress.org/reference/functions/apply_filters/