瀏覽代碼

Merge branch 'master' into t/ckeditor5-dev/209

Kamil Piechaczek 8 年之前
父節點
當前提交
03686039e6

+ 3 - 3
packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js

@@ -159,14 +159,14 @@ export default class ContextualBalloon extends Plugin {
 	}
 
 	/**
-	 * Updates the position of the balloon panel according to the given position data
-	 * or position data of the first view in the stack.
+	 * Updates the position of the balloon using position data of the first visible view in the stack.
+	 * When new position data is given then position data of currently visible panel will be updated.
 	 *
 	 * @param {module:utils/dom/position~Options} [position] position options.
 	 */
 	updatePosition( position ) {
 		if ( position ) {
-			this._stack.values().next().value.position = position;
+			this._stack.get( this.visibleView ).position = position;
 		}
 
 		this.view.attachTo( this._getBalloonPosition() );

+ 14 - 58
packages/ckeditor5-ui/tests/manual/contextualballoon/contextualballoon.html

@@ -1,21 +1,18 @@
 <div id="editor">
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
-	<p>Line of text</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
+	<p>Line of text, line of text, line of text, line of text.</p>
 </div>
 
 <style>
@@ -23,45 +20,4 @@
 		margin: 5em auto;
 		max-width: 70%;
 	}
-
-	.plugin {
-		text-align: center;
-		display: flex;
-		align-items: center;
-		justify-content: center;
-		padding: 10px;
-	}
-
-	.PluginA {
-		padding: 1em;
-	}
-
-	.PluginB {
-		padding: 2em;
-	}
-
-	.PluginC {
-		padding: 3em;
-	}
-
-	.plugin * {
-		text-align: center;
-	}
-
-	.plugin h2 {
-		font-weight: bold;
-		font-size: 16px;
-	}
-
-	.plugin h3 {
-		font-size: 14px;
-	}
-
-	.plugin .toolbar {
-		border: solid 1px #e1e1e1;
-		box-shadow: inset 0 0 5px rgba( 0, 0, 0, .3 );
-		margin: 10px 5px;
-		padding: 15px;
-		border-radius: 5px;
-	}
 </style>

+ 5 - 252
packages/ckeditor5-ui/tests/manual/contextualballoon/contextualballoon.js

@@ -5,265 +5,18 @@
 
 /* globals window, document, console:false */
 
