瀏覽代碼

Use ContextualBalloon plugin to show Mention UI panel.

Maciej Gołaszewski 6 年之前
父節點
當前提交
27cad3daa3
共有 2 個文件被更改,包括 87 次插入74 次删除
  1. 59 51
      packages/ckeditor5-mention/src/mentionui.js
  2. 28 23
      packages/ckeditor5-mention/tests/mentionui.js

+ 59 - 51
packages/ckeditor5-mention/src/mentionui.js

@@ -10,11 +10,11 @@
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
-import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
 import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler';
 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 TextWatcher from './textwatcher';
 
@@ -44,13 +44,6 @@ export default class MentionUI extends Plugin {
 		super( editor );
 
 		/**
-		 * The balloon panel view, containing the mention view.
-		 *
-		 * @type {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
-		 */
-		this.panelView = this._creatPanelView();
-
-		/**
 		 * The mentions view.
 		 *
 		 * @type {module:mention/ui/mentionsview~MentionsView}
@@ -73,9 +66,19 @@ export default class MentionUI extends Plugin {
 	 * @inheritDoc
 	 */
 	init() {
+		const editor = this.editor;
+
+		/**
+		 * The contextual balloon plugin instance.
+		 *
+		 * @private
+		 * @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
+		 */
+		this._balloon = editor.plugins.get( ContextualBalloon );
+
 		// Key listener that handles navigation in mention view.
-		this.editor.editing.view.document.on( 'keydown', ( evt, data ) => {
-			if ( isHandledKey( data.keyCode ) && this.panelView.isVisible ) {
+		editor.editing.view.document.on( 'keydown', ( evt, data ) => {
+			if ( isHandledKey( data.keyCode ) && this._isUIVisible ) {
 				data.preventDefault();
 				evt.stop(); // Required for enter overriding.
 
@@ -92,20 +95,20 @@ export default class MentionUI extends Plugin {
 				}
 
 				if ( data.keyCode == keyCodes.esc ) {
-					this._hidePanelAndRemoveMarker();
+					this._hideUIAndRemoveMarker();
 				}
 			}
 		}, { priority: 'highest' } ); // Required to override enter.
 
 		// Close the #panelView upon clicking outside of the plugin UI.
 		clickOutsideHandler( {
-			emitter: this.panelView,
-			contextElements: [ this.panelView.element ],
-			activator: () => this.panelView.isVisible,
-			callback: () => this._hidePanelAndRemoveMarker()
+			emitter: this._mentionsView,
+			activator: () => this._isUIVisible,
+			contextElements: [ this._balloon.view.element ],
+			callback: () => this._hideUIAndRemoveMarker()
 		} );
 
-		const feeds = this.editor.config.get( 'mention.feeds' );
+		const feeds = editor.config.get( 'mention.feeds' );
 
 		for ( const mentionDescription of feeds ) {
 			const feed = mentionDescription.feed;
@@ -145,24 +148,18 @@ export default class MentionUI extends Plugin {
 		super.destroy();
 
 		// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
-		this.panelView.destroy();
+		this._mentionsView.destroy();
 	}
 
 	/**
-	 * Creates the {@link #panelView}.
-	 *
-	 * @private
-	 * @returns {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
+	 * @inheritDoc
 	 */
-	_creatPanelView() {
-		const panelView = new BalloonPanelView( this.editor.locale );
-
-		panelView.withArrow = false;
-		panelView.render();
-
-		this.editor.ui.view.body.add( panelView );
+	static get requires() {
+		return [ ContextualBalloon ];
+	}
 
-		return panelView;
+	get _isUIVisible() {
+		return this._balloon.visibleView === this._mentionsView;
 	}
 
 	/**
@@ -178,8 +175,6 @@ export default class MentionUI extends Plugin {
 
 		this._items = new Collection();
 
-		this.panelView.content.add( mentionsView );
-
 		mentionsView.items.bindTo( this._items ).using( data => {
 			const { item, marker } = data;
 
@@ -222,7 +217,7 @@ export default class MentionUI extends Plugin {
 			const start = end.getShiftedBy( -matchedTextLength );
 			const range = model.createRange( start, end );
 
-			this._hidePanelAndRemoveMarker();
+			this._hideUIAndRemoveMarker();
 
 			editor.execute( 'mention', {
 				mention: item,
@@ -322,15 +317,15 @@ export default class MentionUI extends Plugin {
 					}
 
 					if ( this._items.length ) {
-						this._showPanel( mentionMarker );
+						this._showUI( mentionMarker );
 					} else {
-						this._hidePanelAndRemoveMarker();
+						this._hideUIAndRemoveMarker();
 					}
 				} );
 		} );
 
 		watcher.on( 'unmatched', () => {
-			this._hidePanelAndRemoveMarker();
+			this._hideUIAndRemoveMarker();
 		} );
 
 		return watcher;
@@ -350,30 +345,42 @@ export default class MentionUI extends Plugin {
 	}
 
 	/**
-	 * Shows the {@link #panelView}. If panel is already visible it will reposition it.
+	 * Shows the mentions balloon.
 	 *
 	 * @private
 	 */
-	_showPanel( markerMarker ) {
-		this.panelView.pin( this._getBalloonPanelPositionData( markerMarker, this.panelView.position ) );
-		this.panelView.show();
+	_showUI( markerMarker ) {
+		if ( this._isUIVisible ) {
+			// Update balloon position as the mention list view may change its size.
+			this._balloon.updatePosition( this._getBalloonPanelPositionData( markerMarker, this._mentionsView.position ) );
+		} else {
+			this._balloon.add( {
+				view: this._mentionsView,
+				position: this._getBalloonPanelPositionData( markerMarker, this._mentionsView.position ),
+				withArrow: false
+			} );
+		}
+
+		this._mentionsView.position = this._balloon.view.position;
+
 		this._mentionsView.selectFirst();
 	}
 
 	/**
-	 * Hides the {@link #panelView} and remove 'mention' marker from markers collection.
+	 * Hides the mentions balloon and remove 'mention' marker from markers collection.
 	 *
 	 * @private
 	 */
-	_hidePanelAndRemoveMarker() {
+	_hideUIAndRemoveMarker() {
 		if ( this.editor.model.markers.has( 'mention' ) ) {
 			this.editor.model.change( writer => writer.removeMarker( 'mention' ) );
 		}
 
-		this.panelView.unpin();
-		// Make last matched position on panel view undefined so the #_getBalloonPanelPositionData() will return all positions on next call.
-		this.panelView.position = undefined;
-		this.panelView.hide();
+		if ( this._isUIVisible ) {
+			this._balloon.remove( this._mentionsView );
+		}
+
+		this._mentionsView.position = undefined;
 	}
 
 	/**
@@ -418,11 +425,11 @@ export default class MentionUI extends Plugin {
 	 * Creates position options object used to position the balloon panel.
 	 *
 	 * @param {module:engine/model/markercollection~Marker} mentionMarker
-	 * @param {String|undefined} positionName Name of last matched position name.
+	 * @param {String|undefined} preferredPosition Name of last matched position name.
 	 * @returns {module:utils/dom/position~Options}
 	 * @private
 	 */
-	_getBalloonPanelPositionData( mentionMarker, positionName ) {
+	_getBalloonPanelPositionData( mentionMarker, preferredPosition ) {
 		const editing = this.editor.editing;
 		const domConverter = editing.view.domConverter;
 		const mapper = editing.mapper;
@@ -435,15 +442,16 @@ export default class MentionUI extends Plugin {
 
 				return rangeRects.pop();
 			},
-			positions: getBalloonPanelPositions( positionName )
+			positions: getBalloonPanelPositions( preferredPosition )
 		};
 	}
 }
 
 // Returns balloon positions data callbacks.
 //
+// @param {String} preferredPosition
 // @returns {Array.<module:utils/dom/position~Position>}
-function getBalloonPanelPositions( positionName ) {
+function getBalloonPanelPositions( preferredPosition ) {
 	const positions = {
 		// Positions panel to the south of caret rect.
 		'caret_se': targetRect => {
@@ -483,9 +491,9 @@ function getBalloonPanelPositions( positionName ) {
 	};
 
 	// Return only last position if it was matched to prevent panel from jumping after first match.
-	if ( positions.hasOwnProperty( positionName ) ) {
+	if ( positions.hasOwnProperty( preferredPosition ) ) {
 		return [
-			positions[ positionName ]
+			positions[ preferredPosition ]
 		];
 	}
 

+ 28 - 23
packages/ckeditor5-mention/tests/mentionui.js

@@ -8,7 +8,6 @@
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
 import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
@@ -16,6 +15,7 @@ import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
 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 MentionUI from '../src/mentionui';
 import MentionEditing from '../src/mentionediting';
@@ -56,6 +56,12 @@ describe( 'MentionUI', () => {
 		} );
 	} );
 
+	it( 'should load ContextualBalloon plugin', () => {
+		return createClassicTestEditor().then( () => {
+			expect( editor.plugins.get( ContextualBalloon ) ).to.be.instanceOf( ContextualBalloon );
+		} );
+	} );
+
 	describe( 'init()', () => {
 		it( 'should throw if marker was not provided for feed', () => {
 			return createClassicTestEditor( { feeds: [ { feed: [ 'a' ] } ] } ).catch( error => {
@@ -87,25 +93,26 @@ describe( 'MentionUI', () => {
 		} );
 	} );
 
-	describe( 'child views', () => {
-		beforeEach( () => createClassicTestEditor() );
-
-		describe( 'panelView', () => {
-			it( 'should create a view instance', () => {
-				expect( panelView ).to.instanceof( BalloonPanelView );
-			} );
+	describe( 'contextual balloon', () => {
+		beforeEach( () => {
+			return createClassicTestEditor( staticConfig )
+				.then( () => {
+					setData( model, '<paragraph>foo []</paragraph>' );
 
-			it( 'should be added to the ui.view.body collection', () => {
-				expect( Array.from( editor.ui.view.body ) ).to.include( panelView );
-			} );
+					model.change( writer => {
+						writer.insertText( '@', doc.selection.getFirstPosition() );
+					} );
+				} )
+				.then( waitForDebounce );
+		} );
 
-			it( 'should have disabled arrow', () => {
-				expect( panelView.withArrow ).to.be.false;
-			} );
+		it( 'should disable arrow', () => {
+			expect( panelView.isVisible ).to.be.true;
+			expect( panelView.withArrow ).to.be.false;
+		} );
 
-			it( 'should have added MentionView as a child', () => {
-				expect( panelView.content.get( 0 ) ).to.be.instanceof( MentionsView );
-			} );
+		it( 'should add MentionView to a panel', () => {
+			expect( editor.plugins.get( ContextualBalloon ).visibleView ).to.be.instanceof( MentionsView );
 		} );
 	} );
 
@@ -153,8 +160,6 @@ describe( 'MentionUI', () => {
 
 					expect( positions ).to.have.length( 4 );
 
-					// Mention UI should not set limiter or default values.
-					expect( pinArgument.limiter ).to.be.undefined;
 					expect( pinArgument.fitInViewport ).to.be.undefined;
 
 					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
@@ -224,7 +229,7 @@ describe( 'MentionUI', () => {
 
 					expect( positions ).to.have.length( 4 );
 
-					positionAfterFirstShow = panelView.position;
+					positionAfterFirstShow = mentionsView.position;
 
 					model.change( writer => {
 						writer.insertText( 't', doc.selection.getFirstPosition() );
@@ -780,7 +785,7 @@ describe( 'MentionUI', () => {
 					} );
 
 					expect( panelView.isVisible ).to.be.false;
-					expect( panelView.position ).to.be.undefined;
+					expect( mentionsView.position ).to.be.undefined;
 					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} );
 		} );
@@ -811,7 +816,7 @@ describe( 'MentionUI', () => {
 					} );
 
 					expect( panelView.isVisible ).to.be.false;
-					expect( panelView.position ).to.be.undefined;
+					expect( mentionsView.position ).to.be.undefined;
 					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} );
 		} );
@@ -1439,7 +1444,7 @@ describe( 'MentionUI', () => {
 				doc = model.document;
 				editingView = editor.editing.view;
 				mentionUI = editor.plugins.get( MentionUI );
-				panelView = mentionUI.panelView;
+				panelView = editor.plugins.get( ContextualBalloon ).view;
 				mentionsView = mentionUI._mentionsView;
 			} );
 	}