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

Commit

Permalink
Merge pull request #20 from ckeditor/t/19
Browse files Browse the repository at this point in the history
Fix: Nested element structures next to widgets will be correctly removed when pressing <kbd>Backspace</kbd> or <kbd>Delete</kbd>. Closes #19.
  • Loading branch information
szymonkups authored Sep 12, 2017
2 parents e57bf4c + 1a19a89 commit 27ee848
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 4 deletions.
11 changes: 7 additions & 4 deletions src/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,15 @@ export default class Widget extends Plugin {

if ( objectElement ) {
modelDocument.enqueueChanges( () => {
const batch = modelDocument.batch();
let previousNode = modelSelection.anchor.parent;

// Remove previous element if empty.
const previousNode = modelSelection.anchor.parent;
while ( previousNode.isEmpty ) {
const nodeToRemove = previousNode;
previousNode = nodeToRemove.parent;

if ( previousNode.isEmpty ) {
const batch = modelDocument.batch();
batch.remove( previousNode );
batch.remove( nodeToRemove );
}

this._setSelectionOverElement( objectElement );
Expand Down
268 changes: 268 additions & 0 deletions tests/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ describe( 'Widget', () => {
doc.schema.allow( { name: 'editable', inside: 'widget' } );
doc.schema.allow( { name: 'editable', inside: '$root' } );

// Image feature.
doc.schema.registerItem( 'image' );
doc.schema.allow( { name: 'image', inside: '$root' } );
doc.schema.objects.add( 'image' );

// Block-quote feature.
doc.schema.registerItem( 'blockQuote' );
doc.schema.allow( { name: 'blockQuote', inside: '$root' } );
doc.schema.allow( { name: '$block', inside: 'blockQuote' } );

// Div element which helps nesting elements.
doc.schema.registerItem( 'div' );
doc.schema.allow( { name: 'div', inside: 'blockQuote' } );
doc.schema.allow( { name: 'div', inside: 'div' } );
doc.schema.allow( { name: 'paragraph', inside: 'div' } );

buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'paragraph' )
.toElement( 'p' );
Expand All @@ -66,6 +82,18 @@ describe( 'Widget', () => {
buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'editable' )
.toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );

buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'image' )
.toElement( 'img' );

buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'blockQuote' )
.toElement( 'blockquote' );

buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'div' )
.toElement( 'div' );
} );
} );

Expand Down Expand Up @@ -450,6 +478,134 @@ describe( 'Widget', () => {
sinon.assert.calledOnce( domEventDataMock.preventDefault );
sinon.assert.notCalled( keydownHandler );
} );

test(
'should remove the entire empty element if it is next to a widget',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.backspace,

'<paragraph>foo</paragraph>[<image></image>]<paragraph>foo</paragraph>'
);

test(
'should remove the entire empty element (deeper structure) if it is next to a widget',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.backspace,

'<paragraph>foo</paragraph>' +
'[<image></image>]' +
'<paragraph>foo</paragraph>'
);

test(
'should remove the entire empty element (deeper structure) if it is next to a widget (forward delete)',

'<paragraph>foo</paragraph>' +
'<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
'<image></image>' +
'<paragraph>foo</paragraph>',

keyCodes.delete,

'<paragraph>foo</paragraph>' +
'[<image></image>]' +
'<paragraph>foo</paragraph>'
);

test(
'should not remove the entire element which is not empty and the element is next to a widget',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote><paragraph>[]</paragraph><paragraph></paragraph></blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.backspace,

'<paragraph>foo</paragraph>' +
'[<image></image>]' +
'<blockQuote><paragraph></paragraph></blockQuote>' +
'<paragraph>foo</paragraph>'
);

test(
'should not remove the entire element which is not empty and the element is next to a widget (forward delete)',

'<paragraph>foo</paragraph>' +
'<blockQuote><paragraph>Foo</paragraph><paragraph>[]</paragraph></blockQuote>' +
'<image></image>' +
'<paragraph>foo</paragraph>',

keyCodes.delete,

'<paragraph>foo</paragraph>' +
'<blockQuote><paragraph>Foo</paragraph></blockQuote>' +
'[<image></image>]' +
'<paragraph>foo</paragraph>'
);

test(
'should not remove the entire element (deeper structure) which is not empty and the element is next to a widget',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>[]</paragraph>' +
'</div>' +
'</div>' +
'<paragraph></paragraph>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.backspace,

'<paragraph>foo</paragraph>' +
'[<image></image>]' +
'<blockQuote>' +
'<paragraph></paragraph>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>'
);

test(
'should do nothing if the nested element is not empty and the element is next to a widget',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>Foo[]</paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.backspace,

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>Foo[]</paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>'
);
} );

describe( 'arrows', () => {
Expand Down Expand Up @@ -743,6 +899,118 @@ describe( 'Widget', () => {
keyCodes.arrowright,
'<paragraph>[foo]</paragraph><widget></widget><paragraph>[bar]</paragraph>'
);

test(
'should work if selection is in nested element (left arrow)',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>[]</paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.arrowleft,

'<paragraph>foo</paragraph>' +
'[<image></image>]' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph></paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>'
);

test(
'should work if selection is in nested element (up arrow)',

'<paragraph>foo</paragraph>' +
'<image></image>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>[]</paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>',

keyCodes.arrowup,

'<paragraph>foo</paragraph>' +
'[<image></image>]' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph></paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<paragraph>foo</paragraph>'
);

test(
'should work if selection is in nested element (right arrow)',

'<paragraph>foo</paragraph>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>[]</paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<image></image>' +
'<paragraph>foo</paragraph>',

keyCodes.arrowright,

'<paragraph>foo</paragraph>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph></paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'[<image></image>]' +
'<paragraph>foo</paragraph>'
);

test(
'should work if selection is in nested element (down arrow)',

'<paragraph>foo</paragraph>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph>[]</paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'<image></image>' +
'<paragraph>foo</paragraph>',

keyCodes.arrowdown,

'<paragraph>foo</paragraph>' +
'<blockQuote>' +
'<div>' +
'<div>' +
'<paragraph></paragraph>' +
'</div>' +
'</div>' +
'</blockQuote>' +
'[<image></image>]' +
'<paragraph>foo</paragraph>'
);
} );

describe( 'Ctrl+A', () => {
Expand Down

0 comments on commit 27ee848

Please sign in to comment.