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

Added manual test for ContextualBalloon.

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

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

@@ -0,0 +1,67 @@
+<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>
+</div>
+
+<style>
+	.ck-editor {
+		margin: 5em auto;
+		max-width: 70%;
+	}
+
+	.plugin {
+		text-align: center;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 10px;
+	}
+
+	.PluginA {
+		padding: 30px;
+	}
+
+	.PluginB {
+		padding: 50px;
+	}
+
+	.PluginC {
+		padding: 40px;
+	}
+
+	.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>

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

@@ -0,0 +1,283 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* 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/classic';
+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 Template from '../../../src/template';
+import View from '../../../src/view';
+import ToolbarView from '../../../src/toolbar/toolbarview';
+import ButtonView from '../../../src/button/buttonview';
+import clickOutsideHandler from '../../../src/bindings/clickoutsidehandler';
+import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
+
+// Plugin view which displays toolbar with next component to open 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 {
+	init() {
+		this._isOpen = false;
+
+		this.editor.editing.view.on( 'selectionChange', () => this._hidePanel() );
+
+		this.view.bind( 'label' ).to( this );
+
+		this.view.on( 'cancel', () => {
+			if ( this.editor.ui.balloon.visible.view === this.view ) {
+				this._hidePanel();
+			}
+		} );
+
+		this.editor.keystrokes.set( 'Esc', ( data, cancel ) => {
+			if ( this.editor.ui.balloon.visible.view === this.view ) {
+				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._isOpen && this.editor.ui.balloon.visible.view === this.view,
+			contextElement: this.view.element,
+			callback: () => this._hidePanel()
+		} );
+	}
+
+	afterInit() {
+		if ( this.buttons ) {
+			const toolbar = new ToolbarView();
+			this.view.toolbar.add( toolbar );
+
+			return toolbar.fillFromConfig( this.buttons, this.editor.ui.componentFactory );
+		}
+
+		return Promise.resolve();
+	}
+
+	_showPanel() {
+		if ( this._isOpen ) {
+			return;
+		}
+
+		const viewDocument = this.editor.editing.view;
+
+		this.editor.ui.balloon.add( {
+			view: this.view,
+			position: {
+				target: viewDocument.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() )
+			}
+		} );
+
+		this._isOpen = true;
+	}
+
+	_hidePanel() {
+		if ( !this._isOpen ) {
+			return;
+		}
+
+		this.editor.ui.balloon.remove( this.view );
+		this._isOpen = false;
+	}
+}
+
+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 toolbar = new ToolbarView();
+	const editingView = editor.editing.view;
+	const arrowVOffset = 20;
+	const positions = {
+		forwardSelection: ( targetRect, balloonRect ) => ( {
+			top: targetRect.bottom + arrowVOffset,
+			left: targetRect.right - balloonRect.width / 2,
+			name: 's'
+		} ),
+
+		backwardSelection: ( targetRect, balloonRect ) => ( {
+			top: targetRect.top - balloonRect.height - arrowVOffset,
+			left: targetRect.left - balloonRect.width / 2,
+			name: 'n'
+		} )
+	};
+
+	let isOpen = false;
+
+	// 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();
+
+			isOpen = true;
+
+			editor.ui.balloon.add( {
+				view: toolbar,
+				position: {
+					target: isBackward ? rangeRects.item( 0 ) : rangeRects.item( rangeRects.length - 1 ),
+					positions: [ positions[ isBackward ? 'backwardSelection' : 'forwardSelection' ] ]
+				}
+			} );
+		}
+	} );
+
+	// Remove toolbar from balloon and mark as not opened.
+	function close() {
+		if ( isOpen ) {
+			editor.ui.balloon.remove( toolbar );
+		}
+
+		isOpen = false;
+	}
+}
+
+// Finally the editor.
+ClassicEditor.create( document.querySelector( '#editor' ), {
+	plugins: [ EssentialsPresets, Paragraph, PluginA, PluginB, PluginC, PluginD ],
+	toolbar: [ 'PluginA', 'PluginB' ]
+} )
+.then( editor => {
+	window.editor = editor;
+	createContextualToolbar( editor );
+} )
+.catch( err => {
+	console.error( err.stack );
+} );

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

@@ -0,0 +1,8 @@
+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.
+6. Check if clicking at button in the editor toolbar rests stack. Select text and open `PluginB` using contextual toolbar,
+then open PluginC. After that without changing selection click PluginA on the editor toolbar. You should see
+`ContextualToolbar` attached to the selection.