Browse Source

Merge branch 'master' into t/44

# Conflicts:
#	src/mentionui.js
Maciej Gołaszewski 6 years ago
parent
commit
5c825e26cd

+ 1 - 1
packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html

@@ -31,7 +31,7 @@
 	</ul>
 	<div class="chat__editor">
 		<p>
-			I agree with <a href="mwilson@example.com" class="mention" data-mention="@mwilson">@mwilson</a> 👍.
+			I agree with <a href="mailto:mwilson@example.com" class="mention" data-mention="@mwilson">@mwilson</a> 👍.
 			It’s so nice of you to always be providing a few options to try! I love
 			<a href="https://example.com/social/greek" class="mention" data-mention="#greek">#greek</a> cuisine with a modern twist,
 			this one will be perfect to try.

+ 1 - 1
packages/ckeditor5-mention/docs/examples/chat-with-mentions.md

@@ -53,7 +53,7 @@ The HTML code of the application is listed below:
 	</ul>
 	<div class="chat__editor">
 		<p>
-			I agree with <a href="mwilson@example.com" class="mention" data-mention="@mwilson">@mwilson</a> 👍.
+			I agree with <a href="mailto:mwilson@example.com" class="mention" data-mention="@mwilson">@mwilson</a> 👍.
 			It’s so nice of you to always be providing a few options to try! I love
 			<a href="https://example.com/social/greek" class="mention" data-mention="#greek">#greek</a> cuisine with a modern twist,
 			this one will be perfect to try.

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

@@ -10,12 +10,12 @@
 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 env from '@ckeditor/ckeditor5-utils/src/env';
+import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 
 import TextWatcher from './textwatcher';
 
@@ -45,13 +45,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 mention view.
 		 *
 		 * @type {module:mention/ui/mentionsview~MentionsView}
@@ -74,9 +67,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 key overriding.
 
@@ -93,20 +96,20 @@ export default class MentionUI extends Plugin {
 				}
 
 				if ( data.keyCode == keyCodes.esc ) {
-					this._hidePanelAndRemoveMarker();
+					this._hideUIAndRemoveMarker();
 				}
 			}
 		}, { priority: 'highest' } ); // Required to override the Enter key.
 
-		// Close the #panelView upon clicking outside of the plugin UI.
+		// Close the dropdown 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;
@@ -146,24 +149,26 @@ 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;
+	/**
+	 * Returns true when {@link #_mentionsView} is in the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} and it is
+	 * currently visible.
+	 *
+	 * @readonly
+	 * @protected
+	 * @type {Boolean}
+	 */
+	get _isUIVisible() {
+		return this._balloon.visibleView === this._mentionsView;
 	}
 
 	/**
@@ -179,8 +184,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;
 
@@ -223,7 +226,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,
@@ -329,15 +332,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;
@@ -357,31 +360,44 @@ export default class MentionUI extends Plugin {
 	}
 
 	/**
-	 * Shows the {@link #panelView}. If the panel is already visible, it will reposition it.
+	 * Shows the mentions balloon. If the panel is already visible, it will reposition it.
 	 *
 	 * @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 removes the 'mention' marker from the markers collection.
+	 * Hides the mentions balloon and removes the 'mention' marker from the markers collection.
 	 *
 	 * @private
 	 */
-	_hidePanelAndRemoveMarker() {
+	_hideUIAndRemoveMarker() {
 		if ( this.editor.model.markers.has( 'mention' ) ) {
 			this.editor.model.change( writer => writer.removeMarker( 'mention' ) );
 		}
 
-		this.panelView.unpin();
+		if ( this._isUIVisible ) {
+			this._balloon.remove( this._mentionsView );
+		}
+
 		// Make the last matched position on panel view undefined so the #_getBalloonPanelPositionData() method will return all positions
 		// on the next call.
-		this.panelView.position = undefined;
-		this.panelView.hide();
+		this._mentionsView.position = undefined;
 	}
 
 	/**
@@ -426,11 +442,11 @@ export default class MentionUI extends Plugin {
 	 * Creates a position options object used to position the balloon panel.
 	 *
 	 * @param {module:engine/model/markercollection~Marker} mentionMarker
-	 * @param {String|undefined} positionName The name of the last matched position name.
+	 * @param {String|undefined} preferredPosition The name of the 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;
@@ -454,15 +470,16 @@ export default class MentionUI extends Plugin {
 
 				return null;
 			},
-			positions: getBalloonPanelPositions( positionName )
+			positions: getBalloonPanelPositions( preferredPosition )
 		};
 	}
 }
 
 // Returns the balloon positions data callbacks.
 //
+// @param {String} preferredPosition
 // @returns {Array.<module:utils/dom/position~Position>}
-function getBalloonPanelPositions( positionName ) {
+function getBalloonPanelPositions( preferredPosition ) {
 	const positions = {
 		// Positions the panel to the southeast of the caret rectangle.
 		'caret_se': targetRect => {
@@ -502,9 +519,9 @@ function getBalloonPanelPositions( positionName ) {
 	};
 
 	// Returns only the last position if it was matched to prevent the panel from jumping after the 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() );
@@ -358,8 +366,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.
@@ -367,6 +373,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...
@@ -1026,7 +1033,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;
 				} );
 		} );
@@ -1057,7 +1064,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;
 				} );
 		} );
@@ -1718,7 +1725,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;
 			} );
 	}