-// This test implements contextual toolbar and few plugins which can be opened inside of the toolbar.
-
-// Code of this manual test should be successfully reduced when more of
-// CKE5 plugins will be integrated with ContextualBalloon and when
-// ContextualToolbar plugin will land as CKE5 plugin.
-
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
-import EssentialsPresets from '@ckeditor/ckeditor5-presets/src/essentials';
-import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-
-import BalloonPanelView from '../../../src/panel/balloon/balloonpanelview';
-import ContextualBalloon from '../../../src/panel/balloon/contextualballoon';
-import ToolbarView from '../../../src/toolbar/toolbarview';
-import ButtonView from '../../../src/button/buttonview';
-import Template from '../../../src/template';
-import View from '../../../src/view';
-import clickOutsideHandler from '../../../src/bindings/clickoutsidehandler';
-import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
-
-// Plugin view which displays toolbar with component to open next
-// plugin inside and cancel button to close currently visible plugin.
-class ViewA extends View {
-	constructor( locale ) {
-		super( locale );
-
-		this.keystrokes = new KeystrokeHandler();
-
-		this.toolbar = this.createCollection();
-
-		this.keystrokes.set( 'Esc', ( data, cancel ) => {
-			this.fire( 'cancel' );
-			cancel();
-		} );
-
-		this.cancel = new ButtonView( locale );
-		this.cancel.label = 'Cancel';
-		this.cancel.withText = true;
-		this.cancel.on( 'execute', () => this.fire( 'cancel' ) );
-
-		this.template = new Template( {
-			tag: 'div',
-
-			attributes: {
-				class: [ 'plugin', this.bindTemplate.to( 'label' ) ],
-				tabindex: '-1'
-			},
-
-			children: [
-				{
-					tag: 'div',
-
-					children: [
-						{
-							tag: 'h2',
-
-							children: [
-								{ text: this.bindTemplate.to( 'label' ) },
-							]
-						},
-						{
-							tag: 'div',
-							attributes: {
-								class: [ 'toolbar' ]
-							},
-							children: [
-								{
-									tag: 'h3',
-									children: [
-										{ text: 'Open:' },
-									]
-								},
-								{
-									tag: 'div',
-									children: this.toolbar
-								},
-							]
-						},
-						this.cancel
-					]
-				}
-			]
-		} );
-	}
-
-	init() {
-		this.keystrokes.listenTo( this.element );
-
-		return super.init();
-	}
-}
-
-// Generic plugin class.
-class PluginGeneric extends Plugin {
-	static get requires() {
-		return [ ContextualBalloon ];
-	}
-
-	init() {
-		this._balloon = this.editor.plugins.get( ContextualBalloon );
-
-		this.editor.editing.view.on( 'selectionChange', () => this._hidePanel() );
-
-		this.view.bind( 'label' ).to( this );
-
-		this.view.on( 'cancel', () => {
-			if ( this._isVisible ) {
-				this._hidePanel();
-			}
-		} );
-
-		this.editor.keystrokes.set( 'Esc', ( data, cancel ) => {
-			if ( this._isVisible ) {
-				this._hidePanel();
-			}
-
-			cancel();
-		} );
-
-		this.editor.ui.componentFactory.add( this.label, locale => {
-			const button = new ButtonView( locale );
-
-			button.label = this.label;
-			button.withText = true;
-			this.listenTo( button, 'execute', () => this._showPanel() );
-
-			return button;
-		} );
-
-		clickOutsideHandler( {
-			emitter: this.view,
-			activator: () => this._isVisible,
-			contextElements: [ this.view.element ],
-			callback: () => this._hidePanel()
-		} );
-	}
-
-	get _isVisible() {
-		return this._balloon.visibleView === this.view;
-	}
-
-	afterInit() {
-		if ( this.buttons ) {
-			const toolbar = new ToolbarView();
-			this.view.toolbar.add( toolbar );
-
-			return toolbar.fillFromConfig( this.buttons, this.editor.ui.componentFactory );
-		}
-	}
-
-	_showPanel() {
-		if ( this._balloon.hasView( this.view ) ) {
-			return;
-		}
-
-		const viewDocument = this.editor.editing.view;
-
-		this._balloon.add( {
-			view: this.view,
-			position: {
-				target: viewDocument.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() )
-			}
-		} );
-	}
-
-	_hidePanel() {
-		if ( !this._balloon.hasView( this.view ) ) {
-			return;
-		}
-
-		this._balloon.remove( this.view );
-	}
-}
-
-class PluginA extends PluginGeneric {
-	init() {
-		this.label = 'PluginA';
-		this.view = new ViewA( this.editor.locale );
-		this.buttons = [ 'PluginB' ];
-
-		super.init();
-	}
-}
-
-class PluginB extends PluginGeneric {
-	init() {
-		this.label = 'PluginB';
-		this.view = new ViewA( this.editor.locale );
-		this.buttons = [ 'PluginC' ];
-
-		super.init();
-	}
-}
-
-class PluginC extends PluginGeneric {
-	init() {
-		this.label = 'PluginC';
-		this.view = new ViewA( this.editor.locale );
-		this.buttons = [ 'PluginD' ];
-
-		super.init();
-	}
-}
-
-class PluginD extends PluginGeneric {
-	init() {
-		this.label = 'PluginD';
-		this.view = new ViewA( this.editor.locale );
-
-		super.init();
-	}
-}
-
-// Create contextual toolbar.
-function createContextualToolbar( editor ) {
-	const balloon = editor.plugins.get( ContextualBalloon );
-	const toolbar = new ToolbarView();
-	const editingView = editor.editing.view;
-	const defaultPositions = BalloonPanelView.defaultPositions;
-
-	// Add plugins to the toolbar.
-	toolbar.fillFromConfig( [ 'PluginA', 'PluginB' ], editor.ui.componentFactory );
-
-	// Close toolbar when selection is changing.
-	editor.listenTo( editingView, 'selectionChange', () => close() );
-
-	// Handle when selection stop changing.
-	editor.listenTo( editingView, 'selectionChangeDone', () => {
-		// This implementation assumes that only non–collapsed selections gets the contextual toolbar.
-		if ( !editingView.selection.isCollapsed ) {
-			const isBackward = editingView.selection.isBackward;
-			const rangeRects = editingView.domConverter.viewRangeToDom( editingView.selection.getFirstRange() ).getClientRects();
-
-			balloon.add( {
-				view: toolbar,
-				position: {
-					target: isBackward ? rangeRects.item( 0 ) : rangeRects.item( rangeRects.length - 1 ),
-					positions: [ defaultPositions[ isBackward ? 'northArrowSouth' : 'southArrowNorth' ] ]
-				}
-			} );
-		}
-	} );
-
-	// Remove toolbar from balloon.
-	function close() {
-		if ( balloon.hasView( toolbar ) ) {
-			balloon.remove( toolbar );
-		}
-	}
-}
+import ArticlePresets from '@ckeditor/ckeditor5-presets/src/article';
+import ContextualToolbar from '@ckeditor/ckeditor5-ui/src/toolbar/contextual/contextualtoolbar';
 
 // Finally the editor.
 ClassicEditor.create( document.querySelector( '#editor' ), {
-	plugins: [ EssentialsPresets, Paragraph, PluginA, PluginB, PluginC, PluginD ],
-	toolbar: [ 'PluginA', 'PluginB' ]
+	plugins: [ ArticlePresets, ContextualToolbar ],
+	toolbar: [ 'bold', 'link' ],
+	contextualToolbar: [ 'bold', 'link' ]
 } )
 .then( editor => {
 	window.editor = editor;
-	createContextualToolbar( editor );
 } )
 .catch( err => {
 	console.error( err.stack );

+ 4 - 5
packages/ckeditor5-ui/tests/manual/contextualballoon/contextualballoon.md

@@ -1,5 +1,4 @@
-1. Select some of text and play with the plugins.
-2. Keep opening panels inside the same balloon.
-3. Check if `Cancel` button closes plugins one by one.
-4. Check if `Esc` press closes plugins one by one (only when plugin or editable is focused).
-5. Check if panels stay opened in the same position as initial when more panels are open in the same balloon.
+1. Select some text in the middle of the content - contextual toolbar should show up.
+2. Click link icon in the contextual toolbar - link balloon should open at the position as contextual toolbar.
+3. Close link balloon (Esc press or Cancel button) - contextual toolbar should show up.
+4. Repeat this for backward selection.

+ 13 - 0
packages/ckeditor5-ui/tests/manual/contextualballoon/externalchanges.html

@@ -0,0 +1,13 @@
+<h2>External changes (insert/edit)</h2>
+<p><button id="button-insert">Start external changes</button></p>
+<div id="editor-insert">
+	<p>EXTERNAL CHANGES WILL START HERE -> This specification defines the 5th <a href="http://ckeditor.com">major revision</a> of the core language of the [Select this text]: the Hypertext Markup Language (HTML).</p>
+</div>
+
+<h2>External changes (delete)</h2>
+<p><button id="button-delete">Start external changes</button></p>
+<div id="editor-delete">
+	<p>A first paragraph.</p>
+	<p>THIS PARAGRAPH WILL BE REMOVED <a href="http://ckeditor.com">major revision</a>. [Select this text].</p>
+	<p>A third paragraph.</p>
+</div>

+ 117 - 0
packages/ckeditor5-ui/tests/manual/contextualballoon/externalchanges.js

@@ -0,0 +1,117 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console:false, document, setTimeout */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ContextualToolbar from '@ckeditor/ckeditor5-ui/src/toolbar/contextual/contextualtoolbar';
+import ArticlePresets from '@ckeditor/ckeditor5-presets/src/article';
+
+import Element from '@ckeditor/ckeditor5-engine/src/model/element';
+import Text from '@ckeditor/ckeditor5-engine/src/model/text';
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
+
+// Editor for the external insert.
+ClassicEditor.create( document.querySelector( '#editor-insert' ), {
+	plugins: [ ArticlePresets, ContextualToolbar ],
+	toolbar: [ 'bold', 'link' ],
+	contextualToolbar: [ 'bold', 'link' ]
+} )
+.then( editor => {
+	const element = document.querySelector( '#button-insert' );
+
+	element.addEventListener( 'click', () => {
+		element.disabled = true;
+		startExternalInsert( editor );
+	} );
+} )
+.catch( err => console.error( err.stack ) );
+
+// Editor for the external delete.
+ClassicEditor.create( document.querySelector( '#editor-delete' ), {
+	plugins: [ ArticlePresets, ContextualToolbar ],
+	toolbar: [ 'bold', 'link' ],
+	contextualToolbar: [ 'bold', 'link' ]
+} )
+	.then( editor => {
+		const element = document.querySelector( '#button-delete' );
+
+		element.addEventListener( 'click', () => {
+			element.disabled = true;
+			startExternalDelete( editor );
+		} );
+	} )
+	.catch( err => console.error( err.stack ) );
+
+function wait( delay ) {
+	return new Promise( resolve => {
+		setTimeout( () => resolve(), delay );
+	} );
+}
+
+function startExternalInsert( editor ) {
+	const document = editor.document;
+	const bath = document.batch( 'transparent' );
+
+	function type( path, text ) {
+		return new Promise( resolve => {
+			let position = new Position( document.getRoot(), path );
+			let index = 0;
+
+			function typing() {
+				wait( 40 ).then( () => {
+					document.enqueueChanges( () => {
+						bath.insert( position, new Text( text[ index ] ) );
+						position = position.getShiftedBy( 1 );
+
+						const nextLetter = text[ ++index ];
+
+						if ( nextLetter ) {
+							typing( nextLetter );
+						} else {
+							index = 0;
+							resolve();
+						}
+					} );
+				} );
+			}
+
+			typing();
+		} );
+	}
+
+	function insertNewLine( path ) {
+		return wait( 200 ).then( () => {
+			document.enqueueChanges( () => {
+				bath.insert( new Position( document.getRoot(), path ), new Element( 'paragraph' ) );
+			} );
+
+			return Promise.resolve();
+		} );
+	}
+
+	wait( 3000 )
+		.then( () => type( [ 0, 36 ], 'This specification defines the 5th major revision of the core language of the World Wide Web. ' ) )
+		.then( () => insertNewLine( [ 0 ] ) )
+		.then( () => type( [ 0, 0 ], 'a' ) )
+		.then( () => insertNewLine( [ 1 ] ) )
+		.then( () => type( [ 1, 0 ], 'b' ) )
+		.then( () => insertNewLine( [ 2 ] ) )
+		.then( () => type( [ 2, 0 ], 'c' ) )
+		.then( () => insertNewLine( [ 0 ] ) )
+		.then( () => type( [ 0, 0 ], 'DONE :)' ) );
+}
+
+function startExternalDelete( editor ) {
+	const document = editor.document;
+	const bath = document.batch( 'transparent' );
+
+	wait( 3000 ).then( () => {
+		document.enqueueChanges( () => {
+			bath.remove( Range.createFromPositionAndShift( new Position( document.getRoot(), [ 1 ] ), 1 ) );
+		} );
+	} );
+}

+ 18 - 0
packages/ckeditor5-ui/tests/manual/contextualballoon/externalchanges.md

@@ -0,0 +1,18 @@
+## Link panel opened from ContextualToolbar should be updated on external changes.
+
+### Create #1:
+
+1. Click **Start external changes** then quickly select some text which is not a link yet and press the Link button in the toolbar (the link editing balloon should show up).
+2. Observe if the balloon remains attached to the selection.
+3. Repeat this for a backward selection.
+
+### Create #2:
+
+1. Click **Start external changes** then quickly select text to wrap place where external changes will be inserted e.g `HE[RE -> The spe]cification` then open link click by link icon in contextual toolbar.
+2. Observe if the balloon remains attached to the selection.
+3. Repeat this for a backward selection.
+
+### Delete:
+
+1. Click **Start external changes** then quickly select some text in the **second paragraph** press the Link button in the contextual toolbar (the link editing balloon should show up).
+2. Observe if the balloon remains attached to the the selection after the removal.

+ 20 - 2
packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js

@@ -267,18 +267,36 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'fake' } );
 		} );
 
-		it( 'should attach balloon to the target using new position options', () => {
+		it( 'should set given position to the currently visible view and use position from the first view in the stack #1', () => {
 			balloon.view.attachTo.reset();
 
 			balloon.updatePosition( { target: 'new' } );
 
 			expect( balloon.view.attachTo.calledOnce );
 			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'new' } );
+		} );
+
+		it( 'should set given position to the currently visible view and use position from the first view in the stack #2', () => {
+			balloon.add( {
+				view: viewB,
+				position: {
+					target: 'other'
+				}
+			} );
+
+			balloon.view.attachTo.reset();
+
+			balloon.updatePosition( { target: 'new' } );
+
+			expect( balloon.view.attachTo.calledOnce );
+			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'fake' } );
+
+			balloon.remove( viewA );
 
 			balloon.updatePosition();
 
 			expect( balloon.view.attachTo.calledTwice );
-			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'new' } );
+			expect( balloon.view.attachTo.secondCall.args[ 0 ] ).to.deep.equal( { target: 'new' } );
 		} );
 
 		it( 'should throw an error when there is no given view in the stack', () => {