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 #25 from ckeditor/t/ckeditor5-engine/1563
Browse files Browse the repository at this point in the history
Added: Postfixer cleaning incorrect blockquotes.
  • Loading branch information
Piotr Jasiun authored Oct 1, 2018
2 parents 71edad5 + 5ee3158 commit fcb00c0
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 24 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"@ckeditor/ckeditor5-list": "^11.0.1",
"@ckeditor/ckeditor5-paragraph": "^10.0.2",
"@ckeditor/ckeditor5-typing": "^11.0.0",
"@ckeditor/ckeditor5-engine": "^10.2.0",
"@ckeditor/ckeditor5-basic-styles": "^10.0.2",
"eslint": "^5.5.0",
"eslint-config-ckeditor5": "^1.0.7",
"husky": "^0.14.3",
Expand Down
54 changes: 54 additions & 0 deletions src/blockquoteediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Range from '@ckeditor/ckeditor5-engine/src/model/range';
import Position from '@ckeditor/ckeditor5-engine/src/model/position';

import BlockQuoteCommand from './blockquotecommand';

/**
Expand Down Expand Up @@ -40,6 +43,57 @@ export default class BlockQuoteEditing extends Plugin {
} );

editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );

// Postfixer which cleans incorrect model states connected with block quotes.
editor.model.document.registerPostFixer( writer => {
const changes = editor.model.document.differ.getChanges();

for ( const entry of changes ) {
if ( entry.type == 'insert' ) {
const element = entry.position.nodeAfter;

if ( !element ) {
// We are inside a text node.
continue;
}

if ( element.is( 'blockQuote' ) && element.isEmpty ) {
// Added an empty blockQuote - remove it.
writer.remove( element );

return true;
} else if ( element.is( 'blockQuote' ) && !schema.checkChild( entry.position, element ) ) {
// Added a blockQuote in incorrect place - most likely inside another blockQuote. Unwrap it
// so the content inside is not lost.
writer.unwrap( element );

return true;
} else if ( element.is( 'element' ) ) {
// Just added an element. Check its children to see if there are no nested blockQuotes somewhere inside.
const range = Range.createIn( element );

for ( const child of range.getItems() ) {
if ( child.is( 'blockQuote' ) && !schema.checkChild( Position.createBefore( child ), child ) ) {
writer.unwrap( child );

return true;
}
}
}
} else if ( entry.type == 'remove' ) {
const parent = entry.position.parent;

if ( parent.is( 'blockQuote' ) && parent.isEmpty ) {
// Something got removed and now blockQuote is empty. Remove the blockQuote as well.
writer.remove( parent );

return true;
}
}
}

return false;
} );
}

/**
Expand Down
54 changes: 33 additions & 21 deletions tests/blockquotecommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,19 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote>' +
'<heading>a[bc</heading>' +
'<paragraph>x]x</paragraph>' +
'<heading>abc</heading>' +
'<paragraph>[x]x</paragraph>' +
'<paragraph>yy</paragraph>' +
'</blockQuote>' +
'<paragraph>def</paragraph>'
);

// Selection incorrectly trimmed.
expect( getViewData( editor.editing.view ) ).to.equal(
'<blockquote><h>a{bc</h><p>x}x</p><p>yy</p></blockquote><p>def</p>'
'<blockquote><h>abc</h><p>{x}x</p><p>yy</p></blockquote><p>def</p>'
);
} );

Expand Down Expand Up @@ -258,17 +260,19 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote>' +
'<heading>a[bc</heading>' +
'<heading>abc</heading>' +
'<paragraph>def</paragraph>' +
'<paragraph>x]x</paragraph>' +
'<paragraph>[x]x</paragraph>' +
'</blockQuote>' +
'<paragraph>ghi</paragraph>'
);

// Selection incorrectly trimmed.
expect( getViewData( editor.editing.view ) ).to.equal(
'<blockquote><h>a{bc</h><p>def</p><p>x}x</p></blockquote><p>ghi</p>'
'<blockquote><h>abc</h><p>def</p><p>{x}x</p></blockquote><p>ghi</p>'
);
} );

Expand All @@ -282,18 +286,20 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote>' +
'<paragraph>a[bc</paragraph>' +
'<paragraph>abc</paragraph>' +
'</blockQuote>' +
'<widget>xx</widget>' +
'[<widget>xx</widget>' +
'<blockQuote>' +
'<paragraph>de]f</paragraph>' +
'</blockQuote>'
);

// Selection incorrectly trimmed.
expect( getViewData( editor.editing.view ) ).to.equal(
'<blockquote><p>a{bc</p></blockquote><widget>xx</widget><blockquote><p>de}f</p></blockquote>'
'<blockquote><p>abc</p></blockquote>[<widget>xx</widget><blockquote><p>de}f</p></blockquote>'
);
} );

Expand All @@ -310,17 +316,19 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote><paragraph>a[bc</paragraph></blockQuote>' +
'<widget>xx</widget>' +
'<blockQuote><paragraph>abc</paragraph></blockQuote>' +
'[<widget>xx</widget>' +
'<blockQuote><paragraph>def</paragraph><paragraph>ghi</paragraph></blockQuote>' +
'<widget>yy</widget>' +
'<blockQuote><paragraph>jk]l</paragraph></blockQuote>'
);

// Selection incorrectly trimmed.
expect( getViewData( editor.editing.view ) ).to.equal(
'<blockquote><p>a{bc</p></blockquote>' +
'<widget>xx</widget>' +
'<blockquote><p>abc</p></blockquote>' +
'[<widget>xx</widget>' +
'<blockquote><p>def</p><p>ghi</p></blockquote>' +
'<widget>yy</widget>' +
'<blockquote><p>jk}l</p></blockquote>'
Expand All @@ -341,26 +349,28 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<paragraph>x</paragraph>' +
'<blockQuote>' +
'<paragraph>a[bc</paragraph>' +
'<paragraph>abc</paragraph>' +
'<paragraph>def</paragraph>' +
'<paragraph>ghi</paragraph>' +
'<paragraph>jkl</paragraph>' +
'<paragraph>mn]o</paragraph>' +
'<paragraph>[mn]o</paragraph>' +
'</blockQuote>' +
'<paragraph>y</paragraph>'
);

// Selection incorrectly trimmed.
expect( getViewData( editor.editing.view ) ).to.equal(
'<p>x</p>' +
'<blockquote>' +
'<p>a{bc</p>' +
'<p>abc</p>' +
'<p>def</p>' +
'<p>ghi</p>' +
'<p>jkl</p>' +
'<p>mn}o</p>' +
'<p>{mn}o</p>' +
'</blockquote>' +
'<p>y</p>'
);
Expand All @@ -386,11 +396,12 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote>' +
'<paragraph>a[bc</paragraph>' +
'<paragraph>abc</paragraph>' +
'</blockQuote>' +
'<fooBlock>xx</fooBlock>' +
'<fooBlock>[xx</fooBlock>' +
'<blockQuote>' +
'<paragraph>de]f</paragraph>' +
'</blockQuote>'
Expand All @@ -417,11 +428,12 @@ describe( 'BlockQuoteCommand', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote>' +
'<paragraph>a[bc</paragraph>' +
'<paragraph>abc</paragraph>' +
'</blockQuote>' +
'<fooWrapper><fooBlock>xx</fooBlock></fooWrapper>' +
'<fooWrapper><fooBlock>[xx</fooBlock></fooWrapper>' +
'<blockQuote>' +
'<paragraph>de]f</paragraph>' +
'</blockQuote>'
Expand Down
66 changes: 65 additions & 1 deletion tests/blockquoteediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import BlockQuoteEditing from '../src/blockquoteediting';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';

import Range from '@ckeditor/ckeditor5-engine/src/model/range';

import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
Expand All @@ -18,7 +21,7 @@ describe( 'BlockQuoteEditing', () => {
beforeEach( () => {
return VirtualTestEditor
.create( {
plugins: [ BlockQuoteEditing, Paragraph ]
plugins: [ BlockQuoteEditing, Paragraph, BoldEditing ]
} )
.then( newEditor => {
editor = newEditor;
Expand Down Expand Up @@ -77,4 +80,65 @@ describe( 'BlockQuoteEditing', () => {
expect( editor.getData() ).to.equal( '<blockquote><ul><li>xx</li></ul></blockquote>' );
} );
} );

it( 'should remove empty blockQuote elements', () => {
setModelData( model, '<blockQuote></blockQuote><paragraph>Foo</paragraph>' );

expect( editor.getData() ).to.equal( '<p>Foo</p>' );
} );

it( 'should remove blockQuotes which became empty', () => {
setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote>' );

model.change( writer => {
const root = model.document.getRoot();
const bq = root.getChild( 0 );

writer.remove( Range.createIn( bq ) );
} );

expect( editor.getData() ).to.equal( '<p>&nbsp;</p>' ); // Autoparagraphed.
} );

it( 'should unwrap a blockQuote if it was inserted into another blockQuote', () => {
setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote>' );

model.change( writer => {
const root = model.document.getRoot();
const bq = writer.createElement( 'blockQuote' );
const p = writer.createElement( 'paragraph' );

writer.insertText( 'Bar', p, 0 ); // <p>Bar</p>.
writer.insert( p, bq, 0 ); // <blockquote><p>Bar</p></blockquote>.
writer.insert( bq, root.getChild( 0 ), 1 ); // Insert after <p>Foo</p>.
} );

expect( editor.getData() ).to.equal( '<blockquote><p>Foo</p><p>Bar</p></blockquote>' );
} );

it( 'should unwrap nested blockQuote if it was wrapped into another blockQuote', () => {
setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote><paragraph>Bar</paragraph>' );

model.change( writer => {
const root = model.document.getRoot();

writer.wrap( Range.createIn( root ), 'blockQuote' );
} );

expect( editor.getData() ).to.equal( '<blockquote><p>Foo</p><p>Bar</p></blockquote>' );
} );

it( 'postfixer should do nothing on attribute change', () => {
// This is strictly a 100% CC test.
setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote>' );

model.change( writer => {
const root = model.document.getRoot();
const p = root.getChild( 0 ).getChild( 0 );

writer.setAttribute( 'bold', true, Range.createIn( p ) );
} );

expect( editor.getData() ).to.equal( '<blockquote><p><strong>Foo</strong></p></blockquote>' );
} );
} );
5 changes: 3 additions & 2 deletions tests/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,14 @@ describe( 'BlockQuote integration', () => {

editor.execute( 'blockQuote' );

// Selection incorrectly trimmed.
expect( getModelData( model ) ).to.equal(
'<blockQuote>' +
'<paragraph>fo[o</paragraph>' +
'<paragraph>foo</paragraph>' +
'<image src="foo.png">' +
'<caption>xxx</caption>' +
'</image>' +
'<paragraph>b]ar</paragraph>' +
'<paragraph>[b]ar</paragraph>' +
'</blockQuote>'
);
} );
Expand Down

0 comments on commit fcb00c0

Please sign in to comment.