Explorar o código

Show notification on failed feed callback call.

Maciej Gołaszewski %!s(int64=6) %!d(string=hai) anos
pai
achega
e85acd0edf

+ 37 - 18
packages/ckeditor5-mention/src/mentionui.js

@@ -15,6 +15,7 @@ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
 import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
+import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
 import { debounce } from 'lodash-es';
 
 import TextWatcher from './textwatcher';
@@ -38,6 +39,13 @@ export default class MentionUI extends Plugin {
 		return 'MentionUI';
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ ContextualBalloon, Notification ];
+	}
+
 	/**
 	 * @inheritDoc
 	 */
@@ -105,13 +113,22 @@ export default class MentionUI extends Plugin {
 			this._getFeed( marker, feedText )
 				.catch( error => {
 					if ( error.feedDiscarded ) {
+						// Do nothing on discarded feeds as they might come after another one was requested.
 						return;
 					}
 
-					// TODO: show warning
-
-					// Hide the UI on errors? Or just remove marker?
+					// At this point something bad happened - most likely unreachable host or some feed callback error.
+					// So cleanup marker, remove the UI and...
 					this._hideUIAndRemoveMarker();
+
+					const notification = editor.plugins.get( Notification );
+					const t = editor.locale.t;
+
+					// ...show warning notification.
+					notification.showWarning( t( 'Could not obtain mention autocomplete feed.' ), {
+						title: t( 'Requesting feed failed' ),
+						namespace: 'mention'
+					} );
 				} )
 				.then( feed => {
 					if ( !feed ) {
@@ -122,6 +139,7 @@ export default class MentionUI extends Plugin {
 						return;
 					}
 
+					// Remove old entries.
 					this._items.clear();
 
 					for ( const feedItem of feed ) {
@@ -129,9 +147,11 @@ export default class MentionUI extends Plugin {
 
 						this._items.add( { item, marker } );
 					}
+
 					if ( this._items.length ) {
 						this._showUI( mentionMarker );
 					} else {
+						// Do not show empty mention UI.
 						this._hideUIAndRemoveMarker();
 					}
 				} );
@@ -227,13 +247,6 @@ export default class MentionUI extends Plugin {
 		this._mentionsView.destroy();
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	static get requires() {
-		return [ ContextualBalloon ];
-	}
-
 	/**
 	 * Returns true when {@link #_mentionsView} is in the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} and it is
 	 * currently visible.
@@ -332,9 +345,11 @@ export default class MentionUI extends Plugin {
 	/**
 	 * Returns a promise that resolves with autocomplete items for a given text.
 	 *
+	 * **Note**: This method unifies the responses from feed callbacks to always return a promise.
+	 *
 	 * @param {String} marker
 	 * @param {String} feedText
-	 * @return {Promise<module:mention/mention~MentionFeedItem>}
+	 * @returns {Promise<module:mention/mention~MentionFeedItem>}
 	 * @private
 	 */
 	_getFeed( marker, feedText ) {
@@ -342,6 +357,7 @@ export default class MentionUI extends Plugin {
 
 		this._lastRequested = feedText;
 
+		// To unify later processing the _getFeed should return a promise even if feed callback returns an array.
 		return new Promise( ( resolve, reject ) => {
 			const response = feedCallback( feedText );
 
@@ -351,13 +367,16 @@ export default class MentionUI extends Plugin {
 				resolve( response );
 			}
 
-			return response.then( response => {
-				if ( this._lastRequested == feedText ) {
-					resolve( response );
-				} else {
-					reject( { feedDiscarded: true } );
-				}
-			} );
+			response
+				.then( response => {
+					if ( this._lastRequested == feedText ) {
+						resolve( response );
+					} else {
+						// Discard a response that is not for the last requested feed.
+						reject( { feedDiscarded: true } );
+					}
+				} )
+				.catch( reject ); // Re-reject any errors from feed callback.
 		} );
 	}
 

+ 24 - 14
packages/ckeditor5-mention/tests/mentionui.js

@@ -16,6 +16,7 @@ import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventd
 import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
+import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
 import env from '@ckeditor/ckeditor5-utils/src/env';
 
 import MentionUI from '../src/mentionui';
@@ -63,6 +64,10 @@ describe( 'MentionUI', () => {
 		} );
 	} );
 
+	it( 'should load Notification plugin', () => {
+		expect( editor.plugins.get( Notification ) ).to.instanceOf( Notification );
+	} );
+
 	describe( 'init()', () => {
 		it( 'should throw if marker was not provided for feed', () => {
 			return createClassicTestEditor( { feeds: [ { feed: [ 'a' ] } ] } ).catch( error => {
@@ -922,25 +927,30 @@ describe( 'MentionUI', () => {
 					} );
 			} );
 
-			it( 'should warning if requested failed', () => {
+			it( 'should show warning if requested feed failed', () => {
 				setData( model, '<paragraph>foo []</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '#', doc.selection.getFirstPosition() );
-				} );
-
 				feedCallbackStub.throws( 'Request timeout' );
 
-				return Promise.resolve()
-					.then( waitForDebounce )
-					.then( () => {
-						expect( panelView.isVisible ).to.be.false;
-					} )
-					.then( waitForDebounce )
-					.then( () => {
-						expect( panelView.isVisible, 'panel is hidden' ).to.be.false;
-						expect( editor.model.markers.has( 'mention' ), 'there is no marker' ).to.be.false;
+				const notification = editor.plugins.get( Notification );
+
+				return new Promise( resolve => {
+					notification.on( 'show:warning', ( evt, data ) => {
+						// Stop the event to not block Karma tests.
+						evt.stop();
+						resolve( data );
+					}, { priority: 'high' } );
+
+					model.change( writer => {
+						writer.insertText( '#', doc.selection.getFirstPosition() );
 					} );
+				} ).then( data => {
+					expect( panelView.isVisible, 'panel is hidden' ).to.be.false;
+					expect( editor.model.markers.has( 'mention' ), 'there is no marker' ).to.be.false;
+
+					expect( data.message ).to.equal( 'Could not obtain mention autocomplete feed.' );
+					expect( data.title ).to.equal( 'Requesting feed failed' );
+				} );
 			} );
 		} );
 	} );