Skip to content
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

Introduce <tableColumnGroup> and <tableColumn> elements to store columns width information #13328

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0ed0d5c
Introduce `<tableColumnGroup>` and `<tableColumn>` elements to store …
filipsobol Jan 18, 2023
c387051
Fix default slot in table
filipsobol Jan 25, 2023
1c82f3b
Fix failing table tests
filipsobol Jan 25, 2023
384d0d4
Merge remote-tracking branch 'origin/master' into ck/11479-add-tableC…
filipsobol Jan 25, 2023
eac6746
Update upcasting method in TableColumnResize
filipsobol Jan 26, 2023
0cd3b30
Add % sign to `columnWidth` attribute values
filipsobol Jan 26, 2023
0f850f4
Downcast 'ck-table-resized' table class
filipsobol Jan 26, 2023
3d749e6
Fix tests
filipsobol Jan 26, 2023
cbb4e02
Fix reverting changes when read-only mode is enabled when resizing table
filipsobol Jan 27, 2023
30588d7
ElementToStructure should not override attributeToAttribute converter…
niegowski Jan 30, 2023
9e0bd63
Fix tests for tablecolumnresize
filipsobol Jan 30, 2023
773e7cb
Added tests.
niegowski Jan 30, 2023
cda018e
Improve code coverage
filipsobol Jan 31, 2023
bfecd20
Should inject re-conversion change item just before changes applied t…
niegowski Jan 31, 2023
7ade964
Merge branch 'ck/11479-add-tableColumnGroup-and-tableColumn-elements-…
filipsobol Feb 1, 2023
405b307
Remove no longer used fixer
filipsobol Feb 1, 2023
6078480
Add helpers for accessing table columns information
filipsobol Feb 1, 2023
4263b94
Merge two TableColumnResize commands and fix tests
filipsobol Feb 1, 2023
bfa4a7f
Add tests to cover new table helpers
filipsobol Feb 1, 2023
f0c137c
Move TableColumnResize helpers into TableUtils
filipsobol Feb 1, 2023
5c71a3d
Merge branch 'master' into ck/11479-add-tableColumnGroup-and-tableCol…
niegowski Feb 6, 2023
bfccda7
The colgroup and col elements should be converted separately so GHS c…
niegowski Feb 6, 2023
8837b00
Add test in GHS checking if attributes in colgroup and col are not st…
filipsobol Feb 8, 2023
5356353
Fix table downcast for custom slots with `after` and `before` position
filipsobol Feb 8, 2023
499547c
Move utils specific to column resize to TableColumnResizeEditing plugin
filipsobol Feb 8, 2023
92b470e
Fix tests and move util to better location
filipsobol Feb 8, 2023
f325938
Move tablecolumnresize utils to `utils.js` to not require TableColum…
filipsobol Feb 8, 2023
2f9d641
Update tests to take better use of model writer
filipsobol Feb 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion packages/ckeditor5-engine/src/conversion/downcasthelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2450,8 +2450,24 @@ function createChangeReducer( model: NormalizedModelElementConfig ) {
data.reconvertedElements.add( node );

const position = ModelPosition._createBefore( node );
let changeIndex = reducedChanges.length;

reducedChanges.push( {
// We need to insert remove+reinsert before any other change on and inside the re-converted element.
// This is important because otherwise we would remove element that had already been modified by the previous change.
// Note that there could be some element removed before the re-converted element, so we must not break this behavior.
for ( let i = reducedChanges.length - 1; i >= 0; i-- ) {
const change = reducedChanges[ i ];
const changePosition = change.type == 'attribute' ? change.range.start : change.position;
const positionRelation = changePosition.compareWith( position );

if ( positionRelation == 'before' || change.type == 'remove' && positionRelation == 'same' ) {
break;
}

changeIndex = i;
}

reducedChanges.splice( changeIndex, 0, {
type: 'remove',
name: ( node as ModelElement ).name,
position,
Expand Down
322 changes: 322 additions & 0 deletions packages/ckeditor5-engine/tests/conversion/downcasthelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,167 @@ describe( 'DowncastHelpers', () => {
expect( spy.called ).to.be.true;
} );

it( 'should convert attribute change and add a child at the same time (separate converters)', () => {
model.schema.extend( 'simpleBlock', { allowAttributes: 'someOther' } );
downcastHelpers.attributeToAttribute( { model: 'someOther', view: 'data-other' } );

setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore, textBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.setAttribute( 'someOther', 'foo', modelRoot.getChild( 0 ) );
} );

const [ viewAfter, paraAfter, textAfter ] = getNodes();

expectResult( '<div data-other="foo"><p>foo</p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( textAfter, 'text' ).to.equal( textBefore );
expect( spy.called ).to.be.true;
} );

it( 'should convert attribute change, remove element before, and add a child at the same time (separate)', () => {
model.schema.extend( 'simpleBlock', { allowAttributes: 'someOther' } );
downcastHelpers.attributeToAttribute( { model: 'someOther', view: 'data-other' } );

setModelData( model, '<paragraph></paragraph><simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore, textBefore ] = getNodes( 1 );

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.remove( modelRoot.getChild( 0 ) );
writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.setAttribute( 'someOther', 'foo', modelRoot.getChild( 0 ) );
} );

const [ viewAfter, paraAfter, textAfter ] = getNodes();

expectResult( '<div data-other="foo"><p>foo</p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( textAfter, 'text' ).to.equal( textBefore );
expect( spy.called ).to.be.true;
} );

it( 'should reconvert before content modifications (some deeply nested node added)', () => {
setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore, textBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.insertText( 'abc', modelRoot.getChild( 0 ).getChild( 0 ), 'end' );
} );

const [ viewAfter, paraAfter, textAfter ] = getNodes();

expectResult( '<div><p>fooabc</p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( textAfter, 'text' ).to.equal( textBefore );
expect( spy.called ).to.be.true;
} );

it( 'should reconvert before content modifications (some deeply nested node removed)', () => {
setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.remove( modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ) );
} );

const [ viewAfter, paraAfter ] = getNodes();

expectResult( '<div><p></p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( spy.called ).to.be.true;
} );

it( 'should reconvert before content modifications (with element added before)', () => {
setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.remove( modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ) );
writer.insertElement( 'paragraph', modelRoot, 0 );
} );

