Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 3e9f81b

Browse files
authored
Merge pull request #1327 from ckeditor/t/1326
Fix: Improved how `model.Differ` checks whether the operation should be buffered or not. Closes #1326.
2 parents 8a68b0e + 0023702 commit 3e9f81b

File tree

2 files changed

+68
-19
lines changed

2 files changed

+68
-19
lines changed

src/model/differ.js

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,54 @@ export default class Differ {
107107
*/
108108
bufferOperation( operation ) {
109109
switch ( operation.type ) {
110-
case 'insert':
110+
case 'insert': {
111+
if ( this._isInInsertedElement( operation.position.parent ) ) {
112+
return;
113+
}
114+
111115
this._markInsert( operation.position.parent, operation.position.offset, operation.nodes.maxOffset );
112116

113117
break;
118+
}
114119
case 'addAttribute':
115120
case 'removeAttribute':
116-
case 'changeAttribute':
121+
case 'changeAttribute': {
117122
for ( const item of operation.range.getItems() ) {
123+
if ( this._isInInsertedElement( item.parent ) ) {
124+
continue;
125+
}
126+
118127
this._markAttribute( item );
119128
}
120129

121130
break;
131+
}
122132
case 'remove':
123133
case 'move':
124-
case 'reinsert':
125-
this._markRemove( operation.sourcePosition.parent, operation.sourcePosition.offset, operation.howMany );
126-
this._markInsert( operation.targetPosition.parent, operation.getMovedRangeStart().offset, operation.howMany );
134+
case 'reinsert': {
135+
const sourceParentInserted = this._isInInsertedElement( operation.sourcePosition.parent );
136+
const targetParentInserted = this._isInInsertedElement( operation.targetPosition.parent );
137+
138+
if ( !sourceParentInserted ) {
139+
this._markRemove( operation.sourcePosition.parent, operation.sourcePosition.offset, operation.howMany );
140+
}
141+
142+
if ( !targetParentInserted ) {
143+
this._markInsert( operation.targetPosition.parent, operation.getMovedRangeStart().offset, operation.howMany );
144+
}
127145

128146
break;
129-
case 'rename':
147+
}
148+
case 'rename': {
149+
if ( this._isInInsertedElement( operation.position.parent ) ) {
150+
return;
151+
}
152+
130153
this._markRemove( operation.position.parent, operation.position.offset, 1 );
131154
this._markInsert( operation.position.parent, operation.position.offset, 1 );
132155

133156
break;
157+
}
134158
}
135159

136160
// Clear cache after each buffered operation as it is no longer valid.
@@ -396,10 +420,6 @@ export default class Differ {
396420
* @param {Number} howMany
397421
*/
398422
_markInsert( parent, offset, howMany ) {
399-
if ( this._isInInsertedElement( parent ) ) {
400-
return;
401-
}
402-
403423
const changeItem = { type: 'insert', offset, howMany, count: this._changeCount++ };
404424

405425
this._markChange( parent, changeItem );
@@ -414,10 +434,6 @@ export default class Differ {
414434
* @param {Number} howMany
415435
*/
416436
_markRemove( parent, offset, howMany ) {
417-
if ( this._isInInsertedElement( parent ) ) {
418-
return;
419-
}
420-
421437
const changeItem = { type: 'remove', offset, howMany, count: this._changeCount++ };
422438

423439
this._markChange( parent, changeItem );
@@ -432,10 +448,6 @@ export default class Differ {
432448
* @param {module:engine/model/item~Item} item
433449
*/
434450
_markAttribute( item ) {
435-
if ( this._isInInsertedElement( item.parent ) ) {
436-
return;
437-
}
438-
439451
const changeItem = { type: 'attribute', offset: item.startOffset, howMany: item.offsetSize, count: this._changeCount++ };
440452

441453
this._markChange( item.parent, changeItem );

tests/model/differ.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,18 @@ describe( 'Differ', () => {
641641
] );
642642
} );
643643
} );
644+
645+
it( 'inside a new element', () => {
646+
// Since the rename is inside a new element, it should not be listed on changes list.
647+
model.change( () => {
648+
insert( new Element( 'blockQuote', null, new Element( 'paragraph' ) ), new Position( root, [ 2 ] ) );
649+
rename( root.getChild( 2 ).getChild( 0 ), 'listItem' );
650+
651+
expectChanges( [
652+
{ type: 'insert', name: 'blockQuote', length: 1, position: new Position( root, [ 2 ] ) }
653+
] );
654+
} );
655+
} );
644656
} );
645657

646658
describe( 'attribute', () => {
@@ -1230,7 +1242,10 @@ describe( 'Differ', () => {
12301242
} );
12311243
} );
12321244

1233-
it( 'proper filtering of changes in inserted elements', () => {
1245+
// In this scenario we create a new element, then remove something from before it to mess up with offsets,
1246+
// finally we insert some content into a new element. Since we are inserting into a new element, the
1247+
// inserted children should not be shown on changes list.
1248+
it( 'proper filtering of changes in inserted elements #1', () => {
12341249
root.removeChildren( 0, root.childCount );
12351250
root.appendChildren( new Element( 'image' ) );
12361251

@@ -1250,6 +1265,26 @@ describe( 'Differ', () => {
12501265
] );
12511266
} );
12521267
} );
1268+
1269+
// In this scenario we create a new element, then move another element that was before the new element into
1270+
// the new element. This way we mess up with offsets and insert content into a new element in one operation.
1271+
// Since we are inserting into a new element, the insertion of moved element should not be shown on changes list.
1272+
it( 'proper filtering of changes in inserted elements #2', () => {
1273+
root.removeChildren( 0, root.childCount );
1274+
root.appendChildren( new Element( 'image' ) );
1275+
1276+
model.change( () => {
1277+
// Insert `div` after `image`.
1278+
insert( new Element( 'div' ), new Position( root, [ 1 ] ) );
1279+
// Move `image` to the new `div`.
1280+
move( new Position( root, [ 0 ] ), 1, new Position( root, [ 1, 0 ] ) );
1281+
1282+
expectChanges( [
1283+
{ type: 'remove', name: 'image', length: 1, position: new Position( root, [ 0 ] ) },
1284+
{ type: 'insert', name: 'div', length: 1, position: new Position( root, [ 0 ] ) }
1285+
] );
1286+
} );
1287+
} );
12531288
} );
12541289

12551290
describe( 'getChanges()', () => {
@@ -1410,6 +1445,8 @@ describe( 'Differ', () => {
14101445
function expectChanges( expected, includeChangesInGraveyard = false ) {
14111446
const changes = differ.getChanges( { includeChangesInGraveyard } );
14121447

1448+
expect( changes.length ).to.equal( expected.length );
1449+
14131450
for ( let i = 0; i < expected.length; i++ ) {
14141451
for ( const key in expected[ i ] ) {
14151452
if ( expected[ i ].hasOwnProperty( key ) ) {

0 commit comments

Comments
 (0)