diff --git a/packages/ckeditor5-paragraph/src/insertparagraphcommand.ts b/packages/ckeditor5-paragraph/src/insertparagraphcommand.ts index be5780a6e10..db619a63fb2 100644 --- a/packages/ckeditor5-paragraph/src/insertparagraphcommand.ts +++ b/packages/ckeditor5-paragraph/src/insertparagraphcommand.ts @@ -8,7 +8,7 @@ */ import { Command, type Editor } from '@ckeditor/ckeditor5-core'; -import type { Element, Position } from '@ckeditor/ckeditor5-engine'; +import type { Element, Position, Writer } from '@ckeditor/ckeditor5-engine'; /** * The insert paragraph command. It inserts a new paragraph at a specific @@ -50,7 +50,7 @@ export default class InsertParagraphCommand extends Command { const model = this.editor.model; const attributes = options.attributes; - let position = options.position; + let position: Position | null = options.position; // Don't execute command if position is in non-editable place. if ( !model.canEditAt( position ) ) { @@ -58,53 +58,57 @@ export default class InsertParagraphCommand extends Command { } model.change( writer => { - const paragraph = writer.createElement( 'paragraph' ); - const allowedParent = model.schema.findAllowedParent( position, paragraph ); + position = this._findPositionToInsertParagraph( position!, writer ); - // It could be there's no ancestor limit that would allow paragraph. - // In theory, "paragraph" could be disallowed even in the "$root". - if ( !allowedParent ) { + if ( !position ) { return; } + const paragraph = writer.createElement( 'paragraph' ); + if ( attributes ) { model.schema.setAllowedAttributes( paragraph, attributes, writer ); } - if ( position.path.length < 2 ) { - model.insertContent( paragraph, position ); - writer.setSelection( paragraph, 'in' ); - - return; - } + model.insertContent( paragraph, position ); + writer.setSelection( paragraph, 'in' ); + } ); + } - const positionParent = position.parent as Element; + /** + * Returns the best position to insert a new paragraph. + */ + private _findPositionToInsertParagraph( position: Position, writer: Writer ): Position | null { + const model = this.editor.model; - // E.g. - // [] ---> [] - const isInEmptyBlock = positionParent.isEmpty; + if ( model.schema.checkChild( position, 'paragraph' ) ) { + return position; + } - // E.g. - // foo[] ---> foo[] - const isAtEndOfTextBlock = position.isAtEnd && !positionParent.isEmpty; + const allowedParent = model.schema.findAllowedParent( position, 'paragraph' ); - // E.g. - // []foo ---> []foo - const isAtStartOfTextBlock = position.isAtStart && !positionParent.isEmpty; + // It could be there's no ancestor limit that would allow paragraph. + // In theory, "paragraph" could be disallowed even in the "$root". + if ( !allowedParent ) { + return null; + } - const canBeChild = model.schema.checkChild( positionParent, paragraph ); + const positionParent = position.parent as Element; + const isTextAllowed = model.schema.checkChild( positionParent, '$text' ); - if ( isInEmptyBlock || isAtEndOfTextBlock ) { - position = writer.createPositionAfter( positionParent ); - } else if ( isAtStartOfTextBlock ) { - position = writer.createPositionBefore( positionParent ); - } else if ( !canBeChild ) { - position = writer.split( position, allowedParent ).position; - } + // At empty $block or at the end of $block. + // [] ---> [] + // foo[] ---> foo[] + if ( positionParent.isEmpty || isTextAllowed && position.isAtEnd ) { + return model.createPositionAfter( positionParent ); + } - model.insertContent( paragraph, position ); + // At the start of $block with text. + // []foo ---> []foo + if ( !positionParent.isEmpty && isTextAllowed && position.isAtStart ) { + return model.createPositionBefore( positionParent ); + } - writer.setSelection( paragraph, 'in' ); - } ); + return writer.split( position, allowedParent ).position; } } diff --git a/packages/ckeditor5-paragraph/tests/insertparagraphcommand.js b/packages/ckeditor5-paragraph/tests/insertparagraphcommand.js index 03f1ccd2b7c..a4b2059eedc 100644 --- a/packages/ckeditor5-paragraph/tests/insertparagraphcommand.js +++ b/packages/ckeditor5-paragraph/tests/insertparagraphcommand.js @@ -137,6 +137,68 @@ describe( 'InsertParagraphCommand', () => { ); } ); + // See https://github.com/ckeditor/ckeditor5/issues/14714. + it( 'should insert paragraph bellow the block widget (inside container)', () => { + schema.register( 'blockContainer', { inheritAllFrom: '$container' } ); + schema.register( 'blockWidget', { inheritAllFrom: '$blockObject', allowIn: 'allowP' } ); + + setData( model, + '' + + '[]' + + '' + ); + + command.execute( { + position: model.document.selection.getLastPosition() + } ); + + expect( getData( model ) ).to.equal( + '' + + '' + + '[]' + + '' + ); + } ); + + // See https://github.com/ckeditor/ckeditor5/issues/14714. + it( 'should insert paragraph bellow the block widget (inside table cell)', () => { + schema.register( 'table', { inheritAllFrom: '$blockObject' } ); + schema.register( 'tableRow', { allowIn: 'table', isLimit: true } ); + schema.register( 'tableCell', { + allowContentOf: '$container', + allowIn: 'tableRow', + isLimit: true, + isSelectable: true + } ); + + schema.register( 'blockWidget', { inheritAllFrom: '$blockObject' } ); + + setData( model, + '' + + '' + + '' + + '[]' + + '' + + '' + + '
' + ); + + command.execute( { + position: model.document.selection.getLastPosition() + } ); + + expect( getData( model ) ).to.equal( + '' + + '' + + '' + + '' + + '[]' + + '' + + '' + + '
' + ); + } ); + it( 'should do nothing if the paragraph is not allowed at the provided position', () => { // Create a situation where "paragraph" is disallowed even in the "root". schema.addChildCheck( ( context, childDefinition ) => {