-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Fix for: editor.insertHtml
and editor.insertElement
pollutes editable with empty spans
#2987
Conversation
728e5ff
to
e4f5440
Compare
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 still able to reproduce the issue using editor.insertElement
function. IMO it doesn't make sense to fix only part of the issue if different insertion functions are still bugged.
I'm wondering if we can safely remove spans from content in such manner. They may be a part of some custom feature like bookmarks, although this issue: #2484 says otherwise and even our selection.createBookmars
function creates spans with
character 🤔
These changes are pretty wide in the context of the whole editor. Although, I ran all unit tests on Chrome with no fails.
I'm also wondering if this PR shouldn't be a part of major
release, because it may cause some breaking changes if someone gets around spans
removal and using them as a part of some feature. WDYT?
core/editable.js
Outdated
@@ -1717,14 +1718,24 @@ | |||
// Split inline elements so HTML will be inserted with its own styles. | |||
path = range.startPath(); | |||
if ( ( node = path.contains( isInline, false, 1 ) ) ) { | |||
range.splitElement( node ); | |||
clone = range.splitElement( 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.
A bit misleading thus this element is not a clone but the late element after the split. Maybe rename it into endNode
?
core/editable.js
Outdated
that.inlineStylesRoot = node; | ||
that.inlineStylesPeak = path.lastElement; | ||
} | ||
|
||
// Record inline merging candidates for later cleanup in place. | ||
bm = range.createBookmark(); | ||
|
||
// When called by insertHtml remove empty element created after splitting (#2813). | ||
if ( isHtml && clone && clone.isEmptyInlineRemoveable() ) { |
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.
Would be nice to extract both span
node removals into a separate function to avoid duplication.
@@ -0,0 +1,33 @@ | |||
/* bender-tags: editor,insertion */ |
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.
Do we really need a separate file for these tests? It looks like they could be placed inside tests/core/editable/insertion.js
. WDYT?
|
||
bender.tools.selection.setWithHtml( editor, initial ); | ||
|
||
editor.insertHtml( '<div>div</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.
I'm missing tests for editor.insertHtmlIntoRange
which also uses prepareRangeToDataInsertion
private function.
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've added such tests. Small note, it's a method of editable
😉
It is possible that someone is somehow preserving empty spans (#2484). However this fix affects elements that were created or altered by removing selected content from selection containers thus it shouldn't affect empty spans that were in editor before insertion. Also after looking at element.isEmptyInlineRemovable it looks like changing |
editor.insertHtml
pollutes editable with empty spanseditor.insertHtml
and editor.insertElement
pollutes editable with empty spans
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.
It looks like editor.insertHtml
doesn't work correctly with block elements and your tests exploit this issue:
- Open
tests/core/editable/manual/insert
manual test. - Follow test steps for
insert html
button.
Expected
Div element is inserted under paragraph like it's done for insert element
button.
Actual
Div element is inlined inside paragraph.
I see that there is no other easy way around this issue, so it would be nice to create separate ticket for it and reference the issue inside manual test. Also, don't forget to add information about workaround for manual test inside newly opened issue ticket, so we can get back to it when the issue is resolved.
I'm finding unit tests inside tests/core/editable/insert.js
hightly unreadable. I see that you tried to save some LOC but it shouldn't be done in expense of readibility. As an example, the first test checking editor.insertHtml
could be as simple as:
var editor = this.editor;
bender.tools.selection.setWithHtml( editor, '<span>{foo}bar</span>' );
editor.insertHtml( '<div>div</div>' );
assert.areEqual( '<div>div</div><span>bar</span>', editor.editable().getHtml() );
It also would be great to add tests verifying that empty edge spans are preserved for both inserted html and editor content. E.g. you could check if div html into <span>foo{baz} </span>
preserves the space. We will be sure that your changes don't touch user content.
I'm also missing a manual test for insertHtmlIntoRange
and insertElementIntoRange
.
core/editable.js
Outdated
@@ -2286,6 +2301,10 @@ | |||
return insert; | |||
} )(); | |||
|
|||
function removeEmptyInline( element ) { | |||
element && element.isEmptyInlineRemoveable() && element.remove(); |
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.
Please, use if
statement if a return value of the expression is irrelevant.
I can rework test, so each test case is explicitly declared, however I'd prefer to use one general test callback which will test each case based on |
Extracted to #3042 |
I've added such test case, but I need to mention that when you press space in editor, when selection is at the end of any element, than |
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.
tests/core/editable/insertion.js
Outdated
var methods = [ 'insertHtml', 'insertHtmlIntoRange', 'insertElement', 'insertElementIntoRange', 'insertText' ]; | ||
|
||
CKEDITOR.tools.array.forEach( methods, function( methodName ) { | ||
tests[ options.name.replace( 'insert', methodName ) ] = function() { |
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.
Small suggestion to compose a test method name rather than replace insert
string:
tests[ 'test ' + methodName + ' ' + options.name ] = ...
seems less hacky for me and more resistant for incorrect usage e.g. if we decide to add new test without insert
prefix.
tests/core/editable/insertion.js
Outdated
} | ||
|
||
function assertInsertionMethod( editor, expected, methodName, html ) { | ||
var useRange = methodName.indexOf( 'Range' ) >= 0, |
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 you could make it even more readable by using a switch
statement instead of string sniffing directly from addInsertionTests
?
tests/core/editable/insertion.js
Outdated
bender.test( tests ); | ||
|
||
function addInsertionTests( tests ) { | ||
CKEDITOR.tools.array.forEach( tests, addInsertionTest ); |
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.
tests
variable already exists in this file. I know that function is very small, nevertheless, maybe you could rename it to avoid using the same name? Hoisting makes name covering a bit unreadable.
}; | ||
|
||
// Add tests for (#2813). | ||
addInsertionTests( [ |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Apparently space character ' ' should be used instead of |
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.
Looks good, just some minor issues.
Failing test are not ignored on IE.
But the tests are ignored on IE 🤔
It would be nice to add a test case when you are inserting an empty element.
tests/core/editable/insertion.js
Outdated
@@ -4,7 +4,8 @@ | |||
'use strict'; | |||
|
|||
var doc = CKEDITOR.document, | |||
tools = bender.tools; | |||
tools = bender.tools, | |||
space = CKEDITOR.env.ie ? ' ' : ' '; |
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 are declaring space here for IE issue, but at the same time IE tests are ignored and the test is working on Edge without this modification.
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 value should be here, as this is used for the IE, however because of other bug test is ignored. Once #3061 is resolved test can be just unignored.
tests/core/editable/insertion.js
Outdated
name: 'when selection is in the middle of span', | ||
initial: '<span>foo{bar}' + space + '</span>', | ||
data: '<div>div</div>', | ||
expected: '<span>foo</span><div>div</div><span>' + space + '</span>', |
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.
It would be nice to add test covering the case when selection is preceded with space (<span> {foo}</span>
) because you are removing it at https://github.com/ckeditor/ckeditor-dev/blob/2669504728055ff89038f8bf87828cb6925868ae/core/editable.js#L468-L469
This reverts commit 9214d3a.
Rebased on the latest |
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 recent tests fix looks good 👍
However, I have encountered a group of cases which I'm not sure how to treat them. It happens when selection spans over multiple block elements:
It still leaves empty elements, but it seems the cause is different. Not sure if we should treat it as separate issue 🤔 Also the empty spans are not focusable so caret cannot be placed inside and are removed on getData()
. It kind of looks like behaviour related to this issue, WDYT? Alternatively we can treat it as a follow-up and fix if necessary.
Although it looks similar to the original bug it's caused by something else, so I'm for extracting it as a follow-up issue. |
I have extracted it to #3177. Since it is present from 4.5 (or even older), there is no need to fix it unless really necessary and we should be careful to make sure nothing breaks since it touches multiple block elements. |
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.
Ok, looks good 👍
What is the purpose of this pull request?
Bug fix
Does your PR contain necessary tests?
All patches which change the editor code must include tests. You can always read more
on PR testing,
how to set the testing environment and
how to create tests
in the official CKEditor documentation.
This PR contains
What changes did you make?
Inside
prepareRangeToDataInsertion
I've added check that will remove created nodes for whichisEmptyInlineRemoveable
is true.Closes #2813