diff --git a/package.json b/package.json index 4403189bb..46e8280f3 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@ckeditor/ckeditor5-link": "^18.0.0", "@ckeditor/ckeditor5-list": "^18.0.0", "@ckeditor/ckeditor5-paragraph": "^18.0.0", + "@ckeditor/ckeditor5-table": "^18.0.0", "@ckeditor/ckeditor5-theme-lark": "^18.0.0", "@ckeditor/ckeditor5-typing": "^18.0.0", "@ckeditor/ckeditor5-undo": "^18.0.0", diff --git a/src/model/operation/transform.js b/src/model/operation/transform.js index b12f65b20..c7ac06d26 100644 --- a/src/model/operation/transform.js +++ b/src/model/operation/transform.js @@ -728,9 +728,14 @@ function padWithNoOps( operations, howMany ) { // ----------------------- setTransformation( AttributeOperation, AttributeOperation, ( a, b, context ) => { - if ( a.key === b.key ) { - // If operations attributes are in conflict, check if their ranges intersect and manage them properly. - + // If operations in conflict, check if their ranges intersect and manage them properly. + // + // Operations can be in conflict only if: + // + // * their key is the same (they change the same attribute), and + // * they are in the same parent (operations for ranges [ 1 ] - [ 3 ] and [ 2, 0 ] - [ 2, 5 ] change different + // elements and can't be in conflict). + if ( a.key === b.key && a.range.start.hasSameParentAs( b.range.start ) ) { // First, we want to apply change to the part of a range that has not been changed by the other operation. const operations = a.range.getDifference( b.range ).map( range => { return new AttributeOperation( range, a.key, a.oldValue, a.newValue, 0 ); diff --git a/tests/model/operation/transform/attribute.js b/tests/model/operation/transform/attribute.js index afbed67f4..3fd8bfa4b 100644 --- a/tests/model/operation/transform/attribute.js +++ b/tests/model/operation/transform/attribute.js @@ -172,6 +172,21 @@ describe( 'transform', () => { expectClients( '<$text attr="bar">Foo Bar' ); } ); + + // https://github.com/ckeditor/ckeditor5/issues/6265 + it( 'on elements on different but intersecting "levels"', () => { + john.setData( '[Foo
]' ); + kate.setData( '[Foo]
' ); + + john.setAttribute( 'attr', 'foo' ); + kate.setAttribute( 'attr', 'bar' ); + + syncClients(); + + expectClients( + 'Foo
' + ); + } ); } ); describe( 'by insert', () => { diff --git a/tests/model/operation/transform/utils.js b/tests/model/operation/transform/utils.js index ca914ac6b..0daf7bf31 100644 --- a/tests/model/operation/transform/utils.js +++ b/tests/model/operation/transform/utils.js @@ -12,6 +12,7 @@ import Typing from '@ckeditor/ckeditor5-typing/src/typing'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting'; import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting'; +import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting'; import { getData, parse } from '../../../../src/dev-utils/model'; import { transformSets } from '../../../../src/model/operation/transform'; @@ -37,7 +38,7 @@ export class Client { // UndoEditing is needed for undo command. // Block plugins are needed for proper data serializing. // BoldEditing is needed for bold command. - plugins: [ Typing, Paragraph, ListEditing, UndoEditing, BlockQuoteEditing, HeadingEditing, BoldEditing ] + plugins: [ Typing, Paragraph, ListEditing, UndoEditing, BlockQuoteEditing, HeadingEditing, BoldEditing, TableEditing ] } ).then( editor => { this.editor = editor; this.document = editor.model.document;