From b77b183dd85af7fddfd4d284e955a977efb6c013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Kup=C5=9B?= Date: Fri, 27 Jan 2017 09:49:21 +0100 Subject: [PATCH] Fixed integration between alternate text panel and image toolbar. --- src/imagealternatetext/imagealternatetext.js | 36 ++++--- src/imagetoolbar.js | 14 ++- .../imagealternatetext/imagealternatetext.js | 94 ++++++++++++++----- tests/imagetoolbar.js | 11 +++ 4 files changed, 116 insertions(+), 39 deletions(-) diff --git a/src/imagealternatetext/imagealternatetext.js b/src/imagealternatetext/imagealternatetext.js index aa6bfeb4..59120aab 100644 --- a/src/imagealternatetext/imagealternatetext.js +++ b/src/imagealternatetext/imagealternatetext.js @@ -11,6 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import ImageAlternateTextEngine from './imagealternatetextengine'; import escPressHandler from '@ckeditor/ckeditor5-ui/src/bindings/escpresshandler'; +import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler'; import ImageToolbar from '../imagetoolbar'; import AlternateTextFormView from './ui/alternatetextformview'; import ImageBalloonPanel from '../ui/imageballoonpanelview'; @@ -99,6 +100,21 @@ export default class ImageAlternateText extends Plugin { this._hideBalloonPanel(); } ); + // If image toolbar is present - hide it when alternate text balloon is visible. + const imageToolbar = editor.plugins.get( ImageToolbar ); + + if ( imageToolbar ) { + this.listenTo( panel, 'change:isVisible', () => { + if ( panel.isVisible ) { + imageToolbar.hide(); + imageToolbar.isEnabled = false; + } else { + imageToolbar.show(); + imageToolbar.isEnabled = true; + } + } ); + } + this.listenTo( form, 'cancel', () => this._hideBalloonPanel() ); // Close on `ESC` press. @@ -108,6 +124,14 @@ export default class ImageAlternateText extends Plugin { callback: () => this._hideBalloonPanel() } ); + // Close on click outside of balloon panel element. + clickOutsideHandler( { + emitter: panel, + activator: () => panel.isVisible, + contextElement: panel.element, + callback: () => this._hideBalloonPanel() + } ); + return Promise.all( [ panel.content.add( form ), editor.ui.view.body.add( panel ) @@ -122,12 +146,6 @@ export default class ImageAlternateText extends Plugin { _showBalloonPanel() { const editor = this.editor; const command = editor.commands.get( 'imageAlternateText' ); - const imageToolbar = editor.plugins.get( ImageToolbar ); - - if ( imageToolbar ) { - imageToolbar.hide(); - } - this.form.lebeledInput.value = command.value || ''; this.balloonPanel.attach(); this.form.lebeledInput.select(); @@ -142,11 +160,5 @@ export default class ImageAlternateText extends Plugin { const editor = this.editor; this.balloonPanel.detach(); editor.editing.view.focus(); - - const imageToolbar = editor.plugins.get( ImageToolbar ); - - if ( imageToolbar ) { - imageToolbar.show(); - } } } diff --git a/src/imagetoolbar.js b/src/imagetoolbar.js index 8b091c14..5bf8029e 100644 --- a/src/imagetoolbar.js +++ b/src/imagetoolbar.js @@ -30,6 +30,14 @@ export default class ImageToolbar extends Plugin { super( editor ); editor.config.set( 'image.defaultToolbar', [] ); + + /** + * 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; } /** @@ -70,12 +78,14 @@ export default class ImageToolbar extends Plugin { // Show balloon panel each time image widget is selected. this.listenTo( this.editor.editing.view, 'render', () => { - this.show(); + if ( this.isEnabled ) { + this.show(); + } }, { 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 ) { + if ( !was && is && this.isEnabled ) { this.show(); } } ); diff --git a/tests/imagealternatetext/imagealternatetext.js b/tests/imagealternatetext/imagealternatetext.js index 9fb76dc8..bf23d782 100644 --- a/tests/imagealternatetext/imagealternatetext.js +++ b/tests/imagealternatetext/imagealternatetext.js @@ -13,6 +13,8 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global'; import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; +/* global Event */ + describe( 'ImageAlternateText', () => { let editor, plugin, command, balloonPanel, form; @@ -72,31 +74,6 @@ describe( 'ImageAlternateText', () => { sinon.assert.calledOnce( spy ); } ); - it( 'should hide ImageToolbar on execute', () => { - const editorElement = global.document.createElement( 'div' ); - global.document.body.appendChild( editorElement ); - - return ClassicTestEditor.create( editorElement, { - plugins: [ ImageAlternateText, Image, ImageToolbar ], - image: { - toolbar: [ 'imageAlternateText' ] - } - } ) - .then( newEditor => { - newEditor.editing.view.attachDomRoot( editorElement ); - const button = newEditor.ui.componentFactory.create( 'imageAlternateText' ); - const toolbarPlugin = newEditor.plugins.get( ImageToolbar ); - - const spy = sinon.spy( toolbarPlugin, 'hide' ); - setData( newEditor.document, '[foo bar]' ); - button.fire( 'execute' ); - - sinon.assert.calledOnce( spy ); - - newEditor.destroy(); - } ); - } ); - it( 'should set alt attribute value to textarea and select it', () => { const spy = sinon.spy( form.lebeledInput, 'select' ); setData( editor.document, '[foo bar]' ); @@ -175,6 +152,73 @@ describe( 'ImageAlternateText', () => { sinon.assert.called( hidePanelSpy ); } ); } ); + + describe( 'mouse', () => { + it( 'should close and not focus editable on click outside the panel', () => { + balloonPanel.isVisible = true; + global.document.body.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) ); + + sinon.assert.called( hidePanelSpy ); + } ); + + it( 'should not close on click inside the panel', () => { + balloonPanel.isVisible = true; + balloonPanel.element.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) ); + + sinon.assert.notCalled( hidePanelSpy ); + } ); + } ); + } ); + } ); + + describe( 'working with ImageToolbar', () => { + let editor, button, imageToolbarPlugin, plugin; + + beforeEach( () => { + const editorElement = global.document.createElement( 'div' ); + global.document.body.appendChild( editorElement ); + + return ClassicTestEditor.create( editorElement, { + plugins: [ ImageAlternateText, Image, ImageToolbar ], + image: { + toolbar: [ 'imageAlternateText' ] + } + } ) + .then( newEditor => { + editor = newEditor; + editor.editing.view.attachDomRoot( editorElement ); + button = newEditor.ui.componentFactory.create( 'imageAlternateText' ); + imageToolbarPlugin = newEditor.plugins.get( ImageToolbar ); + plugin = editor.plugins.get( ImageAlternateText ); + } ); + } ); + + afterEach( () => editor.destroy() ); + + it( 'should hide ImageToolbar when visible', () => { + setData( editor.document, '[foo bar]' ); + const spy = sinon.spy( imageToolbarPlugin, 'hide' ); + button.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + } ); + + it( 'ImageToolbar should not show when alternate text panel is visible', () => { + setData( editor.document, '[foo bar]' ); + button.fire( 'execute' ); + const spy = sinon.spy( imageToolbarPlugin, 'show' ); + editor.editing.view.render(); + + sinon.assert.notCalled( spy ); + } ); + + it( 'ImageToolbar should show when alternate text panel is not visible', () => { + setData( editor.document, '[foo bar]' ); + button.fire( 'execute' ); + const spy = sinon.spy( imageToolbarPlugin, 'show' ); + plugin.balloonPanel.isVisible = false; + + sinon.assert.called( spy ); } ); } ); } ); diff --git a/tests/imagetoolbar.js b/tests/imagetoolbar.js index 57244530..aa30a9c0 100644 --- a/tests/imagetoolbar.js +++ b/tests/imagetoolbar.js @@ -93,6 +93,17 @@ describe( 'ImageToolbar', () => { sinon.assert.calledOnce( spy ); } ); + it( 'should not show the panel automatically when it is disabled', () => { + plugin.isEnabled = false; + setData( doc, '[]' ); + editor.ui.focusTracker.isFocused = true; + const spy = sinon.spy( plugin, 'show' ); + + editingView.render(); + + sinon.assert.notCalled( spy ); + } ); + it( 'should not show the panel when editor looses focus', () => { editor.ui.focusTracker.isFocused = true; const spy = sinon.spy( plugin, 'show' );