8
0
Просмотр исходного кода

Merge branch 'master' into t/192

Oskar Wróbel 8 лет назад
Родитель
Сommit
52c3dc9fd5

+ 21 - 11
packages/ckeditor5-ui/src/contextualballoon.js → packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js

@@ -4,11 +4,11 @@
  */
  */
 
 
 /**
 /**
- * @module ui/contextualballoon
+ * @module ui/panel/balloon/contextualballoon
  */
  */
 
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import BalloonPanelView from './panel/balloon/balloonpanelview';
+import BalloonPanelView from './balloonpanelview';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 
 /**
 /**
@@ -42,15 +42,15 @@ export default class ContextualBalloon extends Plugin {
 
 
 		/**
 		/**
 		 * Stack of the views injected into the balloon. Last one in the stack is displayed
 		 * Stack of the views injected into the balloon. Last one in the stack is displayed
-		 * as a content of {@link module:ui/contextualballoon~ContextualBalloon#view}.
+		 * as a content of {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon#view}.
 		 *
 		 *
 		 * @private
 		 * @private
 		 * @member {Map} #_stack
 		 * @member {Map} #_stack
 		 */
 		 */
 		this._stack = new Map();
 		this._stack = new Map();
 
 
-		// Add balloon panel view to editor `body` collection.
-		this.editor.ui.view.body.add( this.view );
+		// Add balloon panel view to editor `body` collection and wait until view will be ready.
+		return this.editor.ui.view.body.add( this.view );
 	}
 	}
 
 
 	static get pluginName() {
 	static get pluginName() {
@@ -83,8 +83,9 @@ export default class ContextualBalloon extends Plugin {
 	 * Adds a new view to the stack and makes it visible.
 	 * Adds a new view to the stack and makes it visible.
 	 *
 	 *
 	 * @param {Object} data Configuration of the view.
 	 * @param {Object} data Configuration of the view.
-	 * @param {module:ui/view~View} view Content of the balloon.
-	 * @param {module:utils/dom/position~Options} position Positioning options.
+	 * @param {module:ui/view~View} [data.view] Content of the balloon.
+	 * @param {module:utils/dom/position~Options} [data.position] Positioning options.
+	 * @returns {Promise} A Promise resolved when the child {@link module:ui/view~View#init} is done.
 	 */
 	 */
 	add( data ) {
 	add( data ) {
 		if ( this.hasView( data.view ) ) {
 		if ( this.hasView( data.view ) ) {
@@ -105,7 +106,7 @@ export default class ContextualBalloon extends Plugin {
 		// Add new view to the stack.
 		// Add new view to the stack.
 		this._stack.set( data.view, data );
 		this._stack.set( data.view, data );
 		// And display it.
 		// And display it.
-		this._show( data.view );
+		return this._show( data.view );
 	}
 	}
 
 
 	/**
 	/**
@@ -114,6 +115,7 @@ export default class ContextualBalloon extends Plugin {
 	 * When there is no view in the stack then balloon will hide.
 	 * When there is no view in the stack then balloon will hide.
 	 *
 	 *
 	 * @param {module:ui/view~View} view A view to be removed from the balloon.
 	 * @param {module:ui/view~View} view A view to be removed from the balloon.
+	 * @returns {Promise} A Promise resolved when the preceding view is ready.
 	 */
 	 */
 	remove( view ) {
 	remove( view ) {
 		if ( !this.hasView( view ) ) {
 		if ( !this.hasView( view ) ) {
@@ -125,6 +127,9 @@ export default class ContextualBalloon extends Plugin {
 			throw new CKEditorError( 'contextualballoon-remove-view-not-exist: Cannot remove configuration of not existing view.' );
 			throw new CKEditorError( 'contextualballoon-remove-view-not-exist: Cannot remove configuration of not existing view.' );
 		}
 		}
 
 
+		// A Promise resolved when the preceding view is ready.
+		let promise = Promise.resolve();
+
 		// When visible view is being removed.
 		// When visible view is being removed.
 		if ( this.visibleView === view ) {
 		if ( this.visibleView === view ) {
 			// We need to remove it from the view content.
 			// We need to remove it from the view content.
@@ -139,7 +144,7 @@ export default class ContextualBalloon extends Plugin {
 			// If it is some other view.
 			// If it is some other view.
 			if ( last ) {
 			if ( last ) {
 				// Just show it.
 				// Just show it.
-				this._show( last.view );
+				promise = this._show( last.view );
 			} else {
 			} else {
 				// Hide the balloon panel.
 				// Hide the balloon panel.
 				this.view.hide();
 				this.view.hide();
@@ -148,6 +153,8 @@ export default class ContextualBalloon extends Plugin {
 			// Just remove given view from the stack.
 			// Just remove given view from the stack.
 			this._stack.delete( view );
 			this._stack.delete( view );
 		}
 		}
+
+		return promise;
 	}
 	}
 
 
 	/**
 	/**
@@ -164,10 +171,12 @@ export default class ContextualBalloon extends Plugin {
 	 *
 	 *
 	 * @private
 	 * @private
 	 * @param {module:ui/view~View} view View to show in the balloon.
 	 * @param {module:ui/view~View} view View to show in the balloon.
+	 * @returns {Promise} A Promise resolved when the child {@link module:ui/view~View#init} is done.
 	 */
 	 */
 	_show( view ) {
 	_show( view ) {
-		this.view.content.add( view );
-		this.view.pin( this._getBalloonPosition() );
+		return this.view.content.add( view ).then( () => {
+			this.view.pin( this._getBalloonPosition() );
+		} );
 	}
 	}
 
 
 	/**
 	/**
@@ -187,6 +196,7 @@ export default class ContextualBalloon extends Plugin {
 	destroy() {
 	destroy() {
 		this.editor.ui.view.body.remove( this.view );
 		this.editor.ui.view.body.remove( this.view );
 		this.view.destroy();
 		this.view.destroy();
+		this._stack.clear();
 		super.destroy();
 		super.destroy();
 	}
 	}
 }
 }

+ 1 - 1
packages/ckeditor5-ui/tests/manual/contextualballoon/contextualballoon.js

@@ -16,7 +16,7 @@ import EssentialsPresets from '@ckeditor/ckeditor5-presets/src/essentials';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
-import ContextualBalloon from '../../../src/contextualballoon';
+import ContextualBalloon from '../../../src/panel/balloon/contextualballoon';
 import ToolbarView from '../../../src/toolbar/toolbarview';
 import ToolbarView from '../../../src/toolbar/toolbarview';
 import ButtonView from '../../../src/button/buttonview';
 import ButtonView from '../../../src/button/buttonview';
 import Template from '../../../src/template';
 import Template from '../../../src/template';

+ 95 - 128
packages/ckeditor5-ui/tests/contextualballoon.js → packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js

@@ -4,14 +4,13 @@
  */
  */
 
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
-import ContextualBalloon from '../src/contextualballoon';
-import BalloonPanelView from '../src/panel/balloon/balloonpanelview';
-import View from '../src/view';
-import Template from '../src/template';
+import ContextualBalloon from '../../../src/panel/balloon/contextualballoon';
+import BalloonPanelView from '../../../src/panel/balloon/balloonpanelview';
+import View from '../../../src/view';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
-/* global document */
+/* global document, setTimeout */
 
 
 describe( 'ContextualBalloon', () => {
 describe( 'ContextualBalloon', () => {
 	let editor, editorElement, balloon, viewA, viewB;
 	let editor, editorElement, balloon, viewA, viewB;
@@ -27,12 +26,21 @@ describe( 'ContextualBalloon', () => {
 			editor = newEditor;
 			editor = newEditor;
 			balloon = editor.plugins.get( ContextualBalloon );
 			balloon = editor.plugins.get( ContextualBalloon );
 
 
-			viewA = new ViewA();
-			viewB = new ViewB();
-
 			// We don't need to test BalloonPanelView attachTo and pin methods it's enough to check if was called with proper data.
 			// We don't need to test BalloonPanelView attachTo and pin methods it's enough to check if was called with proper data.
 			sinon.stub( balloon.view, 'attachTo', () => {} );
 			sinon.stub( balloon.view, 'attachTo', () => {} );
 			sinon.stub( balloon.view, 'pin', () => {} );
 			sinon.stub( balloon.view, 'pin', () => {} );
+
+			viewA = new View();
+			viewB = new View();
+
+			// Add viewA to the pane and init viewB.
+			return Promise.all( [
+				balloon.add( {
+					view: viewA,
+					position: { target: 'fake' }
+				} ),
+				viewB.init(),
+			] );
 		} );
 		} );
 	} );
 	} );
 
 
@@ -62,20 +70,10 @@ describe( 'ContextualBalloon', () => {
 
 
 	describe( 'hasView()', () => {
 	describe( 'hasView()', () => {
 		it( 'should return true when given view is in stack', () => {
 		it( 'should return true when given view is in stack', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			expect( balloon.hasView( viewA ) ).to.true;
 			expect( balloon.hasView( viewA ) ).to.true;
 		} );
 		} );
 
 
 		it( 'should return true when given view is in stack but is not visible', () => {
 		it( 'should return true when given view is in stack but is not visible', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -86,17 +84,39 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should return false when given view is not in stack', () => {
 		it( 'should return false when given view is not in stack', () => {
-			expect( balloon.hasView( viewA ) ).to.false;
+			expect( balloon.hasView( viewB ) ).to.false;
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'add()', () => {
 	describe( 'add()', () => {
-		it( 'should add view to the stack and display in balloon', () => {
-			balloon.add( {
-				view: viewA,
+		it( 'should return promise resolved when view is ready', ( done ) => {
+			const clock = sinon.useFakeTimers();
+
+			const view = {
+				init: () => {
+					return new Promise( ( resolve ) => {
+						setTimeout( () => {
+							resolve();
+						}, 10 );
+					} );
+				},
+				destroy: () => {}
+			};
+
+			const result = balloon.add( {
+				view: view,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
 			} );
 			} );
 
 
+			expect( result ).to.instanceof( Promise );
+
+			result.then( done );
+
+			clock.tick( 11 );
+			clock.restore();
+		} );
+
+		it( 'should add view to the stack and display in balloon attached using given position options', () => {
 			expect( balloon.view.content.length ).to.equal( 1 );
 			expect( balloon.view.content.length ).to.equal( 1 );
 			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewA );
 			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewA );
 			expect( balloon.view.pin.calledOnce ).to.true;
 			expect( balloon.view.pin.calledOnce ).to.true;
@@ -104,20 +124,10 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should pin balloon to the target element', () => {
 		it( 'should pin balloon to the target element', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: document.createElement( 'div' ) }
-			} );
-
 			sinon.assert.calledOnce( balloon.view.pin );
 			sinon.assert.calledOnce( balloon.view.pin );
 		} );
 		} );
 
 
 		it( 'should throw an error when try to add the same view more than once', () => {
 		it( 'should throw an error when try to add the same view more than once', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			expect( () => {
 			expect( () => {
 				balloon.add( {
 				balloon.add( {
 					view: viewA,
 					view: viewA,
@@ -127,11 +137,6 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should add multiple views to he stack and display last one', () => {
 		it( 'should add multiple views to he stack and display last one', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -141,47 +146,31 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewB );
 			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewB );
 		} );
 		} );
 
 
-		it( 'should add multiple views to the stack and keep balloon in the same position', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake', foo: 'bar' }
-			} );
-
-			balloon.add( {
+		it( 'should keep balloon at the same position after adding next view', () => {
+			return balloon.add( {
 				view: viewB,
 				view: viewB,
-				position: { target: 'fake', bar: 'biz' }
-			} );
-
-			expect( balloon.view.pin.calledTwice ).to.true;
+				position: { target: 'other' }
+			} )
+			.then( () => {
+				expect( balloon.view.pin.calledTwice ).to.true;
 
 
-			expect( balloon.view.pin.firstCall.args[ 0 ] ).to.deep.equal( {
-				target: 'fake',
-				foo: 'bar'
-			} );
+				expect( balloon.view.pin.firstCall.args[ 0 ] ).to.deep.equal( {
+					target: 'fake'
+				} );
 
 
-			expect( balloon.view.pin.secondCall.args[ 0 ] ).to.deep.equal( {
-				target: 'fake',
-				foo: 'bar'
+				expect( balloon.view.pin.secondCall.args[ 0 ] ).to.deep.equal( {
+					target: 'fake'
+				} );
 			} );
 			} );
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'visibleView', () => {
 	describe( 'visibleView', () => {
 		it( 'should return data of currently visible view', () => {
 		it( 'should return data of currently visible view', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			expect( balloon.visibleView ).to.equal( viewA );
 			expect( balloon.visibleView ).to.equal( viewA );
 		} );
 		} );
 
 
 		it( 'should return data of currently visible view when there is more than one in the stack', () => {
 		it( 'should return data of currently visible view when there is more than one in the stack', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -191,28 +180,26 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should return `null` when the stack is empty', () => {
 		it( 'should return `null` when the stack is empty', () => {
+			balloon.remove( viewA );
 			expect( balloon.visibleView ).to.null;
 			expect( balloon.visibleView ).to.null;
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'remove()', () => {
 	describe( 'remove()', () => {
+		it( 'should return promise', () => {
+			expect( balloon.remove( viewA ) ).to.instanceof( Promise );
+		} );
+
 		it( 'should remove given view and hide balloon when there is no other view to display', () => {
 		it( 'should remove given view and hide balloon when there is no other view to display', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
+			balloon.view.isVisible = true;
 
 
 			balloon.remove( viewA );
 			balloon.remove( viewA );
 
 
 			expect( balloon.visibleView ).to.null;
 			expect( balloon.visibleView ).to.null;
+			expect( balloon.view.isVisible ).to.false;
 		} );
 		} );
 
 
-		it( 'should remove given view and set previous in the stack as visible when removed view was visible', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
+		it( 'should remove given view and set preceding in the stack as visible when removed view was visible', () => {
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -223,9 +210,22 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.visibleView ).to.equal( viewA );
 			expect( balloon.visibleView ).to.equal( viewA );
 		} );
 		} );
 
 
-		it( 'should remove given view from the stack when view is not visible', () => {
+		it( 'should wait for init of preceding view when was is not ready', ( done ) => {
+			const clock = sinon.useFakeTimers();
+
+			const view = {
+				init: () => {
+					return new Promise( ( resolve ) => {
+						setTimeout( () => {
+							resolve();
+						}, 10 );
+					} );
+				},
+				destroy: () => {}
+			};
+
 			balloon.add( {
 			balloon.add( {
-				view: viewA,
+				view: view,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
 			} );
 			} );
 
 
@@ -234,6 +234,18 @@ describe( 'ContextualBalloon', () => {
 				position: { target: 'fake' }
 				position: { target: 'fake' }
 			} );
 			} );
 
 
+			balloon.remove( viewB ).then( done );
+
+			clock.tick( 11 );
+			clock.restore();
+		} );
+
+		it( 'should remove given view from the stack when view is not visible', () => {
+			balloon.add( {
+				view: viewB,
+				position: { target: 'fake' }
+			} );
+
 			balloon.remove( viewA );
 			balloon.remove( viewA );
 
 
 			expect( balloon.visibleView ).to.equal( viewB );
 			expect( balloon.visibleView ).to.equal( viewB );
@@ -241,26 +253,17 @@ describe( 'ContextualBalloon', () => {
 
 
 		it( 'should throw an error when there is no given view in the stack', () => {
 		it( 'should throw an error when there is no given view in the stack', () => {
 			expect( () => {
 			expect( () => {
-				balloon.remove( viewA );
+				balloon.remove( viewB );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'updatePosition()', () => {
 	describe( 'updatePosition()', () => {
 		it( 'should attach balloon to the target using the same position options as first view in the stack', () => {
 		it( 'should attach balloon to the target using the same position options as first view in the stack', () => {
-			balloon.add( {
-				view: viewA,
-				position: {
-					target: 'fake',
-					foo: 'bar'
-				}
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: {
 				position: {
-					target: 'fake',
-					bar: 'biz'
+					target: 'other'
 				}
 				}
 			} );
 			} );
 
 
@@ -270,59 +273,23 @@ describe( 'ContextualBalloon', () => {
 
 
 			expect( balloon.view.attachTo.calledOnce );
 			expect( balloon.view.attachTo.calledOnce );
 			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( {
 			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( {
-				target: 'fake',
-				foo: 'bar'
-			} );
-		} );
-
-		it( 'should remove given view from the stack when view is not visible', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
-			balloon.add( {
-				view: viewB,
-				position: { target: 'fake' }
+				target: 'fake'
 			} );
 			} );
-
-			balloon.remove( viewA );
-
-			expect( balloon.visibleView ).to.equal( viewB );
 		} );
 		} );
 
 
 		it( 'should throw an error when there is no given view in the stack', () => {
 		it( 'should throw an error when there is no given view in the stack', () => {
 			expect( () => {
 			expect( () => {
-				balloon.remove( viewA );
+				balloon.remove( viewB );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'destroy()', () => {
 	describe( 'destroy()', () => {
-		it( 'should balloon panel remove view from editor body collection', () => {
+		it( 'should remove balloon panel view from editor body collection and clear stack', () => {
 			balloon.destroy();
 			balloon.destroy();
 
 
 			expect( editor.ui.view.body.getIndex( balloon.view ) ).to.equal( -1 );
 			expect( editor.ui.view.body.getIndex( balloon.view ) ).to.equal( -1 );
+			expect( balloon.visibleView ).to.null;
 		} );
 		} );
 	} );
 	} );
 } );
 } );
-
-class ViewA extends View {
-	constructor( locale ) {
-		super( locale );
-
-		this.template = new Template( {
-			tag: 'div'
-		} );
-	}
-}
-
-class ViewB extends View {
-	constructor( locale ) {
-		super( locale );
-
-		this.template = new Template( {
-			tag: 'div'
-		} );
-	}
-}

+ 0 - 11
packages/ckeditor5-ui/theme/components/panel/floatingpanel.scss

@@ -1,11 +0,0 @@
-// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-.ck-floating-panel {
-	position: absolute;
-	display: none;
-
-	&_active {
-		display: block;
-	}
-}

+ 0 - 1
packages/ckeditor5-ui/theme/theme.scss

@@ -14,6 +14,5 @@
 @import 'components/list';
 @import 'components/list';
 @import 'components/label';
 @import 'components/label';
 @import 'components/panel/balloonpanel';
 @import 'components/panel/balloonpanel';
-@import 'components/panel/floatingpanel';
 @import 'components/editor';
 @import 'components/editor';
 @import 'components/toolbar/stickytoolbar';
 @import 'components/toolbar/stickytoolbar';