const [ viewAfter, paraAfter ] = getNodes( 1 );

expectResult( '<p></p><div><p></p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( spy.called ).to.be.true;
} );

it( 'should convert on removing a child', () => {
setModelData( model,
'<simpleBlock><paragraph>foo</paragraph><paragraph>bar</paragraph></simpleBlock>' );
Expand Down Expand Up @@ -1502,6 +1663,167 @@ describe( 'DowncastHelpers', () => {
expect( spy.called ).to.be.true;
} );

it( 'should convert attribute change and add a child at the same time (separate converters)', () => {
model.schema.extend( 'simpleBlock', { allowAttributes: 'someOther' } );
downcastHelpers.attributeToAttribute( { model: 'someOther', view: 'data-other' } );

setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore, textBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.setAttribute( 'someOther', 'foo', modelRoot.getChild( 0 ) );
} );

const [ viewAfter, paraAfter, textAfter ] = getNodes();

expectResult( '<div data-other="foo"><p>foo</p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( textAfter, 'text' ).to.equal( textBefore );
expect( spy.called ).to.be.true;
} );

it( 'should convert attribute change, remove element before, and add a child at the same time (separate)', () => {
model.schema.extend( 'simpleBlock', { allowAttributes: 'someOther' } );
downcastHelpers.attributeToAttribute( { model: 'someOther', view: 'data-other' } );

setModelData( model, '<paragraph></paragraph><simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore, textBefore ] = getNodes( 1 );

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.remove( modelRoot.getChild( 0 ) );
writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.setAttribute( 'someOther', 'foo', modelRoot.getChild( 0 ) );
} );

const [ viewAfter, paraAfter, textAfter ] = getNodes();

expectResult( '<div data-other="foo"><p>foo</p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( textAfter, 'text' ).to.equal( textBefore );
expect( spy.called ).to.be.true;
} );

it( 'should reconvert before content modifications (some deeply nested node added)', () => {
setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore, textBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.insertText( 'abc', modelRoot.getChild( 0 ).getChild( 0 ), 'end' );
} );

const [ viewAfter, paraAfter, textAfter ] = getNodes();

expectResult( '<div><p>fooabc</p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( textAfter, 'text' ).to.equal( textBefore );
expect( spy.called ).to.be.true;
} );

it( 'should reconvert before content modifications (some deeply nested node removed)', () => {
setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.remove( modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ) );
} );

const [ viewAfter, paraAfter ] = getNodes();

expectResult( '<div><p></p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( spy.called ).to.be.true;
} );

it( 'should reconvert before content modifications (with element added before)', () => {
setModelData( model, '<simpleBlock><paragraph>foo</paragraph></simpleBlock>' );

const spy = sinon.spy();

controller.downcastDispatcher.on( 'insert:simpleBlock', ( evt, data ) => {
expect( data ).to.have.property( 'reconversion' ).to.be.true;
spy();
} );

const [ viewBefore, paraBefore ] = getNodes();

model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
const text = writer.createText( 'bar' );

writer.insert( paragraph, modelRoot.getChild( 0 ), 1 );
writer.insert( text, paragraph, 0 );
writer.remove( modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ) );
writer.insertElement( 'paragraph', modelRoot, 0 );
} );

const [ viewAfter, paraAfter ] = getNodes( 1 );

expectResult( '<p></p><div><p></p><p>bar</p></div>' );

expect( viewAfter, 'simpleBlock' ).to.not.equal( viewBefore );
expect( paraAfter, 'para' ).to.equal( paraBefore );
expect( spy.called ).to.be.true;
} );

it( 'should convert on removing a child', () => {
setModelData( model,
'<simpleBlock><paragraph>foo</paragraph><paragraph>bar</paragraph></simpleBlock>' );
Expand Down
8 changes: 8 additions & 0 deletions packages/ckeditor5-html-support/src/schemadefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export default {
model: 'tableCell',
view: 'th'
},
{
model: 'tableColumnGroup',
view: 'colgroup'
},
{
model: 'tableColumn',
view: 'col'
},
{
model: 'caption',
view: 'caption'
Expand Down
Loading