-
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
Blocks: Handle pressing backspace at the beginning of blocks #461
Changes from all commits
5e4945d
7106128
d5fc22a
f0c34ba
ae4f41a
15b70c1
129ffd7
1b360dc
a2c1065
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ class VisualEditorBlock extends wp.element.Component { | |
this.setAttributes = this.setAttributes.bind( this ); | ||
this.maybeDeselect = this.maybeDeselect.bind( this ); | ||
this.maybeHover = this.maybeHover.bind( this ); | ||
this.mergeWithPrevious = this.mergeWithPrevious.bind( this ); | ||
this.previousOffset = null; | ||
} | ||
|
||
|
@@ -38,7 +39,7 @@ class VisualEditorBlock extends wp.element.Component { | |
|
||
setAttributes( attributes ) { | ||
const { block, onChange } = this.props; | ||
onChange( { | ||
onChange( block.uid, { | ||
attributes: { | ||
...block.attributes, | ||
...attributes | ||
|
@@ -61,6 +62,44 @@ class VisualEditorBlock extends wp.element.Component { | |
} | ||
} | ||
|
||
mergeWithPrevious() { | ||
const { block, previousBlock, onRemove, onChange } = this.props; | ||
|
||
// Do nothing when it's the first block | ||
if ( ! previousBlock ) { | ||
return; | ||
} | ||
|
||
const previousBlockSettings = wp.blocks.getBlockSettings( previousBlock.blockType ); | ||
|
||
// Remove the previous block if it's not mergeable | ||
if ( ! previousBlockSettings.merge ) { | ||
onRemove( previousBlock.uid ); | ||
return; | ||
} | ||
|
||
// We can only merge blocks with similar types | ||
// thus, we transform the block to merge first | ||
const blockWithTheSameType = previousBlock.blockType === block.blockType | ||
? block | ||
: wp.blocks.switchToBlockType( block, previousBlock.blockType ); | ||
|
||
// If the block types can not match, do nothing | ||
if ( ! blockWithTheSameType ) { | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to remove the previous block here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. I don't really know the right answer. I think this use-case might not happen as often as we think because I'm assuming the majority of textual blocks could be transformed in between'em but It's a possibility. I preferred to avoid deletion and do nothing but I don't have strong arguments for one or another. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My main reason for bringing it up was merely as a point of consistency, since it seemed from the above condition that the "unhandleable" case should simply be removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not considering the "merge not defined" as "unhandleable" but more like, if you want this block to be removed when we hit backspace, don't define the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Isn't that the condition above though, not this one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I misread your previous message as "if you don't want this block to be removed". Disregard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine, but we should probably document the behavior somewhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd very much prefer that we don't silently delete content here. Submitted a PR to change this behavior - #530 |
||
} | ||
|
||
// Calling the merge to update the attributes and remove the block to be merged | ||
const updatedAttributes = previousBlockSettings.merge( previousBlock.attributes, blockWithTheSameType.attributes ); | ||
onChange( previousBlock.uid, { | ||
attributes: { | ||
...previousBlock.attributes, | ||
...updatedAttributes | ||
} | ||
} ); | ||
onRemove( block.uid ); | ||
} | ||
|
||
componentDidUpdate() { | ||
if ( this.previousOffset ) { | ||
window.scrollTo( | ||
|
@@ -137,6 +176,7 @@ class VisualEditorBlock extends wp.element.Component { | |
setAttributes={ this.setAttributes } | ||
insertBlockAfter={ onInsertAfter } | ||
setFocus={ onFocus } | ||
mergeWithPrevious={ this.mergeWithPrevious } | ||
/> | ||
</div> | ||
); | ||
|
@@ -145,19 +185,23 @@ class VisualEditorBlock extends wp.element.Component { | |
} | ||
|
||
export default connect( | ||
( state, ownProps ) => ( { | ||
order: state.blocks.order.indexOf( ownProps.uid ), | ||
block: state.blocks.byUid[ ownProps.uid ], | ||
isSelected: state.selectedBlock.uid === ownProps.uid, | ||
isHovered: state.hoveredBlock === ownProps.uid, | ||
focus: state.selectedBlock.uid === ownProps.uid ? state.selectedBlock.focus : null, | ||
isTyping: state.selectedBlock.uid === ownProps.uid ? state.selectedBlock.typing : false, | ||
} ), | ||
( state, ownProps ) => { | ||
const order = state.blocks.order.indexOf( ownProps.uid ); | ||
return { | ||
previousBlock: state.blocks.byUid[ state.blocks.order[ order - 1 ] ] || null, | ||
block: state.blocks.byUid[ ownProps.uid ], | ||
isSelected: state.selectedBlock.uid === ownProps.uid, | ||
isHovered: state.hoveredBlock === ownProps.uid, | ||
focus: state.selectedBlock.uid === ownProps.uid ? state.selectedBlock.focus : null, | ||
isTyping: state.selectedBlock.uid === ownProps.uid ? state.selectedBlock.typing : false, | ||
order | ||
}; | ||
}, | ||
( dispatch, ownProps ) => ( { | ||
onChange( updates ) { | ||
onChange( uid, updates ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: I don't love that we have some props that accept a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think making the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that idea because it promotes consistency, but I don't like that idea because it loses the sense that the component is meant to be specific to a single block. |
||
dispatch( { | ||
type: 'UPDATE_BLOCK', | ||
uid: ownProps.uid, | ||
uid, | ||
updates | ||
} ); | ||
}, | ||
|
@@ -210,6 +254,13 @@ export default connect( | |
uid: ownProps.uid, | ||
config | ||
} ); | ||
}, | ||
|
||
onRemove( uid ) { | ||
dispatch( { | ||
type: 'REMOVE_BLOCK', | ||
uid | ||
} ); | ||
} | ||
} ) | ||
)( VisualEditorBlock ); |
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 don't know if it was intentional, but nice optimization having the heaviest part of the condition performed last and taking advantage of short-circuiting 👍
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.
Intentional :)