WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.46k stars 4.18k forks source link

On controlling blocks and the scope of the Block Supports API #32779

Closed getdave closed 3 years ago

getdave commented 3 years ago

TL;DR

Overview

Over in https://github.com/WordPress/gutenberg/issues/30007 we've been discussing how to provide a better way to disable/enable block features in the various editors (eg: Post, Nav, Widget, Site...etc).

In some contexts, various features of certain blocks are either:

Examples might include (but are not limited to):

Whilst there are a number of ways to disable such features via filters, these feel rather imperative in style and are not necessarily easy to understand at a glance. Moreover, disabling features often requires utilising different filters, often in quite abstract ways.

Block Supports to the rescue

We are trying to find a unified solution to disabling / enabling block features. To this end, we have started to explore the possibility of using the existing Block Supports API for this task.

For the purposes of experimentation we've been focusing on the Navigation block, but we see this as being relevant to all blocks.

The main exploratory work has been to move all block features to the block supports definition in block.json and then use the Block Supports API to determine whether to expose a given feature to the interface.

This has, for the most part, worked well and we're keen to continue our experiments in this area.

The primary benefit of the approach is that it unites everything under place (block.json) and makes managing the activation of features much more declarative.

However, we do have a question...

Question: do Block Supports entries have to be universal?

Our main concern is are we using Block Supports in the way it was envisaged/intended?

Up until now, all items added to Block Supports (eg: html, reusable...etc) are all equally applicable to all/any blocks.

However, as our explorations with the Navigation block have shown, when using the Block Supports API to control all features of a given block, there are some items that are not necessarily universally applicable to all blocks.

An example from the Navigation block would be whether or not to show the submenu indicators, but there no doubt be similar scenarios for other blocks.

The question is: should the Block Supports API be reserved for items that are universal to all blocks or can they be arbitrary if and when required on a per-block basis?

Use cases

Our specific use case is the Navigation block and how this marries with the Navigation Editor.

As the block advances, new features are added which may or may not be suitable for the Navigation Editor.

Managing the disabling of these features in the Nav Editor via the existing filter mechanic is proving to be fragile and unduly complex. We believe that unifying under Block Supports would provide a more robust and stable experience for the Nav Editor as we move forward.

We also believe that using the Block Supports API in this manner will be applicable to all blocks and provide some advantages. For example, enabling features via an attribute flag works on an individual block instance basis but if I want to disable a feature for all instances of a given block then it’s much more difficult. Blocks Supports appears to be the ideal API for such a use case, but using it in this manner will require us to agree that it’s ok to introduce Block Supports features that are specific to an individual block.

Other considerations

We have also considered that theme authors may also need the means to disable/enable block features. To this end we already have the Theme JSON API which seems to align well with the use case where a theme author wants to disable a given block feature when using their theme.

As we understand it, so far the theme.json spec we haven’t introduce any setting that’s block-specific. Whilst it is technically possible to achieve the question is whether we want to have block-specific settings exposed via theme.json.

We should also consider how this would work in coordination with the Block Supports API. One suggestion would be to think of Block Supports as defining what the block can do whilst theme.json defines what the theme lets a block do.

An analogy for this might be like kind of a “cascade”, where blocks define a “buffet” of what is possible and themes may choose from that buffet [via theme.json] but not add to it.

Currently, however, there appears to be no direct (formalised) relationship between the two APIs. We did find one example in font-size.js which is checking both Block Supports and Theme JSON for a feature before exposing it on the block. There may be others that we can learn from.

Request for comment

We’d like to hear from everyone (especially the Core team) regarding:

cc'ing @youknowriad @mtias @nosolosw @talldan @draganescu @adamziel

youknowriad commented 3 years ago

A few things that come to my mind when reading this:

Based on this, using block supports for ad-hoc features doesn't seem to make a lot of sense to me. If the intent is to use "settings", these can still be consumed using useSetting directly from the block itself. But the same thing applies to "settings", I don't think settings that apply to only one block make sense as a setting tbh. I'd rather have static ways to retrieve two kind of blocks in that case:

These blocks can share a lot of code in common, it's React so there's a ton of ways to share components and logic.

draganescu commented 3 years ago

But the same thing applies to "settings", I don't think settings that apply to only one block make sense as a setting tbh.

Yes, useSetting is actually the stabilized form of useEditorFeature which IMO was a clearer name, so it's, like supports a global way to share configuration of editor features/settings between blocks.

So my understanding is that the block API does not have a standard way to create "settings that apply to only one block" and which can be used to configure the block based on where it is used. If this were an OOP world, we're looking for constructor configuration.

Having multiple blocks was the original idea, which I still support, so we could try that route again. Nevertheless, the react specific "ton of ways to share components and logic" is reduced by the structure of Gutenberg's codebase (utils packages?). Do we have a recommended way of sharing code between blocks that are specific to different editors?

Like, the LegacyWidget block is in the widgets package. I assume the BasicNavigation block will be in the edit-navigation package, or somewhere else, but not in the block library where the Navigation block is, because we don't want BasicNavigation in the site editor for example. So, is, in this case, component sharing straight forward?

