浏览代码

Add support for letter and number class unicode characters.

Maciej Gołaszewski 6 年之前
父节点
当前提交
e5e738e584

+ 6 - 6
packages/ckeditor5-mention/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;
 	}() )
 };

+ 13 - 13
packages/ckeditor5-mention/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 };
 	};

+ 41 - 8
packages/ckeditor5-mention/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, '<paragraph>foo []</paragraph>' );
+
+				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' ];