From 99c28764b69d20a76111b93a58182e268a55c3dc Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 15 Feb 2024 15:53:28 +0100 Subject: [PATCH 1/2] Inserting a mention should not add an extra white space if there was one already. A mention inserted in brackets should not be followed by a white space. --- .../ckeditor5-mention/src/mentioncommand.ts | 30 ++++++++++++- .../ckeditor5-mention/tests/mentioncommand.js | 45 ++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-mention/src/mentioncommand.ts b/packages/ckeditor5-mention/src/mentioncommand.ts index dbe8cedac42..80eef567377 100644 --- a/packages/ckeditor5-mention/src/mentioncommand.ts +++ b/packages/ckeditor5-mention/src/mentioncommand.ts @@ -13,6 +13,12 @@ import { CKEditorError, toMap } from 'ckeditor5/src/utils.js'; import { _addMentionAttributes } from './mentionediting.js'; +const BRACKET_PAIRS = { + '(': ')', + '[': ']', + '{': '}' +} as const; + /** * The mention command. * @@ -168,8 +174,28 @@ export default class MentionCommand extends Command { attributesWithMention.set( 'mention', mention ); // Replace a range with the text with a mention. - model.insertContent( writer.createText( mentionText, attributesWithMention ), range ); - model.insertContent( writer.createText( ' ', currentAttributes ), range!.start.getShiftedBy( mentionText.length ) ); + const insertionRange = model.insertContent( writer.createText( mentionText, attributesWithMention ), range ); + const nodeBefore = insertionRange.start.nodeBefore; + const nodeAfter = insertionRange.end.nodeAfter; + + const isFollowedByWhiteSpace = nodeAfter && nodeAfter.is( '$text' ) && nodeAfter.data.startsWith( ' ' ); + let isInsertedInBrackets = false; + + if ( nodeBefore && nodeAfter && nodeBefore.is( '$text' ) && nodeAfter.is( '$text' ) ) { + const precedingCharacter = nodeBefore.data.slice( -1 ); + const isPrecededByOpeningBracket = precedingCharacter in BRACKET_PAIRS; + const isFollowedByBracketClosure = isPrecededByOpeningBracket && nodeAfter.data.startsWith( + BRACKET_PAIRS[ precedingCharacter as keyof typeof BRACKET_PAIRS ] + ); + + isInsertedInBrackets = isPrecededByOpeningBracket && isFollowedByBracketClosure; + } + + // Don't add a white space if there's already one after the mention or if the mention was inserted in brackets. + // https://github.com/ckeditor/ckeditor5/issues/4651 + if ( !isInsertedInBrackets && !isFollowedByWhiteSpace ) { + model.insertContent( writer.createText( ' ', currentAttributes ), range!.start.getShiftedBy( mentionText.length ) ); + } } ); } } diff --git a/packages/ckeditor5-mention/tests/mentioncommand.js b/packages/ckeditor5-mention/tests/mentioncommand.js index 9a8bec022b4..87e972c1be9 100644 --- a/packages/ckeditor5-mention/tests/mentioncommand.js +++ b/packages/ckeditor5-mention/tests/mentioncommand.js @@ -4,7 +4,7 @@ */ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor.js'; -import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js'; +import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js'; import MentionCommand from '../src/mentioncommand.js'; @@ -177,6 +177,49 @@ describe( 'MentionCommand', () => { expectToThrowCKEditorError( () => command.execute( options ), /mentioncommand-incorrect-id/, editor ); } } ); + + it( 'should not insert a white space if the mention was already followed by one', () => { + setData( model, 'foo [] bar' ); + + command.execute( { + marker: '@', + mention: '@John' + } ); + + assertMention( doc.getRoot().getChild( 0 ).getChild( 1 ), '@John' ); + + expect( getData( model ) ).to.match( + /foo <\$text mention="{"uid":"[^"]+","_text":"@John","id":"@John"}">@John\[\]<\/\$text> bar<\/paragraph>/ + ); + } ); + + [ [ '(', ')' ], [ '[', ']' ], [ '{', '}' ] ].forEach( ( [ openingBracket, closingBracket ] ) => { + it( `should not insert a white space if the mention injected in "${ openingBracket }${ closingBracket }" brackets`, () => { + model.change( writer => { + const paragraph = writer.createElement( 'paragraph' ); + const text = writer.createText( `${ openingBracket }${ closingBracket }` ); + + writer.append( text, paragraph ); + writer.append( paragraph, model.document.getRoot() ); + + writer.setSelection( paragraph, 1 ); + } ); + + command.execute( { + marker: '@', + mention: '@John' + } ); + + assertMention( doc.getRoot().getChild( 0 ).getChild( 1 ), '@John' ); + + expect( getData( model ) ).to.match( + new RegExp( '\\' + openingBracket + + '<\\$text mention="{"uid":"[^"]+","_text":"@John","id":"@John"}">@John\\[\\]\\' + + closingBracket + '' + ) + ); + } ); + } ); } ); function assertMention( textNode, id ) { From e54a9c8fbf63f9d63ba16c5c2faf8c242dad3073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Jarz=C4=99bski?= Date: Tue, 20 Feb 2024 12:48:05 +0100 Subject: [PATCH 2/2] Expand comment [skip ci]. --- packages/ckeditor5-mention/src/mentioncommand.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-mention/src/mentioncommand.ts b/packages/ckeditor5-mention/src/mentioncommand.ts index 80eef567377..08609394a75 100644 --- a/packages/ckeditor5-mention/src/mentioncommand.ts +++ b/packages/ckeditor5-mention/src/mentioncommand.ts @@ -191,7 +191,9 @@ export default class MentionCommand extends Command { isInsertedInBrackets = isPrecededByOpeningBracket && isFollowedByBracketClosure; } - // Don't add a white space if there's already one after the mention or if the mention was inserted in brackets. + // Don't add a white space if either of the following is true: + // * there's already one after the mention; + // * the mention was inserted in the empty matching brackets. // https://github.com/ckeditor/ckeditor5/issues/4651 if ( !isInsertedInBrackets && !isFollowedByWhiteSpace ) { model.insertContent( writer.createText( ' ', currentAttributes ), range!.start.getShiftedBy( mentionText.length ) );