瀏覽代碼

Moved ImageBalloon logic to ImageToolbar.

Aleksander Nowodzinski 8 年之前
父節點
當前提交
9679f05fe5
共有 2 個文件被更改,包括 109 次插入35 次删除
  1. 72 21
      packages/ckeditor5-image/src/imagetoolbar.js
  2. 37 14
      packages/ckeditor5-image/tests/imagetoolbar.js

+ 72 - 21
packages/ckeditor5-image/src/imagetoolbar.js

@@ -10,8 +10,9 @@
 import Template from '@ckeditor/ckeditor5-ui/src/template';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
-import ImageBalloon from './image/ui/imageballoon';
+import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 import { isImageWidget } from './image/utils';
+import { repositionContextualBalloon, getBalloonPositionData } from './image/ui/utils';
 
 const balloonClassName = 'ck-toolbar-container ck-editor-toolbar-container';
 
@@ -21,7 +22,7 @@ const balloonClassName = 'ck-toolbar-container ck-editor-toolbar-container';
  * Toolbar components are created using editor's {@link module:ui/componentfactory~ComponentFactory ComponentFactory}
  * based on {@link module:core/editor/editor~Editor#config configuration} stored under `image.toolbar`.
  *
- * The toolbar uses {@link module:image/image/ui/imageballoon~ImageBalloon}.
+ * The toolbar uses {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -30,7 +31,7 @@ export default class ImageToolbar extends Plugin {
 	 * @inheritDoc
 	 */
 	static get requires() {
-		return [ ImageBalloon ];
+		return [ ContextualBalloon ];
 	}
 
 	/**
@@ -53,6 +54,14 @@ export default class ImageToolbar extends Plugin {
 		}
 
 		/**
+		 * The contextual balloon plugin instance.
+		 *
+		 * @private
+		 * @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
+		 */
+		this._balloon = this.editor.plugins.get( 'ContextualBalloon' );
+
+		/**
 		 * A `ToolbarView` instance used to display the buttons specific for image
 		 * editing.
 		 *
@@ -72,39 +81,81 @@ export default class ImageToolbar extends Plugin {
 		this._toolbar.fillFromConfig( toolbarConfig, editor.ui.componentFactory );
 
 		// Show balloon panel each time image widget is selected.
-		this.listenTo( this.editor.editing.view, 'render', () => {
+		this.listenTo( editor.editing.view, 'render', () => {
 			this._checkIsVisible();
 		}, { priority: 'low' } );
 
 		// There is no render method after focus is back in editor, we need to check if balloon panel should be visible.
-		this.listenTo( editor.ui.focusTracker, 'change:isFocused', ( evt, name, is, was ) => {
-			if ( !was && is ) {
-				this._checkIsVisible();
-			}
-		} );
+		this.listenTo( editor.ui.focusTracker, 'change:isFocused', () => {
+			this._checkIsVisible();
+		}, { priority: 'low' } );
 	}
 
 	/**
 	 * Checks whether the toolbar should show up or hide depending on the
 	 * current selection.
 	 *
-	 * @protected
+	 * @private
 	 */
 	_checkIsVisible() {
-		const editingView = this.editor.editing.view;
-		const selectedElement = editingView.selection.getSelectedElement();
-		const balloon = this.editor.plugins.get( 'ImageBalloon' );
-		const toolbar = this._toolbar;
-
-		if ( selectedElement && isImageWidget( selectedElement ) ) {
-			if ( !balloon.hasView( toolbar ) ) {
-				balloon.add( {
-					view: toolbar,
+		const editor = this.editor;
+
+		if ( !editor.ui.focusTracker.isFocused ) {
+			this._hideToolbar();
+		} else {
+			const editingView = editor.editing.view;
+			const selectedElement = editingView.selection.getSelectedElement();
+
+			if ( selectedElement && isImageWidget( selectedElement ) ) {
+				this._showToolbar();
+			} else {
+				this._hideToolbar();
+			}
+		}
+	}
+
+	/**
+	 * Shows the {@link #_toolbar} in the {@link _balloon}.
+	 *
+	 * @private
+	 */
+	_showToolbar() {
+		const editor = this.editor;
+
+		if ( this._isVisible ) {
+			repositionContextualBalloon( editor );
+		} else {
+			if ( !this._balloon.hasView( this._toolbar ) ) {
+				this._balloon.add( {
+					view: this._toolbar,
+					position: getBalloonPositionData( editor ),
 					balloonClassName
 				} );
 			}
-		} else if ( balloon.hasView( toolbar ) ) {
-			balloon.remove( toolbar );
 		}
 	}
+
+	/**
+	 * Removes the {@link #_toolbar} from the {@link _balloon}.
+	 *
+	 * @private
+	 */
+	_hideToolbar() {
+		if ( !this._isVisible ) {
+			return;
+		}
+
+		this._balloon.remove( this._toolbar );
+	}
+
+	/**
+	 * Returns `true` when the {@link #_toolbar} is the visible view
+	 * in the {@link #_balloon}.
+	 *
+	 * @private
+	 * @type {Boolean}
+	 */
+	get _isVisible() {
+		return this._balloon.visibleView == this._toolbar;
+	}
 }

+ 37 - 14
packages/ckeditor5-image/tests/imagetoolbar.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* global document */
+
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ImageToolbar from '../src/imagetoolbar';
 import Image from '../src/image';
@@ -11,10 +13,11 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Range from '@ckeditor/ckeditor5-engine/src/model/range';
+import View from '@ckeditor/ckeditor5-ui/src/view';
 import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'ImageToolbar', () => {
-	let editor, doc, editingView, plugin, toolbar, imageBalloon, editorElement;
+	let editor, doc, editingView, plugin, toolbar, balloon, editorElement;
 
 	beforeEach( () => {
 		editorElement = global.document.createElement( 'div' );
@@ -32,7 +35,7 @@ describe( 'ImageToolbar', () => {
 			plugin = editor.plugins.get( ImageToolbar );
 			toolbar = plugin._toolbar;
 			editingView = editor.editing.view;
-			imageBalloon = editor.plugins.get( 'ImageBalloon' );
+			balloon = editor.plugins.get( 'ContextualBalloon' );
 		} );
 	} );
 
@@ -68,7 +71,9 @@ describe( 'ImageToolbar', () => {
 		} );
 
 		it( 'should set proper CSS classes', () => {
-			const spy = sinon.spy( imageBalloon, 'add' );
+			const spy = sinon.spy( balloon, 'add' );
+
+			editor.ui.focusTracker.isFocused = true;
 
 			setData( doc, '[<image src=""></image>]' );
 
@@ -88,10 +93,10 @@ describe( 'ImageToolbar', () => {
 			setData( doc, '[<image src=""></image>]' );
 
 			editor.ui.focusTracker.isFocused = false;
-			expect( imageBalloon.visibleView ).to.be.null;
+			expect( balloon.visibleView ).to.be.null;
 
 			editor.ui.focusTracker.isFocused = true;
-			expect( imageBalloon.visibleView ).to.equal( toolbar );
+			expect( balloon.visibleView ).to.equal( toolbar );
 		} );
 
 		it( 'should hide the toolbar when the editor loses focus and the image is selected', () => {
@@ -100,21 +105,25 @@ describe( 'ImageToolbar', () => {
 			setData( doc, '[<image src=""></image>]' );
 
 			editor.ui.focusTracker.isFocused = true;
-			expect( imageBalloon.visibleView ).to.equal( toolbar );
+			expect( balloon.visibleView ).to.equal( toolbar );
 
 			editor.ui.focusTracker.isFocused = false;
-			expect( imageBalloon.visibleView ).to.be.null;
+			expect( balloon.visibleView ).to.be.null;
 		} );
 	} );
 
 	describe( 'integration with the editor selection (#render event)', () => {
+		beforeEach( () => {
+			editor.ui.focusTracker.isFocused = true;
+		} );
+
 		it( 'should show the toolbar on render when the image is selected', () => {
 			setData( doc, '<paragraph>[foo]</paragraph><image src=""></image>' );
 
-			expect( imageBalloon.visibleView ).to.be.null;
+			expect( balloon.visibleView ).to.be.null;
 
 			editingView.fire( 'render' );
-			expect( imageBalloon.visibleView ).to.be.null;
+			expect( balloon.visibleView ).to.be.null;
 
 			doc.enqueueChanges( () => {
 				// Select the [<image></image>]
@@ -123,18 +132,32 @@ describe( 'ImageToolbar', () => {
 				] );
 			} );
 
-			expect( imageBalloon.visibleView ).to.equal( toolbar );
+			expect( balloon.visibleView ).to.equal( toolbar );
 
 			// Make sure successive render does not throw, e.g. attempting
 			// to insert the toolbar twice.
 			editingView.fire( 'render' );
-			expect( imageBalloon.visibleView ).to.equal( toolbar );
+			expect( balloon.visibleView ).to.equal( toolbar );
+		} );
+
+		it( 'should not engage when the toolbar is in the balloon yet invisible', () => {
+			setData( doc, '[<image src=""></image>]' );
+			expect( balloon.visibleView ).to.equal( toolbar );
+
+			const lastView = new View();
+			lastView.element = document.createElement( 'div' );
+
+			balloon.add( { view: lastView } );
+			expect( balloon.visibleView ).to.equal( lastView );
+
+			editingView.fire( 'render' );
+			expect( balloon.visibleView ).to.equal( lastView );
 		} );
 
 		it( 'should hide the toolbar on render if the image is de–selected', () => {
 			setData( doc, '<paragraph>foo</paragraph>[<image src=""></image>]' );
 
-			expect( imageBalloon.visibleView ).to.equal( toolbar );
+			expect( balloon.visibleView ).to.equal( toolbar );
 
 			doc.enqueueChanges( () => {
 				// Select the <paragraph>[...]</paragraph>
@@ -143,12 +166,12 @@ describe( 'ImageToolbar', () => {
 				] );
 			} );
 
-			expect( imageBalloon.visibleView ).to.be.null;
+			expect( balloon.visibleView ).to.be.null;
 
 			// Make sure successive render does not throw, e.g. attempting
 			// to remove the toolbar twice.
 			editingView.fire( 'render' );
-			expect( imageBalloon.visibleView ).to.be.null;
+			expect( balloon.visibleView ).to.be.null;
 		} );
 	} );