diff --git a/docs/builds/guides/migration/migration-to-28.0.0.md b/docs/builds/guides/migration/migration-to-28.0.0.md index 154dd7668cd..db8d10eeb20 100644 --- a/docs/builds/guides/migration/migration-to-28.0.0.md +++ b/docs/builds/guides/migration/migration-to-28.0.0.md @@ -14,4 +14,10 @@ Listed below are the most important changes that require your attention when upg Prior to version 28.0.0 inserting a table into another table was not allowed. -If you wish to bring back this restriction see {@link features/table#disallowing-nesting-tables "Disallowing nesting tables"}. \ No newline at end of file +If you wish to bring back this restriction see {@link features/table#disallowing-nesting-tables "Disallowing nesting tables"}. + +## Disallowing nesting block quotes + +Prior to version 28.0.0 inserting block quote into a block quote was not allowed. + +If you wish to bring back the old behavior see {@link features/block-quote#disallowing-nesting-block-quotes "Disallowing nesting block quotes"}. diff --git a/packages/ckeditor5-block-quote/docs/features/block-quote.md b/packages/ckeditor5-block-quote/docs/features/block-quote.md index 967b9818cb9..22b885a0f78 100644 --- a/packages/ckeditor5-block-quote/docs/features/block-quote.md +++ b/packages/ckeditor5-block-quote/docs/features/block-quote.md @@ -48,6 +48,33 @@ ClassicEditor Read more about {@link builds/guides/integration/installing-plugins installing plugins}. +## Disallowing nesting block quotes + +By default, the editor supports a block quote inserted into another block quote. + +In order to disallow nesting block quotes you need to register an additional schema rule. It needs to be added before the data gets loaded into the editor, hence it is best to implement it as a plugin: + +```js +function DisallowNestingBlockQuotes( editor ) { + editor.model.schema.addChildCheck( ( context, childDefinition ) => { + if ( context.endsWith( 'blockQuote' ) && childDefinition.name == 'blockQuote' ) { + return false; + } + } ); +} + +// Pass it via config.extraPlugins or config.plugins: + +ClassicEditor + .create( document.querySelector( '#editor' ), { + extraPlugins: [ DisallowNestingBlockQuotes ], + + // The rest of the config. + } ) + .then( ... ) + .catch( ... ); +``` + ## Common API The {@link module:block-quote/blockquote~BlockQuote} plugin registers: diff --git a/packages/ckeditor5-block-quote/src/blockquoteediting.js b/packages/ckeditor5-block-quote/src/blockquoteediting.js index fea393d9b23..eb74d7f6941 100644 --- a/packages/ckeditor5-block-quote/src/blockquoteediting.js +++ b/packages/ckeditor5-block-quote/src/blockquoteediting.js @@ -49,13 +49,6 @@ export default class BlockQuoteEditing extends Plugin { allowContentOf: '$root' } ); - // Disallow blockQuote in blockQuote. - schema.addChildCheck( ( ctx, childDef ) => { - if ( ctx.endsWith( 'blockQuote' ) && childDef.name == 'blockQuote' ) { - return false; - } - } ); - editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } ); // Postfixer which cleans incorrect model states connected with block quotes. @@ -77,13 +70,12 @@ export default class BlockQuoteEditing extends Plugin { return true; } else if ( element.is( 'element', '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. + // Added a blockQuote in incorrect place. 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. + // Just added an element. Check that all children meet the scheme rules. const range = writer.createRangeIn( element ); for ( const child of range.getItems() ) { diff --git a/packages/ckeditor5-block-quote/tests/blockquoteediting.js b/packages/ckeditor5-block-quote/tests/blockquoteediting.js index 68531660bde..2697bb2f654 100644 --- a/packages/ckeditor5-block-quote/tests/blockquoteediting.js +++ b/packages/ckeditor5-block-quote/tests/blockquoteediting.js @@ -48,8 +48,8 @@ describe( 'BlockQuoteEditing', () => { expect( model.schema.checkChild( [ '$root', 'blockQuote' ], 'paragraph' ) ).to.be.true; } ); - it( 'does not allow for blockQuote in blockQuote', () => { - expect( model.schema.checkChild( [ '$root', 'blockQuote' ], 'blockQuote' ) ).to.be.false; + it( 'allows for blockQuote in blockQuote', () => { + expect( model.schema.checkChild( [ '$root', 'blockQuote' ], 'blockQuote' ) ).to.be.true; } ); it( 'does not break when checking an unregisterd item', () => { @@ -102,7 +102,7 @@ describe( 'BlockQuoteEditing', () => { expect( editor.getData( { trim: 'none' } ) ).to.equal( '
' ); // Autoparagraphed. } ); - it( 'should unwrap a blockQuote if it was inserted into another blockQuote', () => { + it( 'should not unwrap a blockQuote if it was inserted into another blockQuote', () => { setModelData( model, '
' ); model.change( writer => { @@ -115,10 +115,10 @@ describe( 'BlockQuoteEditing', () => { writer.insert( bq, root.getChild( 0 ), 1 ); // Insert afterFoo
Foo
. } ); - expect( editor.getData() ).to.equal( '' ); + expect( editor.getData() ).to.equal( 'Foo
Bar
' ); } ); - it( 'should unwrap nested blockQuote if it was wrapped into another blockQuote', () => { + it( 'should not unwrap nested blockQuote if it was wrapped into another blockQuote', () => { setModelData( model, 'Foo
Bar
Foo
' ); + expect( editor.getData() ).to.equal( 'Foo
Bar
' ); } ); it( 'postfixer should do nothing on attribute change', () => { @@ -143,4 +143,44 @@ describe( 'BlockQuoteEditing', () => { expect( editor.getData() ).to.equal( 'Foo
Bar
' ); } ); + + describe( 'nested blockQuote forbidden by custom rule', () => { + // Nested block quotes are supported since https://github.com/ckeditor/ckeditor5/issues/9210, so let's check + // if the editor will not blow up in case nested block quotes are forbidden by custom scheme rule. + beforeEach( () => { + model.schema.addChildCheck( ( ctx, childDef ) => { + if ( ctx.endsWith( 'blockQuote' ) && childDef.name == 'blockQuote' ) { + return false; + } + } ); + } ); + + it( 'should unwrap a blockQuote if it was inserted into another blockQuote', () => { + setModelData( model, 'Foo
' ); + + model.change( writer => { + const root = model.document.getRoot(); + const bq = writer.createElement( 'blockQuote' ); + const p = writer.createElement( 'paragraph' ); + + writer.insertText( 'Bar', p, 0 ); //Foo
Bar
. + writer.insert( p, bq, 0 ); //. + writer.insert( bq, root.getChild( 0 ), 1 ); // Insert afterBar
Foo
. + } ); + + expect( editor.getData() ).to.equal( '' ); + } ); + + it( 'should unwrap nested blockQuote if it was wrapped into another blockQuote', () => { + setModelData( model, 'Foo
Bar
Foo
' ); + } ); + } ); } ); diff --git a/packages/ckeditor5-block-quote/tests/manual/blockquote.html b/packages/ckeditor5-block-quote/tests/manual/blockquote.html index 5bde4c10c2e..b8ea9f6b252 100644 --- a/packages/ckeditor5-block-quote/tests/manual/blockquote.html +++ b/packages/ckeditor5-block-quote/tests/manual/blockquote.html @@ -15,4 +15,11 @@Foo
Bar
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+++Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris.
+++Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
diff --git a/packages/ckeditor5-block-quote/tests/manual/blockquote.md b/packages/ckeditor5-block-quote/tests/manual/blockquote.md index d36ddc8cf97..13392bf0adf 100644 --- a/packages/ckeditor5-block-quote/tests/manual/blockquote.md +++ b/packages/ckeditor5-block-quote/tests/manual/blockquote.md @@ -8,4 +8,5 @@ Check block quote related behaviors: * Backspace, * undo/redo, * applying headings and lists, -* stability when used with nested lists. +* stability when used with nested lists, +* stability when used with nested block quotes. diff --git a/packages/ckeditor5-block-quote/tests/manual/blockquotenonesting.html b/packages/ckeditor5-block-quote/tests/manual/blockquotenonesting.html new file mode 100644 index 00000000000..573ba51c487 --- /dev/null +++ b/packages/ckeditor5-block-quote/tests/manual/blockquotenonesting.html @@ -0,0 +1,20 @@ + + +Nested block quotes (in the data):
+ +++Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris.
+++Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+++Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+
` structures: +For instance, to disallow nested `` structures, you can define such a listener: ```js schema.addChildCheck( ( context, childDefinition ) => { diff --git a/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js b/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js index f994115f07d..40a1828c680 100644 --- a/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js +++ b/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js @@ -335,7 +335,9 @@ describe( 'transform', () => { expectClients( '' + '' );A ' + - 'B ' + + '' + + '' + 'B ' + + 'C ' + '