Also, what is wrong with having a canonical way to "set up" blocks? Attributes are data, supports are shared composable behaviors, settings are shared features. How to configure a block when creating it?

youknowriad commented 3 years ago

Also, what is wrong with having a canonical way to "set up" blocks? Attributes are data, supports are shared composable behaviors, settings are shared features. How to configure a block when creating it?

I'm hearing here that you want a way to change the behavior of "block types" depending on screens.

For me, a block type should be consistent and work the same way across screens and I don't think building block editor abstractions and concepts based on the Navigation screen use-case is a good approach because that feels very much as a very special case for me. I think we can get more clarity on the discussion here if we try to come up with use-cases that are different from that one.

draganescu commented 3 years ago

Gutenberg on the front end, Gutenberg used in non CMS apps based on WP ... :) any usecase where the environment might be dictating limitations. Basically the editor might have special cases where certain features of the block are incompatible.

I don't think we'd be altering the block type, we'd be configuring behaviros. The type is consistent and works the same way, but it behaves slightly different. As you can see in the navigation editor we are already doing it but in a very convluted mix of filters, CSS and whatnot.

To make this clear: I understand that the block supports API is not the choice. The question remains: should we add a new API for this problem? The use cases ... we don't have too many of them.

The Editor settings could be used and the block configures itself in the presence of certain signals from the Editor. Do you think this would be a better path?

getdave commented 3 years ago

I (think/hope I) understand the arguments but I'm not currently in favour of a separate "Simple Navigation" block. Whilst it sounds like a great solution I feel it may add extra overhead where it is not warranted (something I know we're keen to avoid as a project).

In my opinion I think a configurable Navigation block is the way to go. Why? Because I believe we're actually going to be forced to develop the "light" version of the core/navigaiton block anyway as Theme authors are going to demand to be able to pair back the features that the block offers. That to be is inevitable - in my previous jobs I'd never have allowed my clients the full power of the Nav block as it stands.

