|
|
@@ -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 );
|
|
|
+} );
|