瀏覽代碼

Integrated ImageToolbar with ImageBalloon.

Aleksander Nowodzinski 8 年之前
父節點
當前提交
2c40100e38
共有 2 個文件被更改,包括 131 次插入90 次删除
  1. 41 50
      packages/ckeditor5-image/src/imagetoolbar.js
  2. 90 40
      packages/ckeditor5-image/tests/imagetoolbar.js

+ 41 - 50
packages/ckeditor5-image/src/imagetoolbar.js

@@ -10,37 +10,34 @@
 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 { isImageWidget } from './image/utils';
-import ImageBalloonPanel from './image/ui/imageballoonpanelview';
+
+const balloonClassName = 'ck-toolbar-container ck-editor-toolbar-container';
 
 /**
- * Image toolbar class. Creates image toolbar placed inside balloon panel that is showed when image widget is selected.
+ * The image toolbar class. Creates an image toolbar that shows up when image widget is selected.
+ *
  * 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}.
+ *
  * @extends module:core/plugin~Plugin
  */
 export default class ImageToolbar extends Plugin {
 	/**
 	 * @inheritDoc
 	 */
-	static get pluginName() {
-		return 'ImageToolbar';
+	static get requires() {
+		return [ ImageBalloon ];
 	}
 
 	/**
 	 * @inheritDoc
 	 */
-	constructor( editor ) {
-		super( editor );
-
-		/**
-		 * When set to `true`, toolbar will be repositioned and showed on each render event and focus change.
-		 * Set to `false` to temporary disable the image toolbar.
-		 *
-		 * @member {Boolean}
-		 */
-		this.isEnabled = true;
+	static get pluginName() {
+		return 'ImageToolbar';
 	}
 
 	/**
@@ -55,65 +52,59 @@ export default class ImageToolbar extends Plugin {
 			return;
 		}
 
-		const panel = this._panel = new ImageBalloonPanel( editor );
-		const toolbar = new ToolbarView();
+		/**
+		 * A `ToolbarView` instance used to display the buttons specific for image
+		 * editing.
+		 *
+		 * @protected
+		 * @type {module:ui/toolbar/toolbarview~ToolbarView}
+		 */
+		this._toolbar = new ToolbarView();
 
 		// Add CSS class to the toolbar.
-		Template.extend( toolbar.template, {
+		Template.extend( this._toolbar.template, {
 			attributes: {
 				class: 'ck-editor-toolbar'
 			}
 		} );
 
-		// Add CSS class to the panel.
-		Template.extend( panel.template, {
-			attributes: {
-				class: [
-					'ck-toolbar-container',
-					'ck-editor-toolbar-container'
-				]
-			}
-		} );
-
-		// Add toolbar to balloon panel.
-		panel.content.add( toolbar );
-
 		// Add buttons to the toolbar.
-		toolbar.fillFromConfig( toolbarConfig, editor.ui.componentFactory );
-
-		// Add balloon panel to editor's UI.
-		editor.ui.view.body.add( panel );
+		this._toolbar.fillFromConfig( toolbarConfig, editor.ui.componentFactory );
 
 		// Show balloon panel each time image widget is selected.
 		this.listenTo( this.editor.editing.view, 'render', () => {
-			if ( this.isEnabled ) {
-				this.show();
-			}
+			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.isEnabled ) {
-				this.show();
+			if ( !was && is ) {
+				this._checkIsVisible();
 			}
 		} );
 	}
 
 	/**
-	 * Shows the toolbar.
+	 * Checks whether the toolbar should show up or hide depending on the
+	 * current selection.
+	 *
+	 * @protected
 	 */
-	show() {
-		const selectedElement = this.editor.editing.view.selection.getSelectedElement();
+	_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 ) ) {
-			this._panel.attach();
+			if ( !balloon.hasView( toolbar ) ) {
+				balloon.add( {
+					view: toolbar,
+					balloonClassName
+				} );
+			}
+		} else if ( balloon.hasView( toolbar ) ) {
+			balloon.remove( toolbar );
 		}
 	}
-
-	/**
-	 * Hides the toolbar.
-	 */
-	hide() {
-		this._panel.detach();
-	}
 }

+ 90 - 40
packages/ckeditor5-image/tests/imagetoolbar.js

@@ -7,34 +7,38 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
 import ImageToolbar from '../src/imagetoolbar';
 import Image from '../src/image';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
-import ImageBalloonPanel from '../src/image/ui/imageballoonpanelview';
 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 { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'ImageToolbar', () => {
-	let editor, editingView, doc, panel, plugin;
+	let editor, doc, editingView, plugin, toolbar, imageBalloon, editorElement;
 
 	beforeEach( () => {
-		const editorElement = global.document.createElement( 'div' );
+		editorElement = global.document.createElement( 'div' );
 		global.document.body.appendChild( editorElement );
 
 		return ClassicEditor.create( editorElement, {
-			plugins: [ Image, ImageToolbar, FakeButton ],
+			plugins: [ Paragraph, Image, ImageToolbar, FakeButton ],
 			image: {
 				toolbar: [ 'fake_button' ]
 			}
 		} )
 		.then( newEditor => {
 			editor = newEditor;
-			editingView = editor.editing.view;
 			doc = editor.document;
 			plugin = editor.plugins.get( ImageToolbar );
-			panel = plugin._panel;
+			toolbar = plugin._toolbar;
+			editingView = editor.editing.view;
+			imageBalloon = editor.plugins.get( 'ImageBalloon' );
 		} );
 	} );
 
 	afterEach( () => {
+		editorElement.remove();
+
 		return editor.destroy();
 	} );
 
@@ -49,57 +53,103 @@ describe( 'ImageToolbar', () => {
 		return ClassicEditor.create( editorElement, {
 			plugins: [ ImageToolbar ],
 		} )
-			.then( newEditor => {
-				expect( newEditor.plugins.get( ImageToolbar )._panel ).to.be.undefined;
+			.then( editor => {
+				expect( editor.plugins.get( ImageToolbar )._toolbar ).to.be.undefined;
 
-				newEditor.destroy();
+				editorElement.remove();
+				editor.destroy();
 			} );
 	} );
 
-	it( 'should set css classes', () => {
-		expect( panel.element.classList.contains( 'ck-toolbar-container' ) ).to.be.true;
-		expect( panel.element.classList.contains( 'ck-editor-toolbar-container' ) ).to.be.true;
-		expect( panel.content.get( 0 ).element.classList.contains( 'ck-editor-toolbar' ) ).to.be.true;
-	} );
+	describe( 'toolbar', () => {
+		it( 'should use the config.image.toolbar to create items', () => {
+			expect( toolbar.items ).to.have.length( 1 );
+			expect( toolbar.items.get( 0 ).label ).to.equal( 'fake button' );
+		} );
 
-	it( 'should add ImageBalloonPanel to view body', () => {
-		expect( panel ).to.be.instanceOf( ImageBalloonPanel );
-	} );
+		it( 'should set proper CSS classes', () => {
+			const spy = sinon.spy( imageBalloon, 'add' );
 
-	it( 'should show the panel when editor gains focus and image is selected', () => {
-		setData( doc, '[<image src=""></image>]' );
+			setData( doc, '[<image src=""></image>]' );
 
-		editor.ui.focusTracker.isFocused = false;
-		const spy = sinon.spy( plugin, 'show' );
-		editor.ui.focusTracker.isFocused = true;
+			expect( toolbar.element.classList.contains( 'ck-editor-toolbar' ) ).to.be.true;
 
-		sinon.assert.calledOnce( spy );
+			sinon.assert.calledWithMatch( spy, {
+				view: toolbar,
+				balloonClassName: 'ck-toolbar-container ck-editor-toolbar-container'
+			} );
+		} );
 	} );
 
-	it( 'should not show the panel automatically when it is disabled', () => {
-		plugin.isEnabled = false;
-		setData( doc, '[<image src=""></image>]' );
-		editor.ui.focusTracker.isFocused = true;
-		const spy = sinon.spy( plugin, 'show' );
+	describe( 'integration with the editor focus', () => {
+		it( 'should show the toolbar when the editor gains focus and the image is selected', () => {
+			editor.ui.focusTracker.isFocused = true;
 
-		editingView.render();
+			setData( doc, '[<image src=""></image>]' );
 
-		sinon.assert.notCalled( spy );
-	} );
+			editor.ui.focusTracker.isFocused = false;
+			expect( imageBalloon.visibleView ).to.be.null;
+
+			editor.ui.focusTracker.isFocused = true;
+			expect( imageBalloon.visibleView ).to.equal( toolbar );
+		} );
+
+		it( 'should hide the toolbar when the editor loses focus and the image is selected', () => {
+			editor.ui.focusTracker.isFocused = false;
+
+			setData( doc, '[<image src=""></image>]' );
 
-	it( 'should not show the panel when editor looses focus', () => {
-		editor.ui.focusTracker.isFocused = true;
-		const spy = sinon.spy( plugin, 'show' );
-		editor.ui.focusTracker.isFocused = false;
+			editor.ui.focusTracker.isFocused = true;
+			expect( imageBalloon.visibleView ).to.equal( toolbar );
 
-		sinon.assert.notCalled( spy );
+			editor.ui.focusTracker.isFocused = false;
+			expect( imageBalloon.visibleView ).to.be.null;
+		} );
 	} );
 
-	it( 'should detach panel with hide() method', () => {
-		const spy = sinon.spy( panel, 'detach' );
-		plugin.hide();
+	describe( 'integration with the editor selection (#render event)', () => {
+		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;
+
+			editingView.fire( 'render' );
+			expect( imageBalloon.visibleView ).to.be.null;
+
+			doc.enqueueChanges( () => {
+				// Select the [<image></image>]
+				doc.selection.setRanges( [
+					Range.createOn( doc.getRoot().getChild( 1 ) )
+				] );
+			} );
+
+			expect( imageBalloon.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 );
+		} );
+
+		it( 'should hide the toolbar on render if the image is de–selected', () => {
+			setData( doc, '<paragraph>foo</paragraph>[<image src=""></image>]' );
 
-		sinon.assert.calledOnce( spy );
+			expect( imageBalloon.visibleView ).to.equal( toolbar );
+
+			doc.enqueueChanges( () => {
+				// Select the <paragraph>[...]</paragraph>
+				doc.selection.setRanges( [
+					Range.createIn( doc.getRoot().getChild( 0 ) )
+				] );
+			} );
+
+			expect( imageBalloon.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;
+		} );
 	} );
 
 	// Plugin that adds fake_button to editor's component factory.