I believe a good way to think about this is through a soda-based metaphor:

  1. Navigation Block - this is full fat Coke.
  2. Navigation Block (with various features disabled) - this is Coke Light (or Diet coke depending where you live!).
  3. Simple Navigation Block (a separate block which is basically the same as #2 with all the features turned off ) - this is Pepsi (a totally different drink/block...Im confusing myself here 😆 ).

As I see it we're going to get #3 for "free" via the requirements driven by #2. As a result we don't need a separate block and nor should we create one.

With the theme.json API it seems inevitable to me (please correct me if I'm totally wrong here) that we're eventually going to have to wire up the Nav block so that we can enable/disable features based on configuration dictated by the Theme author.

For example, who says I want to allow social links in my Nav Block? Gutenberg core team shouldn't decide that for me and nor should have I have to jump through multiple hurdles involving filters to achieve the ability to disable that. I should have a single block-specific setting in Theme JSON which allows me to configure the block much in the same way that I can configure what the site supports for add_theme_support().

This is one example, but there are multiple features I'd like to enable/disable on a per-theme / per-site basis.

Thankfully if we study the code of useSetting (which utilises theme.json) we will see we already support a block specific setting mechanic:

https://github.com/WordPress/gutenberg/blob/a1eac43ce47852fdf0d92187f8d6de79a3dc5e0d/packages/block-editor/src/components/use-setting/index.js#L81-L84

Let's hope we can build upon this to achieve what I'm outlining above.

Proposal

Therefore, I will suggest that, in light of Block Supports being deemed inappropriate for configuring settings (personally I feel the naming is misleading in this regard), we explore utilising the Theme JSON mechanic to determine what features are/aren't enabled on the Navigation block. This provides the following benefits:

  1. A simple means to configure a very complex and critical block for Theme authors - I see this work as inevitable.
  2. A mechanic to allow the Nav Editor (and indeed any other editor - which may be a non-WP editor!) to set the block into a "simple" mode for use in that particular screen.

We essentially get two birds with one projectile.

Keen to here your thoughts 🙇

adamziel commented 3 years ago

@getdave I hear that we want developers to be able to restrict certain block behaviors. Then, I also remember that blocks have attributes, e.g. textColor, backgroundColor, opensInNewTab. If we restrict some features of the block, e.g. make it impossible to change colors (to keep the design in-tact), or make it impossible to change the rel attribute (just a silly example) then some of the attributes become unused. That's even more problematic if they happen to be required.

I think we may either need agree on rigid guidelines as to what may or may not be configurable, or extend the block API so that it works to our advantage here.

I noodled on the idea of turning settings into features defining their own attributes. Then you could enable/disable specific features and Gutenberg would figure the final attributes structure. But the problem here is that if you open that block in a different editor, it may render as "broken" if some attributes are missing – no bueno here.

I don't have any other ideas to offer for now. I will keep noodling on this and I am curious what y'all think.

adamziel commented 3 years ago

Some very loose, tactical food for thought. We tend to set up inspector and toolbar via components such as <BlockControls>. This is out of necessity as we use slots/portals there. We probably don't need any slots in here, but I wonder if we could implement these features using a similar component-based API:

function NavigationBlock() {
   // ...
   return (
       <>
           <FeatureChangeColor />
           <FeatureThis />
           <FeatureThat />
           {/* ... */}
       <>
   );
}

And have Gutenberg know which ones should be enabled based on the context. I have no attributes– or block-supports– related answers though :D

talldan commented 3 years ago

The bit that’s tricky here is that there are some parts of the nav screen that might just be nav screen specific that we want to shim in (which supports the argument of a new block type):

Then there are also parts that might feel like a natural choice for theme authors or site owners to have control over (supports the argument for enabling features via theme.json or another similar system):

And some things they already have control over via theme.json/block supports:

adamziel commented 3 years ago

Good call listing use-cases @talldan! I'd say let's just pick one, make it configurable, and see how that works. Rinse and repeat.

getdave commented 3 years ago

Reading @talldan's summary again it's clear we cannot hope to unify all enhancing/disabling under a single API. That's basically what we expected, but we went through a discovery process in order to rule out the various options.

I now see the Theme JSON APIs as a good route to explore. Some of the items Dan lists above are universally applicable to all blocks and thus would appear to be a good candidate for exploring how the Theme JSON API can be used to control block features.

Other items are very block specific, but I believe this would also be a good use case for the Theme JSON API. Indeed, even some of the universally applicable features such as the allowed Inner Block types would benefit from being able to be controlled at a block level.

For example, as a Theme author I might want to configure the Nav Block to only allow a limited subset of child blocks. I should be able to do this via Theme JSON.

I'd really like to hear from someone experienced with the current state of the Theme JSON API. Perhaps @nosolosw can share his thoughts specifically on:

gziolo commented 3 years ago

I have one important note to share. The navigation editor screen has different assumptions about how the block editor is built in contrast to all existing instances of the block editor:

  1. The post editor when it was created it couldn't use the Post Content block because it didn't exist so it's only concerned about all the inner blocks and everything else for the post is handled through the custom logic. Now that Post Content block is a thing, it exists only in the template editing context. We didn't refactor the edit post screen to use Post Template, Post Content, and Post Title blocks so far.
  2. There are special blocks Template Part and Reusable block. Again, they have a block implementation, but they also can be edited as a post type where you only edit the inner blocks but the wrapping block isn't present but it's rather maintained with the same mechanics as the post.

Seeing the comments on how different the implementation of the Navigation block is when users edit it as part of the template editor and when scoped on the navigation editor screen, maybe it's worth exploring a different approach. How much work it would be to stop using the Navigation block as the explicit wrapper and moving some bits to the block editor implementation present of the separate screen?

oandregal commented 3 years ago

With regards to how themes can provide data to configure the editor, the answer is theme.json (and useSetting to access that data in the corresponding block edit function). Technically, no change is required: for new settings to be available through useSetting, they only need to be added to the allowed list.

Things that can be considered valuable for many blocks should be added directly. In the list shared by Dan there're a few of those.

If something is so specific that only makes sense in the context of a particular block, it can be a good moment to add a mechanism to allow third-party blocks to add their own allowed settings. I've played with a related idea but we considered it too early to have that in 5.8.

adamziel commented 3 years ago

For example, as a Theme author I might want to configure the Nav Block to only allow a limited subset of child blocks. I should be able to do this via Theme JSON.

This is a tough one, a denylist would allow unwanted blocks in sooner or later for sure, but an allowlist would effectively exclude all the third-party blocks from all the plugins out there. Both seem to make sense in certain use cases, e.g.:

I think having both would be a good first step.

Seeing the comments on how different the implementation of the Navigation block is when users edit it as part of the template editor and when scoped on the navigation editor screen, maybe it's worth exploring a different approach. How much work it would be to stop using the Navigation block as the explicit wrapper and moving some bits to the block editor implementation present of the separate screen?

@gziolo we'd still want to use the navigation block in other editors though, right? I'm thinking about FSE in particular.

getdave commented 3 years ago

Quick note to say FSE outreach is exploring Theme JSON API

https://make.wordpress.org/test/2021/06/09/upcoming-fse-outreach-program-schedule-june-july/

gziolo commented 3 years ago

Seeing the comments on how different the implementation of the Navigation block is when users edit it as part of the template editor and when scoped on the navigation editor screen, maybe it's worth exploring a different approach. How much work it would be to stop using the Navigation block as the explicit wrapper and moving some bits to the block editor implementation present of the separate screen?

@gziolo we'd still want to use the navigation block in other editors though, right? I'm thinking about FSE in particular.

Yes, the Navigation block is an integral part of the FSE experience, and as far as I can tell it works well in that context. It's just the navigation screen where it has a special role as the root of the block editor which enforces all the customizations discussed in this issue. It's really hard to tell what's best, but I wanted to make sure you are all aware that post editors approach it differently because of the history. It would be good to at least explore which approach is better in general and try to align that.

getdave commented 3 years ago

Summary

I'll attempt to summarize what we've learnt here from the conversations above:

...still working on summary...

getdave commented 3 years ago

Summary

I'll attempt to summarize what we've learnt here from the conversations above:

Thanks to everyone for their thoughts here. I'm going to close this one out and take the learnings back to the Nav Editor/Block project.