-
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
Editable: Enter splits the current paragraph into two blocks #409
Conversation
I think a text block should include multiple paragraphs. |
In this case, what should happen when you apply a center alignment to a block containing multiple paragraphs? What's the flow for centering a single paragraph in this sequence? |
One possible answer:
I think we should keep it block level, I mean this applies the centering to all the paragraphs, and we store this config as a comment attribute.
You have to split them into multiple blocks |
From a technical perspective this seems reasonable, but I'm a little concerned if it's possible to create an obvious UX around this. |
Question: in a text block where multiple paragraphs are part of the same block, can we without too many headaches still have a UI that is on the HTML block level? I.e. you can still click an individual paragraph as though it was a block, to surface the options there? Early mockup here: #365 (comment) Edit: Another option is to do the same as TinyMCE does currently. If your cursor is on a paragraph that is centered, the centered button is pressed. When you put focus on a left aligned paragraph, the button is unpressed again. |
This seems doable to me. The alignment would be handled the same way we handle "bold", "italic"... |
This seems to break down the flow of render/control logic consuming and operating exclusively on the attributes of a block, since it allows an escape where an |
How do you think the inline controls ( bold, italic... ) should work? I don't think these are considered controls ( I mean controls defined on the Let me know if you have another idea about the inline controls and their state. |
Doesn't this go with the idea that a user could want to select multiple blocks and wrap a |
To me it feels like the disconnect between this approach and |
blocks/components/editable/index.js
Outdated
@@ -57,6 +59,28 @@ export default class Editable extends wp.element.Component { | |||
this.props.onChange( value ); | |||
} | |||
|
|||
onKeyDown( event ) { | |||
if ( ! this.props.tagName && event.keyCode === 13 ) { |
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.
Could we do an early return to avoid indenting the function?
if ( this.props.tagName || event.keyCode !== 13 ) {
return;
}
blocks/components/editable/index.js
Outdated
@@ -91,3 +115,7 @@ export default class Editable extends wp.element.Component { | |||
); | |||
} | |||
} | |||
|
|||
Editable.defaultProps = { |
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.
Aside: I'm wary of the future of defaultProps
, especially since in most cases it can be achieved by destructured object defaults. We're doing this already for the component with the tagName
prop (see render
) so we should at least choose a consistent approach.
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'm ok with default values while destructuring
blocks/components/editable/index.js
Outdated
// Wait for the event to propagate | ||
setTimeout( () => { | ||
// Getting the content before and after the cursor | ||
this.editor.selection.getStart(); |
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.
Problem with a setTimeout
is we can't be guaranteed that the component didn't unmount in the last tick, meaning due to the behavior of the function ref
, this.editor
could potentially be null
.
React will call the
ref
callback with the DOM element when the component mounts, and call it withnull
when it unmounts.
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.
Also... what is this line achieving if It's not being assigned? Are there side effects to calling getStart
?
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.
Nothing :)
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 first point of my comment still applies for calling on this.editor
in the context of setTimeout
:
Problem with a
setTimeout
is we can't be guaranteed that the component didn't unmount in the last tick, meaning due to the behavior of the functionref
,this.editor
could potentially benull
.
blocks/components/editable/index.js
Outdated
const after = getHtml( childNodes.slice( splitIndex ) ); | ||
|
||
// Splitting into two blocks | ||
this.editor.setContent( this.props.value ); |
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'm confused by this line. Wouldn't this reset the content back to its original value prior to the split? I don't see that in practice, but confused as to why it wouldn't.
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, that's the intent. By this, I revert the content to the previous state before hitting Enter, because this change is "controlled", the split callback should update the content accordingly.
If I drop this line, it will work for the moment, but I know that without it we can have issues (Forgot exactly why, this is comming from the multi-instance). I think maybe focus issues.
editor/test/state.js
Outdated
} ); | ||
|
||
expect( Object.keys( state.byUid ) ).to.have.lengthOf( 3 ); | ||
expect( state.order ).to.eql( [ 'kumquat', 'kumquat3', 'kumquat2' ] ); |
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.
Embrace the theme! I demand more originality! 😆
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.
hahahaha :)
Option 1: apply to all paragraphs; require splitting into a separate block to align multiple paragraphs separately. Option 2: remove the alignment buttons. Only half-joking here.
Here are two other ways to think about this issue. Layout blocksLonger-term we want to extend the concept of "content blocks" to "layout blocks" - providing an alternative to a widget seems like a good first step here. I'm going to use the term "widget" to mean the block editing experience that we will eventually want to bring to other pieces of a WP page layout. Should widgets be able to contain multiple paragraphs of text? Yes, absolutely. Should widgets contain multiple "blocks" as we are currently building them? I think probably not. Most common editing operationsWriting multiple paragraphs of text in a row is extremely common. Therefore, this operation should be extremely easy. The only way we should have one paragraph per block is if the friction of inserting a new block and navigating across blocks is near zero. We already know this isn't the case. To me this implies very clearly that a single text block should be able to contain multiple paragraphs. |
@joyously - I think we are not ready to plan for this yet. I'm also operating under the assumption that a block will be "root level" for the foreseeable future - i.e. it will be a direct child of the |
That sounds backwards. Keep it in mind when designing the basics, because it will be wanted.
That's a strange assumption. Nested lists come to mind immediately. An image is a block, right? And can you put it in a paragraph? |
49fd4ea
to
4d9eaef
Compare
In 4d9eaef a text block can have multiple paragraphs and I'm using a parent So now, if you hit "Enter" once, the block is not split. You need another "Enter" press to split. |
blocks/library/text/index.js
Outdated
value={ content } | ||
onChange={ ( value ) => setAttributes( { content: value } ) } | ||
style={ align ? { textAlign: align } : null } | ||
onSplit={ ( before, after ) => { | ||
setAttributes( { content: before } ); | ||
insertBlockAfter( { |
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'll use the createBlock
utility once #407 land
🎉 I suppose the next thing we will want to handle is Backspace at the beginning of a block to collapse two blocks together. I'm still concerned about the use cases in #179 but allowing multiple paragraphs helps a lot.
I thought adding wrapper |
@joyously - this is valuable feedback, but it is pretty far out of scope for this particular pull request. Can you please create two separate issues for these tasks?
and a couple of the use cases you're imagining for each one. |
It's a possibility but harder to implement, I just opted for the simplest for now. I can take a deeper look but the problem here is the |
Yep, this is another set of issues that we've been discussing in #391 and #413. I'm surprised that it ended up being related here, but given that, your approach here seems fine. I'm also a bit concerned about how we now require an extremely specific structure ( Can we get some test cases for parsing HTML content into the structure for this block? Like I'm concerned about the That's all the feedback I had here, this does seem to be working as intended in my testing. |
4d9eaef
to
e5c363e
Compare
Yes, we could. I'm planing to do this in a separate PR because it implies some changes in how we register the blocks. I would like us to be able to test the block definition object without registering them. By this , I mean extracting the All the other feedback have been adressed |
- Do not split when hitting Enter on the first row - Fix the nullable value error
1139bc8
to
de3265a
Compare
@nylen Your fix is good since the block attributes are nullable by default. And I also fixed first line issue. @aduth I've used something similar to #419 to avoid the |
Great discussion. CC: @mtias as I know you've thoughts about this. |
It really seems we have two routes:
In both cases a double (or single) enter would show the block-insert menu to the left. Depending on what you do next we would break out from the current text block or continue. Given this could have significantly different UXs, what do you think, for the sake of moving faster, to make two different blocks for now:
This would allow us to get a feel for them and make a better decision. |
Matías put it pretty excellently, describing the fork in the road: paragraph as a block, or text as a continuous block. If we can do both approaches — this PR being the Paragraph block approach — we will be able to test what works best in writing bouts. In the end, one approach may win, or we might decide that there is a place for both. As Matías says, in both cases, double enter gives you access to the Inserter. Both at the end of content: and between content: In a paragraph-is-the-block world, double enter does split the block. But if you are writing in a text-is-the-block, the block isn't actually split until you make a choice in the inserter. If you choose to insert a paragraph here, the text block isn't split, whereas if you insert a gallery it does split. @youknowriad just like how we are trying two parsers at the same time, can we do the same here? And if yes, should the continuous text block be a separate PR? If yes to both questions, it seems like this one should be good to merge. |
@jasmussen Yes we could create another block for the Contiguous paragraphs are part of the same text block. This means showing the block inserter in the middle of the text block which will give us a lot more headaches 😄 |
Is there anything we can do from an API or code design or user experience point of view that can make this headache less painful? What is the aspirin to this approach? If there's a design change I can make to simplify this, I'm totally open. |
👍 Awesome. Then seems worth doing as a separate PR. Want me to create a ticket for it? |
👍
Actually, I'm not sure we can do anything about it. It's just a hard task because we're mainly considering the |
Created ticket here: #447 |
I'm merging this one for now, if you have any other concern, let me know I'll try to address it. |
@@ -58,6 +61,35 @@ export default class Editable extends wp.element.Component { | |||
this.props.onChange( value ); | |||
} | |||
|
|||
onNewBlock() { |
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.
When is this function called? I cannot find any documentation for TinyMCE's NewBlock
event. Based on its name, I would have thought it'd been called on a simple enter press, but in debugging this is not the case. Can we add a DocBlock explaining when this is expected to occur.
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.
Having some memories about a hint from @iseulde from the TinyMCE source code. The idea was that each time a new paragraph is created the function is called or something like that :)
This PR builds on #407
Tries to address #375 #374
The idea for this PR is to catch
Enter
key down event and split the text blocks into two text blocks.How it works?
tagName
on theEditable
component, I'm assuming we want TinyMCE to handle theEnter
key press (see list block for example)text/block
I'm dropping the tagName to allow customEnter
behavior.setTimeout
), and then we tries to figure out how to split the content ( computingbefore
andafter
) . These values are passed to a new callback proponSplit
edit
property of blocks. I'm calling itinsertBlockAfter
. The block author can call this function to append a new block after the current block.text-align
attribute. Will we duplicate it on the different paragraphs? Should we add adiv
wrapper.