-
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
Improving edge detection in blocks #3015
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3015 +/- ##
==========================================
+ Coverage 34.54% 34.55% +0.01%
==========================================
Files 261 261
Lines 6710 6699 -11
Branches 1225 1229 +4
==========================================
- Hits 2318 2315 -3
+ Misses 3704 3692 -12
- Partials 688 692 +4
Continue to review full report at Codecov.
|
blocks/editable/index.js
Outdated
@@ -298,7 +299,7 @@ export default class Editable extends Component { | |||
|
|||
isStartOfEditor() { | |||
const range = this.editor.selection.getRng(); | |||
if ( range.startOffset !== 0 || ! range.collapsed ) { | |||
if ( ! isAtCursorStart( range.startContainer, range.startOffset ) || ! range.collapsed ) { |
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 replace these methods entirely?
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 is use isEdge( editor.getBody(), true )
for isStart and isEdge( editor.getBody(), false )
for end ? I think you're right. Nice catch :)
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.
In terms of avoiding the need to understand that boolean, are you OK with me exporting LEFT_EDGE = true and RIGHT_EDGE = false as constants, and then using them in this file?
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 alternatively exposing isLeftEdge and isRightEdge?
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.
Maybe isEdge( { container: Element, start: Boolean } )
?
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 defer to your preference :) I guess it matches the name in the range API for collapsing, which is most likely what you're basing it on. I've always found start a very strange name for a boolean, but that's just person taste.
I'm working on this now, so are you OK with the idea of exposing constants for the second argument or would you prefer passing an object like your last suggestion?
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 an object might be better for now, since it's just adding the braces. :) But that's just my opinion.
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.
Used object notation as recommended.
editor/utils/dom.js
Outdated
* @param {Integer} offset the offset | ||
* @return {Boolean} whether or not the offset is at the last cursor position in node | ||
*/ | ||
export function isAtCursorEnd( node, offset ) { |
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.
Might it make sense to only introduce one method (isAtCursorEnd
) for now? In more sure if it's useful to create 4 functions right now. Additionally, if editable uses the rest of the logic in this file, we might not need to export it.
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.
You would, for the tests, but maybe we can test isEdge
as a whole instead?
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.
With your above change to just use isEdge outside instead of needing to call an internal method, my preference would be to keep the methods for isAtCursorStart, but only expose isEdge. How does that sound?
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 major reason is it just increases the likelihood that lots of code won't have to change if it turns out that you have to start ignoring some offsets for start (like empty text nodes). It can get fairly gnarly with text node fragmentation. Having the cursor calculations in one spot helps in the long run. But yep, if we are able to use isEdge the other place I changed, then definitely only expose that one and just test it.
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.
With your above change to just use isEdge outside instead of needing to call an internal method, my preference would be to keep the methods for isAtCursorStart, but only expose isEdge. How does that sound?
That's exactly what I mean. :)
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.
Only isEdge is exposed now.
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 I also meant though: maybe we can only keep only getCursorEnd
if the other one is just a stub for now, and isAtCursorEnd
is just an equality check? Why create so many functions?
editor/utils/dom.js
Outdated
@@ -1,3 +1,50 @@ | |||
const NODE_TEXT_TYPE = 3; | |||
const NODE_ELEMENT_TYPE = 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.
In other modules, we've been using:
/**
* Browser dependencies
*/
const { ELEMENT_NODE, TEXT_NODE } = window.Node
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.
Ah, I was hoping it would be somewhere.
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.
Done.
…al functions are no longer exported
Thanks for the PR, I'll test in Classic Text a bit. |
Works well with an image in Classic Test. I'm seeing a regression though. In the demo content there's an image without a caption. I select it, then put the caret in the caption. Right arrow doesn't do anything. Looks like the node is the editor, but |
What's the DOM structure? Also, whereabouts on the demo page? |
<figcaption contenteditable="true" class="blocks-editable__tinymce mce-content-body" aria-label="Write caption…" id="mce_36" data-is-placeholder-visible="false"><br data-mce-bogus="1"></figcaption> |
And the parent of that node? (Ignore, I now see it's content-editable itself) |
A non editable div. Not sure why that matters? :) |
Yeah, I see what's happening here. It looks like tiny is injecting a |
So I think what we'll have to do is strip any BR tags from the end of the child nodes when counting the length. This is where the edge-case part begins. |
Why not always return true when requesting if it's the edge, and the range is at the content editable container? |
if ( ! range || ! range.collapsed ) {
return false;
}
if ( node.isContentEditable ) {
return true;
}
if ( start && ! isAtCursorStart( node, offset ) ) {
return false;
}
if ( ! start && ! isAtCursorEnd( node, offset ) ) {
return false;
} |
In that case we also don't need to bother checking |
Because the content could be like this:
with the selection being (div, 2) (i.e. just before the 2nd br). Unfortunately, you can't make generalisations like that in complex content. The browser will report the selection being at the root any time it doesn't have a text node to reference. |
True. Then it should be this? if ( node.isContentEditable && offset === 0 ) {
return true;
} |
You could have that shortcut, yes. But the problem at the moment is that it isn't detecting the right edge, because there's a |
So I think I have a solution for that particular case (filtering out trailing br tags and checking again), but it's going to need a lot more work as you go forward. You'll identify nodes which don't create valid cursor positions for the browser but do occupy positions in the DOM. Empty text and formatting nodes and zero width cursors are particularly painful here. |
There was no activity in this PR for over 6 months. It looks like it was abandoned. Let’s close it. We can always reopen and rebase it with master branch to align with the current state of Gutenberg. |
Description
Added some cursor position APIs to help with edge detection
How Has This Been Tested?
I can't add any tests to test the only exported method (isDom) because it requires window.getSelection to be in the test runner.
Types of changes
Bug fix / improvement.
Checklist: