浏览代码

Merge branch 't/27' into t/4

Maciej Gołaszewski 6 年之前
父节点
当前提交
8319324316
共有 2 个文件被更改,包括 90 次插入75 次删除
  1. 59 51
      packages/ckeditor5-mention/src/mentionui.js
  2. 31 24
      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,
@@ -357,15 +352,15 @@ export default class MentionUI extends Plugin {
 						this._items.add( { item, marker } );
 					}
 					if ( this._items.length ) {
-						this._showPanel( mentionMarker );
+						this._showUI( mentionMarker );
 					} else {
-						this._hidePanelAndRemoveMarker();
+						this._hideUIAndRemoveMarker();
 					}
 				} );
 		} );
 
 		watcher.on( 'unmatched', () => {
-			this._hidePanelAndRemoveMarker();
+			this._hideUIAndRemoveMarker();
 		} );
 
 		return watcher;
@@ -385,30 +380,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;
 	}
 
 	/**
@@ -453,11 +460,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;
@@ -481,15 +488,16 @@ export default class MentionUI extends Plugin {
 
 				return null;
 			},
-			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 => {
@@ -529,9 +537,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 ]
 		];
 	}
 

+ 31 - 24
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 env from '@ckeditor/ckeditor5-utils/src/env';
 
 import MentionUI from '../src/mentionui';
@@ -57,6 +57,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 => {
@@ -88,25 +94,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 );
 		} );
 	} );
 
@@ -152,12 +159,13 @@ describe( 'MentionUI', () => {
 			return waitForDebounce()
 				.then( () => {
 					const pinArgument = pinSpy.firstCall.args[ 0 ];
-					const { target, positions, limiter } = pinArgument;
+					const { target, positions, limiter, fitInViewport } = pinArgument;
 
 					expect( positions ).to.have.length( 4 );
 
 					// Mention UI should set limiter to the editable area.
 					expect( limiter() ).to.equal( editingView.domConverter.mapViewToDom( editableElement ) );
+					expect( fitInViewport ).to.be.undefined;
 
 					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 					const mentionMarker = editor.model.markers.get( 'mention' );
@@ -226,7 +234,7 @@ describe( 'MentionUI', () => {
 
 					expect( positions ).to.have.length( 4 );
 
-					positionAfterFirstShow = panelView.position;
+					positionAfterFirstShow = mentionsView.position;
 
 					model.change( writer => {
 						writer.insertText( 't', doc.selection.getFirstPosition() );
@@ -356,8 +364,6 @@ describe( 'MentionUI', () => {
 					stopPropagation: sinon.spy()
 				};
 
-				const mentionElementSpy = testUtils.sinon.spy( mentionsView.element, 'scrollTop', [ 'set' ] );
-
 				return waitForDebounce()
 					.then( () => {
 						// The scroll test highly depends on browser styles.
@@ -365,6 +371,7 @@ describe( 'MentionUI', () => {
 						// To make this test repeatable across different environments it enforces mentions view size to 100px...
 						const reset = 'padding:0px;margin:0px;border:0 none;line-height: 1em;';
 
+						const mentionElementSpy = testUtils.sinon.spy( mentionsView.element, 'scrollTop', [ 'set' ] );
 						mentionsView.element.style = `min-height:100px;height:100px;max-height:100px;${ reset };`;
 
 						// ...and each list view item size to 25px...
@@ -865,7 +872,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;
 				} );
 		} );
@@ -896,7 +903,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;
 				} );
 		} );
@@ -1557,7 +1564,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;
 			} );
 	}