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

Establishing a common, 'official' method to allow blocks to be extended by themes in specific contexts #18892

Closed
andrewstaffell opened this issue Dec 3, 2019 · 6 comments
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Extensibility The ability to extend blocks or the editing experience [Type] Enhancement A suggestion for improvement.

Comments

@andrewstaffell
Copy link

andrewstaffell commented Dec 3, 2019

There have already been some discussions about block extensibility (eg. #6023, #15450, #7931); the current issue is intended to be considered alongside those.

Blocks need to be agnostic to the theme they're used in; yet in some cases style or presentation options are only known to (or named by) the theme, and it is sometimes desirable to offer these as choices / controls within a block, possibly at a granular level affecting only specific elements.

Examples: a choice of (theme) font for a particular element within a block; a choice of (theme) colour for specific elements; a choice of photo filter for individual photos in a gallery.

The editor.blockEdit filter already allows us to pass additional parameters into a block's edit() function, so the underlying mechanism for this block-theme communication exists, but there doesn't seem to be a recommended / official method for it, so we've implemented a simple but versatile interface which we're proposing here for discussion.

We're already using this technique effectively in our own blocks / themes, but if consensus were reached on an official method, block developers could start using it consistently to expose extensible contexts to themes, and would only need to document the name and formats of their extensions (instead of citing long-winded hooks to be copy-pasted); while theme developers would have a consistent, tidy and stable API to register all their block extensions in one place.

Our proposed method consists of just a few additional lines of JS (currently this is an npm package which we import into theme JS enqueued for the block editor) which provide:

  • a registerBlockExtensions() function for themes to register their extensions for specific blocks
  • the filter which takes care of passing the extended data into each respective block, which the blocks can then use to present a relevant UI

This file (fabrica-block-extensions/index.js) looks like this:

const extensions = {};

const extendBlockEdit = (BlockEdit) => (
	(props) => wp.element.createElement(BlockEdit, {...props, extensions: extensions[props.name]})
);
wp.hooks.addFilter('editor.BlockEdit', 'fabrica/extend-blocks', extendBlockEdit);

export default function registerBlockExtensions(contexts, data) {
	if (typeof contexts === 'string') { contexts = [contexts]; }
	if (typeof contexts !== 'object') { return false; }
	contexts.forEach(context => extensions[context] = data);
}

Example usage:

Register extensions in the theme:

import registerBlockExtensions from 'fabrica-block-extensions';

registerBlockExtensions('fabrica/extensible-block', {
	captionFonts: {
		fontFranklinGothic: 'Franklin Gothic',
		fontAveria: 'Averia',
		fontCaslon: 'Caslon',
		fontIawriter: 'iAwriter',
	}
});

Receive and act upon extensions in block:
This example shows an additional radio control allowing font selection for a specific element within the block, but it could be a dropdown, toolbar or any other UI device – it's completely up to the block. The key is that the extensions property sent to the edit function contains all the block's registered extensions.

registerBlockType('fabrica/extensible-block', {
(...)
	edit: ({attributes, className, setAttributes, extensions}) => {
	(...)
		{ extensions && typeof(extensions.captionFonts) === 'object' && <RadioControl
			selected={attributes.captionFont}
			options={[{label: 'Default', value: ''}, ...Object.keys(extensions.captionFonts).map((key) => ({label: extensions.captionFonts[key], value: key}))]}
			onChange={(value) => {setAttributes({captionFont: value})}}	
		/> }
	}

Of course it will fall to block developers to accurately and transparently document the contexts in which they are receptive to extensions, as well as the data format they expect in each case; similarly it falls to them to escape safely if data passed in is malformed (hence the conditional checks wrapping the <RadioControl> in the last example above). They should also escape gracefully if extension data is missing (which simply means a theme has opted not to pass in any extensions).

Anyone interested in testing this can install the npm package fabrica-block-extensions.

Looking forward to reading thoughts and feedback.

@andrewstaffell
Copy link
Author

andrewstaffell commented Dec 5, 2019

We've now enhanced this to allow block extensions (optionally) to be conditional, for example on post type, taxonomy or metadata. To achieve this, instead of passing in an object when registering an extension, we can alternatively pass a function which will receive the wp object and should return the relevant extensions object depending on the conditions.

This example disables colour and alignment features based on the current post type:

registerBlockExtensions('fabrica/extensible-block', (wp) =>
	wp.data.select('core/editor').getCurrentPostType() == 'post' ? {
		disableColors: true,
		disableAlignment: true,
	} : {}
);

The main file fabrica-block-extensions/index.js which handles this is only slightly modified from the version above:

const extensions = {};

const extendBlockEdit = BlockEdit => props => wp.element.createElement(BlockEdit, !extensions[props.name] ? props : {...props, extensions: extensions[props.name](wp)});
wp.hooks.addFilter('editor.BlockEdit', 'fabrica/extend-blocks', extendBlockEdit);

export default function registerBlockExtensions(contexts, extensionsData) {
	if (typeof contexts === 'string') { contexts = [contexts]; }
	if (typeof contexts !== 'object') { return false; }
	const getExtensions = typeof extensionsData === 'function' ? extensionsData : () => extensionsData;
	contexts.forEach(context => extensions[context] = getExtensions);
}

The npm package is updated.

@talldan
Copy link
Contributor

talldan commented Jan 24, 2020

@andrewstaffell Not sure if you saw this post, but it seems very relevant to your proposal:
https://make.wordpress.org/core/2020/01/23/controlling-the-block-editor/

#15450 seems like the other most relevant issue.

@talldan talldan added the [Type] Enhancement A suggestion for improvement. label Jan 24, 2020
@talldan
Copy link
Contributor

talldan commented Jan 24, 2020

There have already been some discussions about block extensibility (eg. #6023, #15450, #7931); the current issue is intended to be considered alongside those.

BTW, this isn't often very effective. Often what happens is one issue gains a lot of attention, and other issues that are similar end up ignored (github's UI doesn't make it easy to combine issues on a similar topic into a sort of 'epic'). I'd recommend commenting on one of the existing issues if you can. That would also ensure those who're already involved in the conversation get to see your suggestion.

Usually this issue would be closed as a duplicate, but I respect that amount of effort you went into with the description 😄

@andrewstaffell
Copy link
Author

@talldan Thanks for the comments Dan, I've made a comment on the post now.

@gziolo gziolo added [Feature] Block API API that allows to express the block paradigm. [Feature] Extensibility The ability to extend blocks or the editing experience labels Mar 2, 2020
@gziolo
Copy link
Member

gziolo commented Mar 3, 2020

@andrewstaffell, re-sharing comment from @youknowriad added to your comment under https://make.wordpress.org/core/2020/01/23/controlling-the-block-editor/:

It seems both proposals are very close with the addition to register custom “extensions” (font sizes, colors…) in yours.

One of our goals is to keep some consistency between blocks (core and third-party) and allowing registering extensions/features can go wild very quickly. I believe it’s better to build on something that already exists and start with a common foundation of what can be customized/restricted across the whole editor and all blocks at once.

We also already know how a proliferation of filters can become quickly a nightmare both in terms of UI and in terms of maintenance for Core. So I think starting from a well defined customization options and a generic behavior is a better approach to build upon.

In addition to that, I wanted to highlight that there seem to be 2 different cases for making block easier to customize. The one proposed in the post linked by @talldan addresses visual aspects and it should cover the use cases included in this issue as soon as it is granular enough to make it possible to let apply customizations on the block level. It's definitely on the table, and I plan to investigate it in the upcoming weeks. There is also an aspect to make the existing controls easier to customize when they are targeting the content creation (less visual), e.g. the number of columns, heading levels, etc. It's tracked in #15450.

Thank you for sharing your use case, it definitely should help to drive the development efforts for the block editor features API that initially was meant to work only on the post type level.

Do you think we can close this issue in favor of the two linked existing issues? Do I miss anything?

@youknowriad
Copy link
Contributor

Let's close and consolidate on a single issue. Once the API in place we can continue discussing specific keys in dedicated issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Extensibility The ability to extend blocks or the editing experience [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

No branches or pull requests

4 participants