From 618c94aeea34a7d9e30cc8c91a472ddcd62ebb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 22 May 2019 14:26:19 +0200 Subject: [PATCH 1/7] Add support for letter and number class unicode characters. --- src/featuredetection.js | 12 +++++----- src/mentionui.js | 26 +++++++++++----------- tests/mentionui.js | 49 ++++++++++++++++++++++++++++++++++------- 3 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/featuredetection.js b/src/featuredetection.js index 234b046..cb56ce6 100644 --- a/src/featuredetection.js +++ b/src/featuredetection.js @@ -15,21 +15,21 @@ */ export default { /** - * Indicates whether the current browser supports ES2018 Unicode punctuation groups `\p{P}`. + * Indicates whether the current browser supports ES2018 Unicode groups like `\p{P}` or `\p{L}`. * * @type {Boolean} */ - isPunctuationGroupSupported: ( function() { - let punctuationSupported = false; - // Feature detection for Unicode punctuation groups. It's added in ES2018. Currently Firefox and Edge does not support it. + isUnicodeGroupSupported: ( function() { + let unicodeGroup = false; + // Feature detection for Unicode groups. It's added in ES2018. Currently Firefox and Edge does not support it. // See https://github.com/ckeditor/ckeditor5-mention/issues/44#issuecomment-487002174. try { - punctuationSupported = '.'.search( new RegExp( '[\\p{P}]', 'u' ) ) === 0; + unicodeGroup = 'ć'.search( new RegExp( '[\\p{L}]', 'u' ) ) === 0; } catch ( error ) { // Firefox throws a SyntaxError when the group is unsupported. } - return punctuationSupported; + return unicodeGroup; }() ) }; diff --git a/src/mentionui.js b/src/mentionui.js index 0a1ab9f..9f0e779 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -543,19 +543,19 @@ function getBalloonPanelPositions( preferredPosition ) { // @returns {RegExp} export function createRegExp( marker, minimumCharacters ) { const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`; - const patternBase = featureDetection.isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; - return new RegExp( buildPattern( patternBase, marker, numberOfCharacters ), 'u' ); -} + const openAfterCharacters = featureDetection.isUnicodeGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; + const mentionCharacters = featureDetection.isUnicodeGroupSupported ? '\\p{L}\\p{N}' : 'a-zA-ZÀ-ž0-9'; -// Helper to build a RegExp pattern string for the marker. -// -// @param {String} whitelistedCharacters -// @param {String} marker -// @param {Number} minimumCharacters -// @returns {String} -function buildPattern( whitelistedCharacters, marker, numberOfCharacters ) { - return `(^|[ ${ whitelistedCharacters }])([${ marker }])([_a-zA-Z0-9À-ž]${ numberOfCharacters }?)$`; + // The pattern is build from 3 groups: + // - 0 (non-capturing): Opening sequence - start of line, space or opening punctuation charcter like ( or ". + // - 1: Marker character. + // - 2: Mention input (with support of minimal lenght to trigger UI) + // The pattern matches up to the caret (end of string switch - $). + // (0: opening sequence )(1: marker )(2: typed mention )$ + const pattern = `(?:^|[ ${ openAfterCharacters }])([${ marker }])([_${ mentionCharacters }]${ numberOfCharacters }?)$`; + + return new RegExp( pattern, 'u' ); } // Creates a test callback for the marker to be used in the text watcher instance. @@ -579,8 +579,8 @@ function createTextMatcher( marker ) { return text => { const match = text.match( regExp ); - const marker = match[ 2 ]; - const feedText = match[ 3 ]; + const marker = match[ 1 ]; + const feedText = match[ 2 ]; return { marker, feedText }; }; diff --git a/tests/mentionui.js b/tests/mentionui.js index bc95448..01b8e11 100644 --- a/tests/mentionui.js +++ b/tests/mentionui.js @@ -445,10 +445,10 @@ describe( 'MentionUI', () => { let regExpStub; // Cache the original value to restore it after the tests. - const originalPunctuationSupport = featureDetection.isPunctuationGroupSupported; + const originalGroupSupport = featureDetection.isUnicodeGroupSupported; before( () => { - featureDetection.isPunctuationGroupSupported = false; + featureDetection.isUnicodeGroupSupported = false; } ); beforeEach( () => { @@ -461,21 +461,21 @@ describe( 'MentionUI', () => { } ); after( () => { - featureDetection.isPunctuationGroupSupported = originalPunctuationSupport; + featureDetection.isUnicodeGroupSupported = originalGroupSupport; } ); it( 'returns a simplified RegExp for browsers not supporting Unicode punctuation groups', () => { - featureDetection.isPunctuationGroupSupported = false; + featureDetection.isUnicodeGroupSupported = false; createRegExp( '@', 2 ); sinon.assert.calledOnce( regExpStub ); - sinon.assert.calledWithExactly( regExpStub, '(^|[ \\(\\[{"\'])([@])([_a-zA-Z0-9À-ž]{2,}?)$', 'u' ); + sinon.assert.calledWithExactly( regExpStub, '(?:^|[ \\(\\[{"\'])([@])([_a-zA-ZÀ-ž0-9]{2,}?)$', 'u' ); } ); it( 'returns a ES2018 RegExp for browsers supporting Unicode punctuation groups', () => { - featureDetection.isPunctuationGroupSupported = true; + featureDetection.isUnicodeGroupSupported = true; createRegExp( '@', 2 ); sinon.assert.calledOnce( regExpStub ); - sinon.assert.calledWithExactly( regExpStub, '(^|[ \\p{Ps}\\p{Pi}"\'])([@])([_a-zA-Z0-9À-ž]{2,}?)$', 'u' ); + sinon.assert.calledWithExactly( regExpStub, '(?:^|[ \\p{Ps}\\p{Pi}"\'])([@])([_\\p{L}\\p{N}]{2,}?)$', 'u' ); } ); } ); @@ -560,7 +560,7 @@ describe( 'MentionUI', () => { // Belongs to Pi (Punctuation, Initial quote) group: '«', '‹', '⸌', ' ⸂', '⸠' ] ) { - testOpeningPunctuationCharacter( character, !featureDetection.isPunctuationGroupSupported ); + testOpeningPunctuationCharacter( character, !featureDetection.isUnicodeGroupSupported ); } it( 'should not show panel for marker in the middle of other word', () => { @@ -792,6 +792,39 @@ describe( 'MentionUI', () => { } ); } ); + describe( 'unicode', () => { + beforeEach( () => { + return createClassicTestEditor( { + feeds: [ + { + // Always return 5 items + feed: () => [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ], + marker: '@' + } + ] + } ); + } ); + + it( 'should open panel for unicode character ב', function() { + if ( !featureDetection.isUnicodeGroupSupported ) { + this.skip(); + } + + setData( model, 'foo []' ); + + model.change( writer => { + writer.insertText( '@ב', doc.selection.getFirstPosition() ); + } ); + + return waitForDebounce() + .then( () => { + expect( panelView.isVisible, 'panel is visible' ).to.be.true; + expect( editor.model.markers.has( 'mention' ), 'marker is inserted' ).to.be.true; + expect( mentionsView.items ).to.have.length( 5 ); + } ); + } ); + } ); + describe( 'asynchronous list with custom trigger', () => { beforeEach( () => { const issuesNumbers = [ '#100', '#101', '#102', '#103' ]; From e8898e81040ce2ece6a692b6102cbea1dd5ba715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 22 May 2019 15:37:47 +0200 Subject: [PATCH 2/7] Fix regexp pattern for mention. --- src/mentionui.js | 4 ++-- tests/mentionui.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mentionui.js b/src/mentionui.js index 9f0e779..f280d30 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -552,8 +552,8 @@ export function createRegExp( marker, minimumCharacters ) { // - 1: Marker character. // - 2: Mention input (with support of minimal lenght to trigger UI) // The pattern matches up to the caret (end of string switch - $). - // (0: opening sequence )(1: marker )(2: typed mention )$ - const pattern = `(?:^|[ ${ openAfterCharacters }])([${ marker }])([_${ mentionCharacters }]${ numberOfCharacters }?)$`; + // (0: opening sequence )(1: marker )(2: typed mention )$ + const pattern = `(?:^|[ ${ openAfterCharacters }])([${ marker }])([_${ mentionCharacters }]${ numberOfCharacters })$`; return new RegExp( pattern, 'u' ); } diff --git a/tests/mentionui.js b/tests/mentionui.js index 01b8e11..aab9c2c 100644 --- a/tests/mentionui.js +++ b/tests/mentionui.js @@ -468,14 +468,14 @@ describe( 'MentionUI', () => { featureDetection.isUnicodeGroupSupported = false; createRegExp( '@', 2 ); sinon.assert.calledOnce( regExpStub ); - sinon.assert.calledWithExactly( regExpStub, '(?:^|[ \\(\\[{"\'])([@])([_a-zA-ZÀ-ž0-9]{2,}?)$', 'u' ); + sinon.assert.calledWithExactly( regExpStub, '(?:^|[ \\(\\[{"\'])([@])([_a-zA-ZÀ-ž0-9]{2,})$', 'u' ); } ); it( 'returns a ES2018 RegExp for browsers supporting Unicode punctuation groups', () => { featureDetection.isUnicodeGroupSupported = true; createRegExp( '@', 2 ); sinon.assert.calledOnce( regExpStub ); - sinon.assert.calledWithExactly( regExpStub, '(?:^|[ \\p{Ps}\\p{Pi}"\'])([@])([_\\p{L}\\p{N}]{2,}?)$', 'u' ); + sinon.assert.calledWithExactly( regExpStub, '(?:^|[ \\p{Ps}\\p{Pi}"\'])([@])([_\\p{L}\\p{N}]{2,})$', 'u' ); } ); } ); From 6b1da39f130f9b88ebd28d5db9087d2dfefce71b Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 26 Jun 2019 16:28:26 +0200 Subject: [PATCH 3/7] Docs and code refactoring. --- src/featuredetection.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/featuredetection.js b/src/featuredetection.js index cb56ce6..724c0a8 100644 --- a/src/featuredetection.js +++ b/src/featuredetection.js @@ -20,16 +20,17 @@ export default { * @type {Boolean} */ isUnicodeGroupSupported: ( function() { - let unicodeGroup = false; - // Feature detection for Unicode groups. It's added in ES2018. Currently Firefox and Edge does not support it. + let isSupported = false; + + // Feature detection for Unicode groups. Added in ES2018. Currently Firefox and Edge do not support it. // See https://github.com/ckeditor/ckeditor5-mention/issues/44#issuecomment-487002174. try { - unicodeGroup = 'ć'.search( new RegExp( '[\\p{L}]', 'u' ) ) === 0; + isSupported = 'ć'.search( new RegExp( '[\\p{L}]', 'u' ) ) === 0; } catch ( error ) { // Firefox throws a SyntaxError when the group is unsupported. } - return unicodeGroup; + return isSupported; }() ) }; From 690feafe2e1a2623442964e3c46eb0b1865d4fb6 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 26 Jun 2019 16:35:40 +0200 Subject: [PATCH 4/7] Docs: Improved internal createRegExp() helper docs. --- src/mentionui.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mentionui.js b/src/mentionui.js index 133563b..e3506bc 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -559,10 +559,11 @@ export function createRegExp( marker, minimumCharacters ) { const openAfterCharacters = featureDetection.isUnicodeGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; const mentionCharacters = featureDetection.isUnicodeGroupSupported ? '\\p{L}\\p{N}' : 'a-zA-ZÀ-ž0-9'; - // The pattern is build from 3 groups: - // - 0 (non-capturing): Opening sequence - start of line, space or opening punctuation charcter like ( or ". - // - 1: Marker character. - // - 2: Mention input (with support of minimal lenght to trigger UI) + // The pattern consists of 3 groups: + // - 0 (non-capturing): Opening sequence - start of the line, space or opening punctuation character like ( or ", + // - 1: The marker character, + // - 2: Mention input (taking the minimal length into consideration to trigger the UI), + // // The pattern matches up to the caret (end of string switch - $). // (0: opening sequence )(1: marker )(2: typed mention )$ const pattern = `(?:^|[ ${ openAfterCharacters }])([${ marker }])([_${ mentionCharacters }]${ numberOfCharacters })$`; From ae44d02ec3617114354317e5a6f63e689db6a210 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 26 Jun 2019 16:41:50 +0200 Subject: [PATCH 5/7] Docs: Added a missing article. --- src/mentionui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mentionui.js b/src/mentionui.js index e3506bc..978a2df 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -560,7 +560,7 @@ export function createRegExp( marker, minimumCharacters ) { const mentionCharacters = featureDetection.isUnicodeGroupSupported ? '\\p{L}\\p{N}' : 'a-zA-ZÀ-ž0-9'; // The pattern consists of 3 groups: - // - 0 (non-capturing): Opening sequence - start of the line, space or opening punctuation character like ( or ", + // - 0 (non-capturing): Opening sequence - start of the line, space or an opening punctuation character like "(" or "\"", // - 1: The marker character, // - 2: Mention input (taking the minimal length into consideration to trigger the UI), // From 6db8e2b96274e670c7fd1d16556fc45afced7c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 27 Jun 2019 14:28:29 +0200 Subject: [PATCH 6/7] Make unicode support test less confusing. --- tests/mentionui.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mentionui.js b/tests/mentionui.js index a4a1625..70027a2 100644 --- a/tests/mentionui.js +++ b/tests/mentionui.js @@ -805,7 +805,7 @@ describe( 'MentionUI', () => { feeds: [ { // Always return 5 items - feed: () => [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ], + feed: [ '@תַפּוּחַ', '@אַגָס', '@apple', '@pear' ], marker: '@' } ] @@ -820,14 +820,14 @@ describe( 'MentionUI', () => { setData( model, 'foo []' ); model.change( writer => { - writer.insertText( '@ב', doc.selection.getFirstPosition() ); + writer.insertText( '@ס', doc.selection.getFirstPosition() ); } ); return waitForDebounce() .then( () => { expect( panelView.isVisible, 'panel is visible' ).to.be.true; expect( editor.model.markers.has( 'mention' ), 'marker is inserted' ).to.be.true; - expect( mentionsView.items ).to.have.length( 5 ); + expect( mentionsView.items ).to.have.length( 1 ); } ); } ); } ); From 20caf4747c27795ee04323e9ef9b4d55ecdb92c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 28 Jun 2019 12:57:30 +0200 Subject: [PATCH 7/7] Fix merge conflicts. --- src/mentionui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mentionui.js b/src/mentionui.js index ebaee79..24cf937 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -568,7 +568,7 @@ function getFeedText( marker, text ) { const match = text.match( regExp ); - return match[ 3 ]; + return match[ 2 ]; } // The default feed callback.