-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Auto-inserting Blocks #39439
Comments
There are many vectors to this challenge. From an extender perspective we want to make it trivial to create blocks that can show up in their semantic places in both the editor and the front-end. Easy to use, easy to discover without user intervention, but also easy to modify. I don't like the term "ghost" to refer to them because in the front-end they are as real as any other block. It's only in the editor where the implications are different, because we don't serialize them, and because neither the theme nor the user has decided on it. In the editor, a user should be able to relocate one of these blocks, so the initial placement is both a suggestion and discovery mechanism for a plugin / block but not an imposition. My suggestion is not necessarily to use global settings but to use configuration to drive the behaviour, which is expressed in both It could also be that autoInsert works upon the first encountered block of the specified type, so it doesn't get repeated if there are multiple navigation blocks in a page. Or maybe this is also a flag like Another part of the challenge is that we need to describe a loose contract between templates and blocks (and templates are also blocks, so between blocks and blocks). I say loose because ideally a theme doesn't need to mark these hookable areas with any additional mechanisms because they are implicit in the semantics of the blocks its using. When it comes to the UX, there are different ways we might want to present auto-inserted blocks to draw a distinction, but that is a slightly separate consideration. |
Fleshing out this idea a bit further, I think we could instrument something where every block that has an inner blocks area with From the perspective of the child block: // End and after only work if the specified block has unlocked inner blocks
{
'load': [ 'core/navigation', 'before' | 'start' | 'end' | 'after' ],
} The |
@senadir (and @ralucaStan) - there's some overlap here with what you and your team has done for extensibility in the WooCommerce Cart and Checkout blocks. There could be some opportunity here to contribute towards a solution in Gutenberg that would be beneficial here? |
I had another realization here that I believe simplifies things greatly: the auto-insert behaviour should work with file based templates, not with saved templates. If a user wants to remove the auto-inserted block, they remove it and save; since the template becomes a saved template at that point, the user choice would be honored. If the user wants to restore the block they can insert it or revert the template. The same goes for moving it elsewhere, since we don't need to calculate whether the block is already inserted we just honored what was stored. I think this contemplates most of the possible scenarios pretty elegantly. The one case we need to consider is when a user has a customized header already, and then install a plugin / block with auto-insert behaviours since those won't kick in. I think this is ok and we should separately work on ways to help surface blocks that may be trying to auto-insert themselves on a saved template by exposing it in the UI somehow (i.e. "there are 5 blocks that could be shown here") and to allow the user to quickly restore or interact with them if they want to. |
This is the primary concern I had as well. Once a template is customized and saved, the discovery process for auto-insertion of content any plugins activated after the fact want to do, is a critical piece imo. Especially for templates that may not be visited often by users (i.e. a checkout template for a commerce application). I think we'd need to think through discovery beyond just within the template itself. |
I was thinking about this as well. I haven't had time to settle on these thoughts yet, but I guess they are worth sharing, just in case. Global Styles are a way to overwrite Imagine this // my-org/my-block block.json
{
"autoInsert": {
"core/navigation": {
"placements": ["after"],
"templatePartCategories": ["header"],
"active": true // <-- This is the default, so it doesn't need to be included.
},
"core/buttons": {
"placements": ["after"],
"templatePartCategories": ["header", "footer"],
"...": "There may be more filtering options"
}
}
} When an auto inserted block is present, the UI should show a button to remove it. That user intent would be stored in the closest section's Global Styles (Section Styles?), turning
// Closest section (parent navigation block)
{
"my-org/my-block": {
"autoInsert": {
"core/navigation": {
"active": false // <-- Overwrite block.json.
}
}
}
} In this example, the user removed the auto-inserted block inside one of the Navigation blocks, but not the ones in other Navigation blocks or templates parts. As it is unclear what is the user intent when they click the "remove" button, a prompt could be presented to make them chose between:
If a user wants to revert these changes, it can do so in the corresponding Global Styles or Section Styles UI. This won't be super intuitive, but at least there will be a place to do so. This also works with HTML templates: if a theme creator knows about an // Section Styles of the "SubMenu" Navigation block
{
"woocommerce/mini-cart": {
"autoInsert": {
"core/navigation": {
"active": false
}
}
}
} Or they could add Two other unrelated and very unsettled thoughts:
PS: I'm not sure about the difference between the |
@luisherranz Suppose we have the following block tree:
and we are interested in
And for
The first case requires block-b to have an inner blocks area. The second doesn't. |
Ohh, got it. Thank you, Matías! |
While we work on the general solution, #37998 adds a way to modify inner blocks for the Navigation block with a PHP filter. This is going to be included in WordPress 6.1. |
Since I'm not sure it's fully captured in any of the above comments, I'll add another use case that @mtias brought back to my mind recently: A plugin wants to add a "Like" button below the post content block, but only in the (This means that we'll need some way and syntax to limit auto-inserting blocks to specific templates.) |
@ockham, are there any prior PRs that explored the potential solution for this feature? I know about #37998, which addressed the same issue for the Navigation block explained in detail in #37717. However, the approach taken is based on WP hooks that can't be generalized for every possible block and can't be visualized in the editor. By the way, I plan to work on this feature for some time and build a prototype with some basic functionality to gather more feedback as we learn about the implications of surfacing auto-inserted blocks in the UI. |
None that I'm aware of!
Yeah, that's kind of the "old-school" approach, with the downsides that you mention. Not really the long-term/generic solution that we're trying to conceive of in this issue 😊
Sounds good! LMK if you need any pointers or help 😄 |
I opened #49789 to explore some ideas. At this moment, there isn't much included, but I wanted to share the branch early for visibility. |
I closed #49789 with the following findings documented in #49789 (comment): I recorded a narrated video to share the progress of the exploration. In the first part, I'm talking about the issue and the importance of the comment #39439 (comment) from @mtias where he says:
I also explain the basic idea explored in this branch that led me to apply the current changes to the branch and how I wanted to keep track of the block names that were auto-inserted in the block editor. Auto-inserting.blocks.20-04.part.1.movIn the second part, I presented how I use the modified E2E test plugin to verify how the changes applied in this branch impact the list of blocks loaded in the block editor. Auto-inserting.blocks.20-04.part.2.movThe learning so far is that the approach taken doesn't scale well despite my initial enthusiasm shared in #49789 (comment). As briefly discussed with @youknowriad in #49789 (comment), it might be very difficult to bend the current approach to integrate seamlessly with the undo/redo, but also with other parts of the editing workflow like switching between the code and the visual editors. I'm inclined to try next the idea brought by Riad to change the list of blocks earlier in the flow – during initial block parsing on the client or even run that logic on the server. In particular, auto-inserting blocks on the server is appealing because we need it anyway for site visitors, so maybe we can integrate a similar logic with REST API for post content (that would apply to posts, pages, CPT, reusable blocks, template parts, and templates). The important note is that since we need to auto-insert blocks on the server if the site admin never opens the block editor, it can't work out of the box with static blocks that depend on the |
Still to clarifyThere are still a few open questions that we need to discuss while we work on prototypes for the auto-inserting mechanism. I summarized below everything I discussed recently with @artemiomorales and @ockham during video calls. Format for the auto-inserting mechanism
Possible scenarios to coverHere is the list of possible scenarios for the cases of how people would interact on the site with auto-inserting blocks. The assumption is that someone has just installed a plugin with auto-inserting blocks on the existing website. Is that list a good baseline for further explorations?
Considering the assumption that we only support auto-inserting for dynamic blocks, removing the block from the site in all scenarios means they shouldn’t display anymore, or only a static HTML fallback saved to the database would remain visible for site visitors. Static vs dynamic blocksWe could potentially seek ways to integrate an auto-inserting mechanism with Template Parts, Block Patterns, or Reusable Blocks. There is a concern that we might have 3 different ways to do a similar thing with one API of auto-inserting blocks. We better limit the scope to the blocks for now but keep in mind that it could be expanded to other existing APIs that block themes use to bring more flexibility and overcome the limitation of not having a way to run JavaScript on the server that is required for the New ideas for integration with the editorWe discussed exploring in the next step alternative ways to modify the HTML for the block editor:
The remaining question is whether we should dirty the block editor state for options 2 and 3 when opening an unmodified template, template part, etc.? |
Should there be some sort of visual indicator in the editor to signal which blocks are auto-inserted to the user? |
@nerrad Yeah, we'll definitely need some UX design around this. For now, we're focusing on the engineering side, since we think that we first need to solve some technical problems. We will need to keep some UX aspects in mind already (especially e.g. with regard to whether auto-inserted blocks should dirty the editor state or not, or if we need a whole new "contains auto-inserted blocks" state), but most of the design work should probably start only once we've settled on a few basic principles. |
Update: The experimental PR (#50103) now supports auto-inserting blocks on the frontend based on a I've become increasingly skeptical that auto-inserting a block is sufficient and laid out my arguments here. As a consquence, I've filed an alternative PR to auto-insert block patterns instead: #51294 Finally, I started exploring auto-insertion in the editor, via the REST API: #51449. I'm quite happy with the state of this latter PR, as it now supports insertion in the editor and on the frontend, via the same underlying mechanism. That PR's description has more details on the underlying technical intricacies. |
Auto inserting template parts might be another good way to look at these APIs (pardon if this was already discussed). Use cases I have in mind are injecting a template part containing a "subscribe to this blog" modal or a GDPR cookie banner. Since the modal should appear on all posts, or cookie banner on all pages and posts, it's better to keep them in template parts injected to other templates. Template part is handy for linking and allowing customer edit the modal/cookie banner in focused template rather than as part of the whole page's template. |
Auto-inserting patterns/template parts is interesting. How do you detect that the particular "pattern" has been already edited and is present in the content to avoid inserting it twice? |
Just throwing in my 👍 for this feature. The new WooCommerce product editor uses a template and we've explored various APIs to allow this template to be extended by third party plugins. This feature would eliminate the need for our own custom API. Some of our earlier discussions went with a similar pattern of inserting before or after specific blocks, but we found this to be somewhat problematic for a couple of reasons:
To better demonstrate this, we might have this template:
If we wanted to insert a block (field) from a plugin between c and d, we would need to insert after c or before d using the current API.
If another plugin adds a field after c, I'm speculating that the result would look like this if the code is executed after the first:
In our POC we opted for a |
@ockham I replied on the PR regarding the patterns vs block distinction. I think the examples used are problematic in that they are not common nor natural. Patterns are problematic if they are instance based (like themes do with I'd stay with the block-first API. Including the ability to provide different sets of default attributes is interesting, though I think too preemptive. We should see if there's an actual demand for it from real use cases. |
@joshuatf Thank you for getting in touch, and really appreciate the feedback! First off, it's exactly use cases like yours that we'd like to cover with this -- I'd go as far as to say that if we don't manage to make it fit for your needs, we would have kinda failed our task 😅 I'd thus be more than happy to collab on this to make sure that it will be useful for y'all!
That's correct. Can you give me an example how the kind of insertion y'all need goes beyond first/last child insertion? Is this what you describe in your example (inserting before or after a given block that's also a child block of another given block, i.e. defining the inserting position by means of two "anchor" blocks rather than just one)?
I know that @mtias doesn't like priority args for APIs like this (and Gutenberg has so far avoided them e.g. in client-side filters -- as opposed to WordPress' server-side ones). I guess this approach implies the line of thought that anything that uses a filter -- or, in our case, an insertion position -- needs to be okay with not controlling that other blocks might render before it. At first glance, if I imagine e.g. a customizable form block (like an address?) with a number of different fields, that seems okay to me: If two different plugins compete for a spot, say, before the "Country" field, neither of them will be guaranteed which one will render before the other, so they will have to make provisions for either case 🤔 Can you give me a concrete example where the order matters?
FWIW, the way we're covering this is that blocks are only auto-inserted into unmodified templates (see) -- the moment a template is modified and saved by the user, we stop auto-inserting. This allows us to respect e.g. the user manually removing the auto-inserted block (instead of continuing to auto-insert it), or to avoid auto-inserting it after the user has already saved it in its suggested position, both of which would otherwise be a considerably harder problems. I'd be curious to test the code in #51449 with some practical examples that you're working on or aware of @joshuatf -- they could serve as a benchmark for the viability of the present approach, and to discover what features are missing! I'd love to land #51449 (or a variant of it) as experimental in Gutenberg fairly soon; but I'd also be happy to first file a PR against any repo you're working on to try it out with those blocks 😄 Maybe you can point me to some candidate repos and/or blocks? |
Really appreciate this, @ockham! Happy to share some of our use cases so we can work towards finding something that works (or invalidate some of the assumptions made in Woo around this if they're wrong).
Sure! It's not necessarily that we need two anchor points and in fact it's probably more akin to not having any anchor points at all. I think the below question and example illustrate this.
If we take a look at the WooCommerce Brands plugin as an example, this plugin adds a brand taxonomy selection which should get added to all product types. Let's say that this gets added directly after the pricing fields in the "General" tab. Then another plugin, like Tiered and Dynamic Pricing wants to add a field at the same spot. Ideally, the pricing fields are adjacent and the brands taxonomy comes after. But since we can't guarantee the order, the order may be The brands plugin chose to insert next to that field because of its order and not necessarily relevance. This problem is further compounded if we have some product types or custom product types that remove the pricing fields and we no longer have an anchor point to attach to.
We had also tried to avoid this, but did not find a pattern that would allow us the flexibility needed. @mtias could you expand a bit on your primary reasons for avoiding the use of an
That makes sense, thanks for the explanation! The case in the product editor is interesting because at least with its current and foreseeable future, we do not plan to allow merchants to make any modifications to the templates. However, areas in WooCommerce Blocks checkout would benefit greatly from this as I know there have already been workarounds made to achieve similar behavior.
Will give this a spin this week! |
I think this is the problem. The example showcases why we shouldn't be relying on priorities for ordering. Relevance should be expressed semantically, so that extending "price" always makes sense and doesn't need to account for other extensions itself. When you need to provide utmost flexibility, your should look at the entire template that's laying these blocks out and allow changing that at a higher level. |
Agreed with the sentiment here, @mtias. I think in practice there are many cases where the highest relevance is the parent block, but the extending plugin wants to insert it at some position other than first or last.
This is definitely the case for the product editor as we need more flexibility and I don't think this auto insertion API provides the level of flexibility needed for those types of templates. I also understand that user-edited templates may make the need for an |
Yeah, this specific API is being designed as a low footprint way of bringing a block into an editor context automatically while allowing users to remove or relocate as needed. It's not really about composing a list of several blocks, organized in specific ways, which is a higher level abstraction. |
Apologies for not getting back to you earlier, @simison. The mechanism currently explored in #51449 applies the same technique to templates and template parts: Blocks are auto-inserted into the response from the |
Sorry for the delay in my reply, @youknowriad. Per @mtias' comment, we're only auto-inserting into templates, template parts, and patterns that don't have any user modifications (i.e. that come straight from a theme or plugin-supplied template file). This solves a lot of problems around the complexities we'd otherwise face in detecting user modifications, even though it incurs some drawbacks (that we've considered acceptable though):
|
A little update for the folks following along at home: I've added a little demo video to the PR and opened it for review since I've felt it's in good enough shape for that. For now, this means that there's:
Please give that PR a try and leave your feedback on it! |
@ockham Out of curiousity, will this auto-insertion API ever be usable with the comma delimited template format?
I know the templates REST API is probably expecting the comment delimited format (e.g., |
Update: I've merged #51449, which means that auto-inserting blocks should become available as a Gutenberg Experiment in GB 16.4. As for next steps, I'm thinking of the following:
Curious to hear from @mtias if you'd like to add other items to the above list, and/or prioritize differently 🙂 |
@joshuatf Apologies for the late reply, I missed your comment. TBH that different format hasn't really been on my radar. Reading the reference you linked to, it seems to be used for a given page's (or, more generally, post type object's) |
There appear to be 3 different template formats and unfortunately none of them are very well documented or compared. This particular template type is very useful for not only post type templates, but also for crafting inner block templates.
I thought the same, but I didn't have any luck finding utils to transform between the comma delimited type and the other two. My best guess as to how these are used is that the parsed associated array version (with On a side note, I did attempt to write some utils that handle parsing between these formats that works okay structurally, but misses
Sure! We use template types on the post object for the new product editor that is block-based. Each product type contains its own template defining how the editor should be laid out. WooCommerce Blocks also uses this format quite often to craft inner blocks for many of its checkout blocks. https://github.com/woocommerce/woocommerce-blocks/blob/f8ce88888bd7bc80629c0849cdd5b22b73d843b2/assets/js/blocks/cart/inner-blocks/cart-totals-block/edit.tsx#L20-L25 Also worth noting that WooCommerce Blocks contains its own useForcedLayout method to try and handle inserting newly added blocks when the layout has not been modified. I think the auto-insertion API may be a good candidate for those areas as well instead of spinning up adhoc solutions in each of these repos. |
Update: Development continues in #52969. Here's the current state, which allows some basic block insertion via enabling a toggle: More work is needed to reliably insert and remove a block, and to have the toggle accurately represent if an auto-inserted block is present or not; there's some related discussion starting at this comment. This will very likely require the introduction of a global |
Update: I've now opened #52969 for review. While it isn't a feature-complete implementation of the toggles yet, it implements most of the desired behavior as described by @mtias here. I think it makes sense to have the PR reviewed (and hopefully merged) already, as it's a good starting point; missing features can be implemented iteratively in follow-up PRs. The PR description contains a short demo video I made, and a TODO list for those follow-ups. |
This would allow a plugin to auto insert a block, like a filter hook, but can it also remove a block when the plugin is deactivated, like a filter? |
Yes, that's kind of how it works -- at least if the containing template or template part has no user modifications. |
Closing in favor of #53987. |
Tasks list
What problem does this address?
There are plugins in the WordPress ecosystem that, when activated, auto-append a Login/out link to any Navigation menu present on the site (via the
wp_nav_menu_items
filter):These no longer work if an FSE theme is active, where all the presentational elements are provided by blocks rather than PHP code. And while there even is a Login/out link block in WordPress (see e.g. #29766), it has been argued that adding it manually isn’t on par with the user experience offered by simply activating a plugin. So in the following, we’ll assume that this is the baseline UX that we want to retain.
We cannot simply add the
wp_nav_menu_items
filter to the Navigation block, as it would allow arbitrary modifications of the block HTML – which could potentially cause it to be no longer parseable. It has thus been requested to add a counterpart filter to the Navigation block that allows for more “controlled” modifications.However, another problem remains: Any (PHP) filter that modifies the rendered markup (of a dynamic block) on the frontend doesn’t allow for those modifications to be edited by the user inside the (FSE) block editor. Frontend/editor parity is an important tenet in Gutenberg, so we’ll add that to our constraints. Gutenberg contributors have thus been wary about adding such a filter, suggesting that in a block theme world, the ideal counterpart to a pre-FSE filter might not be another filter, but a different kind of extension mechanism altogether.
In a previous discussion, @mtias had clarified one more constraint: While a plugin should be able to add a block upon activation that would be both visible on the frontend and modifiable in the editor, that block should not be serialized. Instead, there would need to be a mechanism that would allow the user to either save the block to whatever template they’re editing, or dismiss it. I adopted the term “Ghost Block” to refer to this kind of blocks that are there but aren’t 👻
What is your proposed solution?
A mechanism that would vaguely consist of the following pieces:
render_block
hook for this.insert_ghost_block()
PHP function to plugin authors that utilizes the above filters and that they can hook into plugin activation.We'd obviously also need some UI in the editor in order to alert the user to the automatically added block that needs saving or dismissing, but this seems like the lesser problem for now.
Alternatives
wp_template
CPT). This turned out problematic, since it wouldn’t cover the case where a user newly inserts a Navigation block.Open questions
(Most of the discussions mentioned above happened at an IRL meeting with @mtias, @youknowriad, @priethor, @Poliuk, @luisherranz, @michalczaplinski, @mburridge, @c4rl0sbr4v0, @SantosGuillamot, and @DAreRodz.)
The text was updated successfully, but these errors were encountered: