There are many plugins available that create a Container ( Section or Row ) block that allow Inner Blocks to be added. Examples: Atomic Blocks, CoBlocks, Ultimate Addons for Gutenberg etc. Deactivating any of the plugins tested results in the following warning within the Editor.
If the user chooses to Keep as HTML or switches to the Code Editor then all of the Inner Block content is lost. There is no ability to extract the Inner Blocks as a Common Block or as HTML.
Replicate this issue
NOTE: I have chosen the Atomic Block plugin for this example, but as mentioned it effects all of these types of plugin.
WordPress 5.0.3
Gutenberg 4.8
Atomic Blocks 1.4.23
This ultimately leads to 'Block Lock'. The only way the user can retain the content added to a Container Block is to extract them before the plugin is deleted. This could lead to loss of user content within the Editor.
Proposed solution: Option to extract all blocks as separate HTML or Common Blocks
I've encountered this before as well.
It seems intuitive to me that if we can parse the HTML for the inner blocks they should be able to be retrieved automatically, so perhaps there could be an option to just "Remove containing block".
Why not make all block types that accept InnerBlocks available as a transform option?
This would be a pretty cool feature to have since we could simply choose to transform a missing container to another container type.
It would also allow for quick experimentation between different container blocks.
For example, a user could quickly see what their InnerBlocks layout looks like inside of "Columns" vs inside of a "Slider", etc. without these blocks having to explicitly allow or define a transformation.
Why not (upon identification of a missing block type that accepts InnerBlocks), transform the block into a static container that replicates and "freezes" the original wrapping layout and drops the InnerBlocks into the appropriate location.
If we could make this happen, then users would impacted to the minimum degree should a heavily used block be removed site-wide.
They could still edit the inner contents, but they would lose the original block settings controls since it's being emulated by a generic layout-emulating container.
And, most importantly, the user wouldn't get a big ugly warning where their only option forces them to lose data.
This is definitely a pretty major issue. If a user removes any third-party block which contains inner blocks, all of their content is lost.
Following up on the ping from @diggeddy in #13391
@talldan would you mind casting your eye over this issue - it would be good to get your input with the need for a Core Container block to provide a 'fall back' state for 3rd party container blocks.
I had a quick look into how missing blocks are handled. At the moment:
The post content is parsed. During this phase, inner block markup is extracted from the outer block's markup and mapped into a block data structure. The parser's responsibility here is to map the post's markup into a block data structure, but it only gives each block enough knowledge to do a shallow render:
https://github.com/WordPress/gutenberg/blob/8ba4a209b4b1a1bf6e3037d3dea86392d03c2c44/packages/blocks/src/api/parser.js#L483-L490
Gutenberg tries to recreate the block, and for missing blocks uses a core/missing
block that's registered as a fallback:
https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/index.js#L113
The 'missing' block displays the RawHTML
, but this is the version with the inner block markup extracted by the parser (at step 1). The 'missing' block has access to the innerBlock data, but at this point there's no way of knowing how to restructure the inner blocks into something meaningful (where would it insert those inner blocks?):
https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/missing/index.js#L88
It seems as though this would need to be fixed at the parsing stage (step 1), since that's where knowledge of the inner block markup is taken out of the block creation process.
@rchipka A couple of interesting ideas. I had some thoughts about what the challenges might be with an implementation of those ideas:
Why not make all block types that accept InnerBlocks available as a transform option?
The difficulty here is that a block with inner blocks can contain other blocks with inner blocks that could also be missing. This transform would have to be recursive, I'm not sure how that would be fashioned in a user interface given the user might have limited knowledge about the original markup of the missing block. Also worth noting that with each transform, data can be lost since rarely is a transform a perfect match. The more nesting === the more data lost.
Why not (upon identification of a missing block type that accepts InnerBlocks), transform the block into a static container that replicates and "freezes" the original wrapping layout and drops the InnerBlocks into the appropriate location.
A couple of things that are challenging about this. First, as mentioned in my comment above, currently there's no knowledge of where to drop the inner blocks. Second, each block has it own HTML that can wrap inner blocks. For example, in the columns block the columns are wrapped in divs. This markup would have to be kept as static html, but somehow still accept the dynamic inner blocks as children.
One other idea I had is that we could attempt a raw transform of the missing block—this could work in some cases, maybe mapping images that are inner blocks into a gallery block. It'd likely still not be a perfect transform.
@talldan thanks for looking into this. Reading between the lines it seems like in its current state GB has a major issue with managing out this problem. Especially trying to cover every eventuality.
If a basic core container block existed, and was registered as a transform state in 3rd party container blocks, would this provide a manual fallback for converting blocks before the plugin was removed ?
So revisiting the issue, and the only solution i have found is the Classic Editor. From my ( not conclusive ) tests the markup remains intact if you Edit a page using the classic editor. This is whether the Container Block plugin is active or not. Would the simplest solution be to allow a block to be transformed to a Classic Editor Block if parent is missing?
This looks like a parsing issue, I wonder if @mcsf @dmsnell have thoughts about how we could keep the inner blocks HTML in the originalUndelimitedContent
attribute of the missing block.
Thanks for bringing this up. There's room to improve this, yes. As a basic concept, this works:
-const originalUndelimitedContent = innerHTML;
+const originalUndelimitedContent = reserializeBlock( { innerBlocks, innerContent } );
function reserializeBlock( blockNode ) {
const { innerBlocks, innerContent } = blockNode;
let i = 0;
return innerContent.map( ( item ) => item === null ?
reserializeBlock( innerBlocks[ i++ ] ) :
item
).join( '' );
}
Thanks all for raising this issue. I'd like to refer to a deeper (open) problem we all know needs addressing which is: _what do we do when we don't understand a block in the document_.
I don't think that this is a special case here but rather one of many cases where we don't have a strong fallback mechanism.
To this point I'd like to note that _not all is lost_:
This is definitely a pretty major issue. If a user removes any third-party block which contains inner blocks, all of their content is lost.
The content remains and this is why we want to provide a meaningful static fallback render and avoid things like this…
<!-- wp:latest-posts /-->
…because truly when the plugin disappears the content is gone. If, however, we _do_ provide that render as static HTML we at least have a graceful fallback if not the fully-intended HTML structure of the block. We may not be able to edit it, but as noted, the preview still works.
With shortcodes we have a very similar problem when the plugin is removed, but blocks allow us to provide a sane fallback whereas shortcodes render what should remain an internal detail to the site's admin. On that notion I think the Gutenberg behavior is a step above what has been in WordPress traditionally.
How could we improve this further?
we can parse the HTML for the inner blocks they should be able to be retrieved automatically, so perhaps there could be an option to just "Remove containing block".
I kind of like the idea of having "Extract inner blocks" as an option for blocks with the parent, even if they are known and validated. That is, imagine we have a column block with five columns. If we extract the inner blocks we get a linear list of all the blocks at the top-level.
If we do this it won't resolve the problem that some of the inner blocks will be unknown - that's fine for the author to figure out. I really want to frame this issue in the context of using functionality whose required plugin is missing - this is already a kind of failure condition when we're starting, so anything we do will help even if not completely.
Though the produced markup may have additional surrounding <div>
's or other elements, the innerBlocks
is nothing more than an array that we can remove, or an interspersed array. An extract function could swap all of that out.
Concerning _Transformable Containers_ I don't have strong thoughts at the moment other than it appears to be solving a slightly different problem than this issue seems to be directly addressing. That is, we can build transformable containers and still have the problem noted in the original description.
@youknowriad I'm a bit confused by your comment. can you try and elaborate what you mean by it?
@mcsf how is your solution different than converting an unknown block to raw HTML?
As I understand it we're not capable of transforming the block into an HTML block because when parsing the blocks, we lose the "full HTML" content of the missing block (the HTML of the inner blocks is removed).
So yeah, I was wondering how we could enhance the block parsing to provide the full HTML instead of the "remaining HTML after innerBlocks removal" in the originalUndelimitedContent
attribute of the missing block.
Back when we originally discussed nesting support in the parser, we called this _the Vatican principle_. 😄 Let me explain:
Rome is a city, the capital of Italy. In Rome is enclaved the Vatican, an independent city-state. Though the reality is a lot more nuanced than the principle, the principle was that, when one is in the Vatican, one isn't in Italy. Therefore, the Italian state does not "see" what goes on in the Vatican.
The analogy, then, is that the parser is responsible for building a tree in which each block node is aware of all of its contents only up until the point where its "enclaved" (nested) blocks start.
To reply to:
how is your solution different than converting an unknown block to raw HTML?
Converting an unknown block to raw HTML will yield a block of HTML that excludes the nested content. So if you have a Columns block with two columns of content, and suddenly you no longer had the Columns block type registered, converting to HTML would yield:
<div class="wp-block-columns has-2-columns">
</div>
If we want to preserve the fundamental model for our parser (which includes the Vatican principle, and the fact that we operate in two stages: raw parsing to delineate blocks, and a second stage to ascribe meaning to blocks), then I believe the place to tackle this is around createBlockWithFallback
, where we already have exceptions for a number of things, including if ( ! blockType )
.
That means that we're already working with the output of the first stage of parsing, and so we have to work with block nodes as input in order to produce HTML trees for e.g. originalUndelimitedContent
. Hence that very quick prototype in https://github.com/WordPress/gutenberg/issues/13391#issuecomment-470531441.
Fix in #14443
Most helpful comment
I've encountered this before as well.
Replace container with InnerBlocks
It seems intuitive to me that if we can parse the HTML for the inner blocks they should be able to be retrieved automatically, so perhaps there could be an option to just "Remove containing block".
Transformable containers
Why not make all block types that accept InnerBlocks available as a transform option?
This would be a pretty cool feature to have since we could simply choose to transform a missing container to another container type.
It would also allow for quick experimentation between different container blocks.
For example, a user could quickly see what their InnerBlocks layout looks like inside of "Columns" vs inside of a "Slider", etc. without these blocks having to explicitly allow or define a transformation.
Static substitute container
Why not (upon identification of a missing block type that accepts InnerBlocks), transform the block into a static container that replicates and "freezes" the original wrapping layout and drops the InnerBlocks into the appropriate location.
If we could make this happen, then users would impacted to the minimum degree should a heavily used block be removed site-wide.
They could still edit the inner contents, but they would lose the original block settings controls since it's being emulated by a generic layout-emulating container.
And, most importantly, the user wouldn't get a big ugly warning where their only option forces them to lose data.