diff --git a/src/converters.js b/src/converters.js index 6f78eee..3be0397 100644 --- a/src/converters.js +++ b/src/converters.js @@ -365,8 +365,14 @@ export function viewModelConverter( evt, data, consumable, conversionApi ) { const type = data.input.parent && data.input.parent.name == 'ol' ? 'numbered' : 'bulleted'; writer.setAttribute( 'type', type, listItem ); - // 3. Handle `
  • ` children. - data.context.push( listItem ); + let alteredContext = false; + + // See https://github.com/ckeditor/ckeditor5-list/issues/87 + if ( data.context[ data.context.length - 1 ].name != 'listItem' ) { + // 3. Handle `
  • ` children. + data.context.push( listItem ); + alteredContext = true; + } // `listItem`s created recursively should have bigger indent. data.indent++; @@ -394,7 +400,9 @@ export function viewModelConverter( evt, data, consumable, conversionApi ) { } data.indent--; - data.context.pop(); + if ( alteredContext ) { + data.context.pop(); + } data.output = items; } diff --git a/src/listcommand.js b/src/listcommand.js index aca0609..071e77e 100644 --- a/src/listcommand.js +++ b/src/listcommand.js @@ -8,7 +8,6 @@ */ import Command from '@ckeditor/ckeditor5-core/src/command'; -import Position from '@ckeditor/ckeditor5-engine/src/model/position'; import first from '@ckeditor/ckeditor5-utils/src/first'; /** @@ -308,9 +307,5 @@ function _fixType( blocks, isBackward, lowestIndent ) { // @param {module:engine/model/schema~Schema} schema The schema of the document. // @returns {Boolean} function checkCanBecomeListItem( block, schema ) { - return schema.check( { - name: 'listItem', - attributes: [ 'type', 'indent' ], - inside: Position.createBefore( block ) - } ); + return schema.checkChild( block.parent, 'listItem' ) && !schema.isObject( block ); } diff --git a/src/listengine.js b/src/listengine.js index 7a3ab5e..fcae48d 100644 --- a/src/listengine.js +++ b/src/listengine.js @@ -50,18 +50,13 @@ export default class ListEngine extends Plugin { const editor = this.editor; // Schema. - // Note: in case `$block` will be ever allowed in `listItem`, keep in mind that this feature + // Note: in case `$block` will ever be allowed in `listItem`, keep in mind that this feature // uses `Selection#getSelectedBlocks()` without any additional processing to obtain all selected list items. // If there are blocks allowed inside list item, algorithms using `getSelectedBlocks()` will have to be modified. - const schema = editor.model.schema; - schema.registerItem( 'listItem', '$block' ); - schema.allow( { - name: 'listItem', - inside: '$root', - coinside: '$root', - attributes: [ 'type', 'indent' ] + editor.model.schema.register( 'listItem', { + inheritAllFrom: '$block', + allowAttributes: [ 'type', 'indent' ] } ); - schema.requireAttributes( 'listItem', [ 'type', 'indent' ] ); // Converters. const data = editor.data; diff --git a/tests/indentcommand.js b/tests/indentcommand.js index 6648aaa..641e79a 100644 --- a/tests/indentcommand.js +++ b/tests/indentcommand.js @@ -21,12 +21,11 @@ describe( 'IndentCommand', () => { doc = model.document; root = doc.createRoot(); - model.schema.registerItem( 'listItem', '$block' ); - model.schema.registerItem( 'paragraph', '$block' ); - - model.schema.allow( { name: '$block', inside: '$root' } ); - model.schema.allow( { name: 'listItem', attributes: [ 'type', 'indent' ], inside: '$root' } ); - model.schema.allow( { name: 'paragraph', inside: '$root' } ); + model.schema.register( 'listItem', { + inheritAllFrom: '$block', + allowAttributes: [ 'type', 'indent' ] + } ); + model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); setData( model, @@ -117,7 +116,7 @@ describe( 'IndentCommand', () => { // Edge case but may happen that some other blocks will also use the indent attribute // and before we fixed it the command was enabled in such a case. it( 'should be false if selection starts in a paragraph with indent attribute', () => { - model.schema.allow( { name: 'paragraph', attributes: [ 'indent' ], inside: '$root' } ); + model.schema.extend( 'paragraph', { allowAttributes: 'indent' } ); setData( model, 'ab[]' ); diff --git a/tests/listcommand.js b/tests/listcommand.js index c4843c5..d33bfdb 100644 --- a/tests/listcommand.js +++ b/tests/listcommand.js @@ -23,14 +23,24 @@ describe( 'ListCommand', () => { command = new ListCommand( editor, 'bulleted' ); - model.schema.registerItem( 'listItem', '$block' ); - model.schema.registerItem( 'paragraph', '$block' ); - model.schema.registerItem( 'widget', '$block' ); + model.schema.register( 'listItem', { + inheritAllFrom: '$block', + allowAttributes: [ 'type', 'indent' ] + } ); + model.schema.register( 'paragraph', { + inheritAllFrom: '$block', + allowIn: 'widget' + } ); + model.schema.register( 'widget', { inheritAllFrom: '$block' } ); - model.schema.allow( { name: '$block', inside: '$root' } ); - model.schema.allow( { name: 'paragraph', inside: 'widget' } ); - model.schema.allow( { name: 'listItem', attributes: [ 'type', 'indent' ], inside: '$root' } ); - model.schema.disallow( { name: 'listItem', attributes: [ 'type', 'indent' ], inside: 'widget' } ); + model.schema.on( 'checkChild', ( evt, args ) => { + const def = model.schema.getDefinition( args[ 1 ] ); + + if ( args[ 0 ].endsWith( 'widget' ) && def.name == 'listItem' ) { + evt.stop(); + evt.return = false; + } + } ); setData( model, @@ -247,13 +257,12 @@ describe( 'ListCommand', () => { } ); // https://github.com/ckeditor/ckeditor5-list/issues/62 - it( 'should not rename blocks which cannot become listItems', () => { - model.schema.registerItem( 'restricted' ); - model.schema.allow( { name: 'restricted', inside: '$root' } ); - model.schema.disallow( { name: 'paragraph', inside: 'restricted' } ); + it( 'should not rename blocks which cannot become listItems (list item is not allowed in their parent)', () => { + model.schema.register( 'restricted' ); + model.schema.extend( 'restricted', { allowIn: '$root' } ); - model.schema.registerItem( 'fooBlock', '$block' ); - model.schema.allow( { name: 'fooBlock', inside: 'restricted' } ); + model.schema.register( 'fooBlock', { inheritAllFrom: '$block' } ); + model.schema.extend( 'fooBlock', { allowIn: 'restricted' } ); setData( model, @@ -271,6 +280,29 @@ describe( 'ListCommand', () => { ); } ); + it( 'should not rename blocks which cannot become listItems (block is an object)', () => { + model.schema.register( 'image', { + isBlock: true, + isObject: true, + allowIn: '$root' + } ); + + setData( + model, + 'a[bc' + + '' + + 'de]f' + ); + + command.execute(); + + expect( getData( model ) ).to.equal( + 'a[bc' + + '' + + 'de]f' + ); + } ); + it( 'should rename closest block to listItem and set correct attributes', () => { // From first paragraph to second paragraph. // Command value=false, we are turning on list items. diff --git a/tests/listengine.js b/tests/listengine.js index b253ebf..97cb81c 100644 --- a/tests/listengine.js +++ b/tests/listengine.js @@ -51,17 +51,16 @@ describe( 'ListEngine', () => { } ); it( 'should set proper schema rules', () => { - expect( model.schema.hasItem( 'listItem' ) ); - expect( model.schema.itemExtends( 'listItem', '$block' ) ); + expect( model.schema.isRegistered( 'listItem' ) ); + expect( model.schema.isBlock( 'listItem' ) ); - expect( model.schema.check( { name: '$inline', inside: 'listItem' } ) ).to.be.true; - expect( model.schema.check( { name: 'listItem', inside: 'listItem' } ) ).to.be.false; - expect( model.schema.check( { name: '$block', inside: 'listItem' } ) ).to.be.false; + expect( model.schema.checkChild( [ '$root' ], 'listItem' ) ).to.be.true; + expect( model.schema.checkChild( [ '$root', 'listItem' ], '$text' ) ).to.be.true; + expect( model.schema.checkChild( [ '$root', 'listItem' ], 'listItem' ) ).to.be.false; + expect( model.schema.checkChild( [ '$root', 'listItem' ], '$block' ) ).to.be.false; - expect( model.schema.check( { name: 'listItem', inside: '$root' } ) ).to.be.false; - expect( model.schema.check( { name: 'listItem', inside: '$root', attributes: [ 'indent' ] } ) ).to.be.false; - expect( model.schema.check( { name: 'listItem', inside: '$root', attributes: [ 'type' ] } ) ).to.be.false; - expect( model.schema.check( { name: 'listItem', inside: '$root', attributes: [ 'indent', 'type' ] } ) ).to.be.true; + expect( model.schema.checkAttribute( [ '$root', 'listItem' ], 'indent' ) ).to.be.true; + expect( model.schema.checkAttribute( [ '$root', 'listItem' ], 'type' ) ).to.be.true; } ); describe( 'commands', () => {