-
Notifications
You must be signed in to change notification settings - Fork 40
T/1436: Fixing selection that cross limit node boundaries. #1450
Changes from 3 commits
8be680d
de7ab75
93b927d
c744608
4b381bf
f16620d
8ea5fb5
6060d76
a7b40b7
9a84bb2
f5bffd4
2445ef2
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 |
---|---|---|
|
@@ -494,9 +494,9 @@ describe( 'downcast-selection-converters', () => { | |
|
||
describe( 'table cell selection converter', () => { | ||
beforeEach( () => { | ||
model.schema.register( 'table' ); | ||
model.schema.register( 'tr' ); | ||
model.schema.register( 'td' ); | ||
model.schema.register( 'table', { isLimit: true } ); | ||
model.schema.register( 'tr', { isLimit: true } ); | ||
model.schema.register( 'td', { isLimit: true } ); | ||
|
||
model.schema.extend( 'table', { allowIn: '$root' } ); | ||
model.schema.extend( 'tr', { allowIn: 'table' } ); | ||
|
@@ -519,16 +519,16 @@ describe( 'downcast-selection-converters', () => { | |
} | ||
|
||
for ( const range of selection.getRanges() ) { | ||
const node = range.start.nodeAfter; | ||
const node = range.start.parent; | ||
|
||
if ( node == range.end.nodeBefore && node instanceof ModelElement && node.name == 'td' ) { | ||
if ( node instanceof ModelElement && node.name == 'td' ) { | ||
conversionApi.consumable.consume( selection, 'selection' ); | ||
|
||
const viewNode = conversionApi.mapper.toViewElement( node ); | ||
conversionApi.writer.addClass( 'selected', viewNode ); | ||
} | ||
} | ||
} ); | ||
}, { priority: 'high' } ); | ||
} ); | ||
|
||
it( 'should not be used to convert selection that is not on table cell', () => { | ||
|
@@ -542,7 +542,7 @@ describe( 'downcast-selection-converters', () => { | |
it( 'should add a class to the selected table cell', () => { | ||
test( | ||
// table tr#0 |td#0, table tr#0 td#0| | ||
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. Are the comments up to date now? |
||
[ [ 0, 0, 0 ], [ 0, 0, 1 ] ], | ||
[ [ 0, 0, 0, 0 ], [ 0, 0, 0, 3 ] ], | ||
'<table><tr><td>foo</td></tr><tr><td>bar</td></tr></table>', | ||
'<table><tr><td class="selected">foo</td></tr><tr><td>bar</td></tr></table>' | ||
); | ||
|
@@ -551,9 +551,9 @@ describe( 'downcast-selection-converters', () => { | |
it( 'should not be used if selection contains more than just a table cell', () => { | ||
test( | ||
// table tr td#1, table tr#2 | ||
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. And here too? |
||
[ [ 0, 0, 0, 1 ], [ 0, 0, 2 ] ], | ||
[ [ 0, 0, 0, 1 ], [ 0, 0, 1, 3 ] ], | ||
'<table><tr><td>foo</td><td>bar</td></tr></table>', | ||
'<table><tr><td>f{oo</td><td>bar</td>]</tr></table>' | ||
'[<table><tr><td>foo</td><td>bar</td></tr></table>]' | ||
); | ||
} ); | ||
} ); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1123,12 +1123,12 @@ describe( 'Schema', () => { | |
schema.extend( 'img', { allowAttributes: 'bold' } ); | ||
schema.extend( '$text', { allowIn: 'img' } ); | ||
|
||
setData( model, '[<p>foo<img>xxx</img>bar</p>]' ); | ||
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 changes the semantics of this test. Let's better use 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 still not resolved. |
||
setData( model, '<p>[foo<img>xxx</img>bar]</p>' ); | ||
|
||
const validRanges = schema.getValidRanges( doc.selection.getRanges(), attribute ); | ||
const sel = new Selection( validRanges ); | ||
|
||
expect( stringify( root, sel ) ).to.equal( '[<p>foo<img>]xxx[</img>bar</p>]' ); | ||
expect( stringify( root, sel ) ).to.equal( '<p>[foo<img>]xxx[</img>bar]</p>' ); | ||
} ); | ||
|
||
it( 'should return three ranges when attribute is not allowed on one element but is allowed on its child', () => { | ||
|
@@ -1141,12 +1141,12 @@ describe( 'Schema', () => { | |
} | ||
} ); | ||
|
||
setData( model, '[<p>foo<img>xxx</img>bar</p>]' ); | ||
setData( model, '<p>[foo<img>xxx</img>bar]</p>' ); | ||
|
||
const validRanges = schema.getValidRanges( doc.selection.getRanges(), attribute ); | ||
const sel = new Selection( validRanges ); | ||
|
||
expect( stringify( root, sel ) ).to.equal( '[<p>foo]<img>[xxx]</img>[bar</p>]' ); | ||
expect( stringify( root, sel ) ).to.equal( '<p>[foo]<img>[xxx]</img>[bar]</p>' ); | ||
} ); | ||
|
||
it( 'should not leak beyond the given ranges', () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -562,7 +562,7 @@ describe( 'DataController utils', () => { | |
schema.register( 'image', { allowWhere: '$text' } ); | ||
schema.register( 'paragraph', { inheritAllFrom: '$block' } ); | ||
schema.register( 'heading1', { inheritAllFrom: '$block' } ); | ||
schema.register( 'blockWidget' ); | ||
schema.register( 'blockWidget', { isLimit: true } ); | ||
schema.register( 'restrictedRoot', { | ||
isLimit: true | ||
} ); | ||
|
@@ -621,7 +621,7 @@ describe( 'DataController utils', () => { | |
deleteContent( model, selection ); | ||
|
||
expect( getData( model, { rootName: 'bodyRoot' } ) ) | ||
.to.equal( '[<paragraph>x</paragraph>]<paragraph></paragraph><paragraph>z</paragraph>' ); | ||
.to.equal( '<paragraph>[x]</paragraph><paragraph></paragraph><paragraph>z</paragraph>' ); | ||
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. You can change the initial selection too, so this test is less confusing (we're not checking the sel post-fixer here). |
||
} ); | ||
|
||
it( 'creates a paragraph when text is not allowed (block widget selected)', () => { | ||
|
@@ -644,7 +644,16 @@ describe( 'DataController utils', () => { | |
{ rootName: 'bodyRoot' } | ||
); | ||
|
||
deleteContent( model, doc.selection ); | ||
model.change( writer => { | ||
// Set selection to[<heading1>yyy</heading1>] in change() block due to selection post-fixer. | ||
const range = new Range( | ||
new Position( doc.getRoot( 'bodyRoot' ), [ 1 ] ), | ||
new Position( doc.getRoot( 'bodyRoot' ), [ 2 ] ) | ||
); | ||
writer.setSelection( range ); | ||
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. You don't have to set that selection. 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. Also, it'd be good if you changed it in the |
||
|
||
deleteContent( model, doc.selection ); | ||
} ); | ||
|
||
expect( getData( model, { rootName: 'bodyRoot' } ) ) | ||
.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' ); | ||
|
@@ -657,7 +666,16 @@ describe( 'DataController utils', () => { | |
{ rootName: 'bodyRoot' } | ||
); | ||
|
||
deleteContent( model, doc.selection ); | ||
model.change( writer => { | ||
// Set selection to[<heading1>yyy</heading1><paragraph>yyy</paragraph>] in change() block due to selection post-fixer. | ||
const range = new Range( | ||
new Position( doc.getRoot( 'bodyRoot' ), [ 1 ] ), | ||
new Position( doc.getRoot( 'bodyRoot' ), [ 3 ] ) | ||
); | ||
writer.setSelection( range ); | ||
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. The same as above– no need to set it. Also, it'd be good if you changed it in the |
||
|
||
deleteContent( model, doc.selection ); | ||
} ); | ||
|
||
expect( getData( model, { rootName: 'bodyRoot' } ) ) | ||
.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' ); | ||
|
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 that this comment isn't entirely correct – I mean the "same" part. What about:
Also, what about:
In this case, neither the start's nor end's
textNode
getter will return something.And there's one more case which is missed here:
You might've had a different goal by adding this condition – filtering some cases which might be broken by the following logic of this function. But, if the logic of such a check isn't "complete" in a bigger picture, a wider context of the entire algorithm, then it may be misleading. So, instead of patching this algorithm for a hidden purpose, let's thing wider.
E.g. what kind of non-collapsed selections don't need to be fixed? Those which meet the following criteria (all):
schema.getLimitElement( range.start ) == schema.getLimitElement( range.end )
– it means the selection starts and ends in the same limit element, so it does not cross any limit element boundary. If a range violates this rule, it needs to be extended to cover the lowest common limit element of both its ends (schem.getLimitElement( range )
).Note: Right now,
schema.getLimitElement()
works only with entire selections, but it's not hard to "downgrade" it to support single ranges (returns LCA like for the entire selection) and single positions (returns a lowest limit element in which this position is contained) too.schema.checkChild( range.start, '$text' ) && schema.checkChild( range.end, '$text' )
– both its ends are in a place where a text is allowed. They don't need to be placed in/next to a text (<p>[<inlineWidget/>...
) – it's enough that a text is allowed there.Note: A range containing a block widget will not be rooted in a place where text is allowed, so it will not pass this check. However, it doesn't mean that it's incorrect – I was describing a range which is certainly correct (i.e. a possible early return check), not an algorithm for deciding whether a range is correct or not.
Therefore, if a range violates this rule, it needs to be checked further:
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.
By structuring the algorithm as I described above, the rules by which it works will be easier to read. Right now, it consists of "let's try fixing this, let's try that" which is harder for me to digest.