-
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
Render generic fallback block for unknown block type #335
Conversation
Nice work, really good discussion to be having.
It also ties into the fact that every post published by WordPress so far, will contain un-hinted markup that we somehow have to parse as blocks if you go and edit an old post. Whatever we do there should inform what we do here. Given that, it seems like much of what exists today should ideally not be treated as an "unknown block". I don't know what prompts the "unknown" message from appearing in this PR, but it probably shouldn't be basic headings, paragraphs, images, working embeds, even shortcoded galleries. Ideally we'd be able to find a way to parse all those when reading the old post the first time, and upgrade each old block as you interact with them. Way back in the kickoff days, this discussion came up briefly, which prompted the following mockup: The middle block here is "unparseable", which was in the same discussion also referred to as an "I know what I'm doing block". This one definitely deserves special UI. However we're likely to have many many edgecases where a block that should be easy to identify might have custom stuff or even broken or garbled syntax, that renders right by the browser but is still technically borked. This is also related to #214. Would a custom CSS class on an image, say, Was this helpful? |
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.
the block parser should not be aware of this generic block type.
Good point, I think we should rename the html
block that results from the grammar to unknown
block which allows us to keep the output of the grammar consistent even when we have unknown blocks (without comments).
Moved parser logic back into base blocks/index.js . Primarily to enable attribute parsing to wait until block has been determined.
I don't know, I don't feel this is a good move, I think having something like that is good:
const parse = ( postContent ) => {
grammarParse( postContent ).parse()
// Converter could happen here
.map(fallbackUnknownBlocks)
.map(getBlockSettings)
.map(getBlockStateWithAttributes)
}
We could compose those map's of course ;)
In the above, the fallback still happens before the attributes parsing, but I think this logic is good separated in a parser
module instead of the editor
rendering.
blocks/README.md
Outdated
@@ -129,6 +129,7 @@ Registers a new block provided a unique slug and an object defining its behavior | |||
- `save( attributes: Object ): WPElement` - Returns an element describing the markup of a block to be saved in the published content. This function is called before save and when switching to an editor's HTML view. | |||
- `controls: string[]` - Slugs for controls to be made available to block. See also: [`wp.blocks.registerControl`](#wpblocksregistercontrol-slug-string-settings-object-) | |||
- `encodeAttributes( attributes: Object ): Object` - Called when save markup is generated, this function allows you to control which attributes are to be encoded in the block comment metadata. By default, all attribute values not defined in the block's `attributes` property are serialized to the comment metadata. If defined, this function should return the subset of attributes to encode, or `null` to bypass default behavior. | |||
- `isVisible: boolean` - Whether the block is to be made available as an option in the block inserter overlay. Defaults to `true`. |
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.
I would prefer isDeprecated
for deprecating blocks and I don't think we should make this block invisible in the inserter (so maybe the isVisible
is not worth it for now). IMO the fallback block should be something close to the current editor which will serve as a backwards compatibility solution (think current editor inside as a block).
blocks/index.js
Outdated
// Block attributes by hpq | ||
if ( blockSettings.attributes ) { | ||
return { ...attrs, ...query.parse( rawContent, blockSettings.attributes ) }; | ||
} |
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.
Now that I think about it, we probably need a fallback return attrs
@jasmussen I may be wrong, but I think there are way more unparseable blocks especially since we're trying to keep the structure as flat as possible, nesting complex HTML tags could lead to other sort of unparseable blocks. I was thinking we leave the "current editor" (or tweak its UI a bit) for unparseable blocks, that way we have a good backwards compatibility strategy that handles all the use cases. And for people complaining about the new editor, they could still use this |
That sounds pretty good to me. Sounds like much larger sequences of markup can count as the contents of an "unparseable block", correct? Perhaps that you could even choose to insert an "unparseable block"? In case of the latter, we might want to rename it :) — an "I know what I'm doing block" has been suggested. Perhaps "Generic", as Andrew suggested in this very PR is a good term. |
Yep
I was think of "HTML block" :) |
That works too, though it would be rendered, right? I.e. you wouldn't see the markup itself? If yes, we should dig up the past discussions around labelling the two previous editor modes "Visual" and "Text", seems like we should sync up with that somehow. |
Also CC: @mtias |
I don't think a |
Here's a potential approach that might help us get closer to a solution to this issue:
As an aside: the sooner we get to the point people can "code blocks" as modules separate from the actual block engine, the better, as we can quickly prototype any of the above. |
Hi! We (Ephox) have been thinking about this issue and experimenting with some ideas... Imagine that in addition to all the block types that we are building ( #16 ) we create a TinyMCE Block Type. So, when you click on the inserter ( #323 ), TinyMCE Block Type would pop up as one of the choices. The idea behind this block type would be to leverage TinyMCE to:
The UI on the TinyMCE Block Type would look just like the rest of the WP blocks, so it would appear seamless in the context of the whole page. A really rough sketch: blue are all the WP block types; red is the TinyMCE Block Type |
68ce6e9
to
55c6ffc
Compare
55c6ffc
to
a4e2994
Compare
I've rebased and pushed a new approach. Instead of registering a generic block, the logic changes now boil down to:
The latter case is like the idea of the "unparseable block" in @jasmussen's earlier comment. I think eventually we'll want to move out the fallback rendering behavior. It's not clear yet whether this is just containing it in a separate component or perhaps integrating this into some transformations API. Just like we'll need to support blocks initializing from other blocks, we might be able to extend this so that a generic block could express wildcard ability to render any block which has no other applicable handlers. This is similar to a middleware architecture like how an Express application handles 404 pages as simply the last available handler for a route. Obviously ordering (or priority) would be a requirement to supporting this.
@folletto Is the thinking here that if a block's render behavior isn't known, to display the raw HTML (the escaped text, not rendered HTML) of the parsed node to clearly demonstrate that something's broken?
@annaephox Are you proposing that the TinyMCE block would be the fallback handler for unknown blocks? This could potentially align with my idea above of "a generic block expressing wildcard ability to render any block" (except in this case that generic block is the TinyMCE block). |
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.
I think this is way better. Good job here. I prefer the blockType fallback (see bellow), but It could be done later too.
blocks/parser.js
Outdated
@@ -37,16 +39,14 @@ export function getBlockAttributes( blockNode, blockSettings ) { | |||
* @return {Array} Block list | |||
*/ | |||
export default function parse( content ) { | |||
return grammarParse( content ).reduce( ( memo, blockNode ) => { | |||
return grammarParse( content ).map( ( blockNode ) => { | |||
const settings = getBlockSettings( blockNode.blockType ); |
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.
What do you think if we fallback here to a blockType = "html"
(or tiny, generic or whatever, just pick one) whenever we get falsy
settings, we could call, getBlockSettings again with the default block after that.
This would allow us to leverage the TinyMCE
block everyone is talking about (me included) as a fallback block.
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.
How does the parser know that the generic block even exists? Would this be a pre-registered block maybe?
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.
For me it's ok to have a "special" block defined in all use cases (auto-registered) in the blocks module
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.
The grammar already returns and html
blockType if no comment is found
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.
The grammar already returns and
html
blockType if no comment is found
Yeah, I wasn't sure how I felt about that, vs. simply omitting the property if it isn't a block.
For me it's ok to have a "special" block defined in all use cases (auto-registered) in the blocks module
This could work. It will likely add a dependency to wp.element
from blocks and blocks would now include accompanying CSS.
A subtle side-effect to the logic in the visual editor render is that even if a block is registered, it's also gracefully handled if it defines neither edit
nor save
. I don't think we'd care to support this, so it probably shouldn't need to be graceful.
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.
Yeah, I'm thinking more an more that the distinction between blocks
and editor
is superficial. We could merge everything IMO. We can keep two different global variables, but the module separation is not really important.
<div key={ index }> | ||
{ wp.blocks.getBlockSettings( block.blockType ).edit( block.attributes, onChangeBlock( index ) ) } | ||
</div> | ||
) } |
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.
If we fallback to a different blockType as suggested above, all the changes here are not needed anymore.
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.
If we fallback to a different blockType as suggested above, all the changes here are not needed anymore.
I kept some changes mostly for consideration of:
- What if a block decides it doesn't need to be editable, and only assigns a
save
implementation? Should we support this? With updated logic it will simply show the front-end UI within the editor. - We currently allow a block to register itself with neither
edit
norsave
implementations, which will throw if left unchecked
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.
and only assigns a save implementation?
Yep, could be good for separators and stuff like that.
We currently allow a block to register itself with neither edit nor save implementations, which will throw if left unchecked
I think we should add some validation to the registerBlock
(category, save, title, icon are all mandatory to me) (but granted this is a separate task)
@aduth see #349 for more discussion about falling back to TinyMCE for unparseable content. It is very tolerant of bad HTML and would help people progressively "upgrade" their content to add blocks or convert existing content to newer, smarter blocks (e.g. through the Type Indicator to switch a blockquote to a New Quote). |
Might not want to fall back to tinymce on everything though, like script tags or such things? |
@Afraithe we could have a Script Tag Block... in that way it is not unknown, it is known :) |
@aduth Hi and yes, I think I am proposing exactly what you wrote up there!
@mimo84 : can you take a look at the above and translate the hard bits for me, I think this aligns with what we are planning on doing next week :-) |
Last few commits introduce a |
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.
I left some small notes but this is looking good! I really like the setUnknownTypeHandler
👍
const settings = wp.blocks.getBlockSettings( block.blockType ); | ||
|
||
let BlockEdit; | ||
if ( settings ) { |
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.
I think we can assume this is always defined, right!
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.
I think we can assume this is always defined, right!
I guess this depends if the editor implementation should trust itself to know that elsewhere it has assigned the unknown type handler. 😄 I might be erring too far on the side of safety here. Would certainly be more convenient to be able to assume that settings
is always truthy.
I agree we should have more validation for I'm inclined to keep the safety check on Let's merge this and continue iterating in subsequent pull requests. |
This pull request seeks to start handling of blocks which cannot be associated with a known registered block type. The current implementation is very basic and unfinished; merely setting HTML of a
div
(dangerously) and displaying an overlay with messaging atop the rendered content.Caveats:
Open questions:
isVisible
as an option to blocks? In this case it was something of an internal flag to prevent the generic block being made available as an option in the inserter, but I could potentially see this being useful for other cases (discontinuing blocks) and aligns reasonably well toisVisible
control behavior.Implementation notes:
blocks/index.js
. Primarily to enable attribute parsing to wait until block has been determined. The fallback logic occurs in theEditor
class, not in the parsing, since the block parser should not be aware of this generic block type. (cc @youknowriad for thoughts)settings.edit
as a component, which has a few nice advantages around avoiding wrapper elements and enabling the plugin author to defineedit
andsave
asComponent
classes (e.g. if needing to trigger network request upon mount)