-
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
Block Switcher: Adding the transformations API and the block switcher #429
Conversation
I'm not sure about the text block having to define transformations, and transformations between types that are not the text block. I was thinking it might be best to define transformations to and from the text block when registering a block that can do these, and when we need to transform, we first transform to text, and then from text? So always first to the "base" block. |
🤔 The advantage of your proposal is that we should think of transformations into a unique block, but it has some drawbacks:
|
1e515d3
to
eaa8da2
Compare
I'm not sure what you mean with this. It could work by blocks defining if they can act as a base block, and blocks adding transformations are added to the switcher.
Sure, this will depend on the base block. What kind of data are you thinking about?
They would not have transformations to text? |
I thought you're talking about having a unique "base block" which could be "text". I don't see the advantage of having multiple base blocks compared to just have targeted transformations (my proposal). In both cases you have to think about multiple blocks.
I'm not sure, but any data stored as a comment attribute and thus hard to store in a text block.
How do they define transformations (for example transforming a Google Map into another Map block) |
From a high level, the block switcher should be able to make only non-destructive transformations. I.e. text to heading, quote, list and back, or a gallery to a slideshow and back. It would be nice if this were plugin extensible. So a checklist block could register itself as compatible with lists and then show up in the switcher. Or imagine a Podcast player block. You might insert an MP3 and get the built-in Audio block by default, but the Podcast block might have registered itself as compatible with Audio, and let you switch it to that player instead. I don't know what consequences this has for the discussion above, if any, but wanted to clarify the type of conversions in case it is helpful. |
@iseulde Thinking more about this. and what I like about your proposal is the possibility to define the transformation to a certain block or a transformation from a certain block. I'm starting to think we could be flexible in allowing both. Having two separate keys: Edit With a priority to one direction (maybe |
How does this relate to #413 and some of the ideas for unifying an API to handle not only transformations between types of blocks, but also support for legacy content and shortcodes? Also with regards to desire to enforce always transforming from for sake of extensibility. |
I'm not sure how these two APIs can be unified. #413 works on HTML content while this transforms block attributes to another type's attributes. I don't see how can we merge those. Maybe you're thinking of transforming the HTML output and parsing it again? Seems like a hack to me.
My first idea was to always enforce the "transform to" but after the discussion with @iseulde I'm thinking we should allow both, that way a block author that wants its block to be switchable from and to a core block, can write both transformations. If we enforce one direction, the custom block can be transformed to (or from) a core block but the not the opposite |
This is not entirely true, but in fairness #413 lacks good examples that would help clarify this point. The intent there is that to define the equivalent of what you have here with
In other words, the compatibility layer would always initialize by returning an object of attributes for the new block. The incoming argument could be a block, or a parsed shortcode, or a DOM node. My point in raising this is more so we don't focus too much on the individual case of supporting block transformations that we overlook the shared characteristics between it and backwards compatibility more generally. |
I agree, this makes sense 👍 |
I see what you mean now. Thus, I think we can provide this API: transformFrom: [
{
type: 'block',
blocks: [ 'core/text' ],
transform: ( attributes ) => ({})
},
{
type: 'shortcode',
transform: ( shortcodeAttribiutes ) => ({})
},
{
type: 'dom',
transform: ( domNode ) => ({})
},
],
transformTo: [
{
type: 'block',
blocks: [ 'core/text' ],
transform: ( attributes ) => ({})
}
], What do you think? |
Something like this could work, sure. A few specific remarks:
|
Shouldn't a checklist show up as a list variant as part of a list block, just like we have block quote variants and heading variants? In that case the list block would handle transformations to other blocks outside list. |
By this logic it feels like an ordered list should be a type variant of the unordered list too, which feels like a bridge too far. However your point is salient, and I feel highlights that it's difficult to know exactly where to set the line between what is a block type, and what is a block style. If a podcast block registers itself as a compatible conversion from the audio block, should it show up as a style choice or a block conversion choice? I agree, it's not super clean. But for now it feels like we should make the separation at the markup level. A |
Not sure if I explained it right, but I meant that ordered list, unordered list, and check list would all be variants/styles of the "higher" list block. Just like heading levels are variants/styles of the heading block and there are variants/styles of a blockquote block. And yes, this has a lot to do with markup/structure, but nothing with the |
Understood. It's a tricky UI, and the question basically boils down to whether we treat the list as the block, and the type of list as a style option for that block (which it sounds like you are proposing). This ties into the freeform block mockup I made: It's the best UI, arguably, for when you can select across paragraphs. But on the other hand, if it's a style option you'd first have to choose the "list" block in the switcher, and then the style afterwards. This is probably a bit more obtuse if unordered is the default, and you're looking for a way to create an ordered list and don't see the option in the dropdown. Since list is such a fundamental option, I think it might be worth proceeding with the current plan, with ordered and unordered both showing up in the switcher. This would also allow a plugin to surface different bullet types as style options for the unordered list, and different number/letter/numeral styles for the ordered list block. CC: @intronic and @mimo84 for their work in #382 |
Yeah it kind of boils down to how much UI we want to show. If you're looking to create an ordered list, you'd first have to add a list, the block is converted, and then options will pop up to choose a variant. This works the same as headings. Speaking of headings, how would that work in the freeform block mockup? |
You'd use the switcher. Even if it's a continuous text field there'd still be block level controls. |
API updated in 6e3dc9d to match #429 (comment) |
UI wise, block switcher works great. I'd like to polish the styles a little more, but I'll do that in a separate PR. Design 👍 from me. |
blocks/api/factory.js
Outdated
// Find the from transformation | ||
const transformation = | ||
transformationsFrom.find( t => t.blocks.indexOf( block.blockType ) !== -1 ) || | ||
transformationsTo.find( t => t.blocks.indexOf( blockType ) !== -1 ); |
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 the transformation to
should come first here. This way, if two blocks define conflicting transformations (for example, for a list block, "list to
text" and "text from
list"), then the current block gets the first shot at the transformation. What do you think?
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.
Yes, I had to make a choice. I remember vaguely discussing this while implementing the per-block prototype and choosing the from
transformation as the default but don't remember the arguments though.
Thinking about it more, seems more logic to give priority to the to
transformation.
editor/modes/visual-editor/block.js
Outdated
isActive: () => control.isActive( block.attributes ) | ||
} ) ) } /> | ||
) : null } | ||
<div className="editor-visual-editor__block_controls"> |
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.
Should be editor-visual-editor__block-controls
no? (block-controls
instead of block_controls
)
This is working well. Careful merging it, as I did a rebase, which "succeeded" with a bunch of complex git stuff:
However, the resulting build was broken. One thing I wanted to test out was what happens when switching a multi-paragraph I tried to test this out and found that pressing Enter within a text block does absolutely nothing. We need to get more thorough test cases around actual UX flows as soon as possible. In any case I would recommend merging this sooner rather than later so I'm going to approve it. |
@jasmussen: I'm sure you've thought about this, but design-wise, one thing I think is missing here is including the block type icon inline with the up/down arrows. Currently it's not possible to tell what block type you're actually working with. If we add this, the type switcher could just live in the same place, and it would only be enabled if there were actually any blocks available for switching. |
Never mind about this, I just needed to restart I'm going to push up the rebase, but now, when switching a text block to a heading, regardless of how many paragraphs it has, there is no content in the heading. |
8234662
to
9380c38
Compare
We actually had this in some of the very first prototypes: During the prototype phase, though, this did not appear to provide a ton of value to testers, and on the flipside it did cause a fair amount of feedback about too many different places to interact with the block: the switcher on the side, the top docked block level controls, and the inline popup toolbars. And so as part of a unification, we moved it all to be block-docked. This also gave some welcome recognition to what the role of the switcher is, since it essentially became a callback to this: That's not to say the switcher showing on hover can't work. Absolutely something to keep in mind. |
Then perhaps a smaller tweak would be to always show the block icon as the first icon in the toolbar. |
Fixed in 0c8e6b4, though the fix causes data loss when switching a multi-paragraph text block. |
Isn't that implicitly the case with the icon sitting inside the switcher? Or do you mean even on hover? |
Currently, for a list block for example, no such icon is shown because there are no possibilities for compatible blocks. |
This isn't really OK, we need to validate it. However this can be addressed in a separate PR. |
@@ -52,5 +52,33 @@ registerBlock( 'core/heading', { | |||
style={ align ? { textAlign: align } : null } | |||
dangerouslySetInnerHTML={ { __html: content } } /> | |||
); | |||
}, | |||
|
|||
transforms: { |
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.
Minor: Thinking about conventions of property order, I quite like in a typical React component the render
is usually the last member of the class, and had thought we could do similar with edit
/ save
, having other properties occur earlier. Do you think there's any value in this or another convention?
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.
Yes, I always forget about this convention sorry. Will fix later
blocks/library/heading/index.js
Outdated
blocks: [ 'core/text' ], | ||
transform: ( { content, align } ) => { | ||
return { | ||
tag: 'H2', |
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.
As evidenced with new warnings in the React 16 upgrade, we can't render a heading via the capitalized <H2 />
tag name. I guess the easiest option would be to change the edit
and save
functions to lowercase the tag, which would be fine to do in a separate pull request, unless you had another idea for guaranteeing it in the parse/transform.
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.
or maybe we should store the tag
attribute as lowercase
Yep let's merge this, we have a solid foundation for the transformations API I think |
This PR adds a v1 block switcher.
With this proposal a block can define "transformations". A transformation is function transforming the attributes of block of a certain type into another blockType.
This is similar the multi-instance prototype