From 6d6f600cb71a764fae77d48235e0ce599a69c42d Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Thu, 13 Sep 2018 16:49:57 +0200 Subject: [PATCH 01/27] WIP - WidgetToolbar. --- src/widgettoolbar.js | 151 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/widgettoolbar.js diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js new file mode 100644 index 00000000..6a618cf0 --- /dev/null +++ b/src/widgettoolbar.js @@ -0,0 +1,151 @@ +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; +import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; +import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview'; +import { isWidget } from './utils'; + +export default class WidgetToolbar extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ ContextualBalloon ]; + } + + /** + * @inheritDoc + */ + static get pluginName() { + return 'WidgetToolbar'; + } + + /** + * @inheritDoc + */ + init() { + const editor = this.editor; + const balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); + + // Disable the default balloon toolbar for all widgets. + this.listenTo( balloonToolbar, 'show', evt => { + if ( isWidgetSelected( editor.editing.view.document.selection ) ) { + evt.stop(); + } + }, { priority: 'high' } ); + + this._balloon = this.editor.plugins.get( 'ContextualBalloon' ); + + this._toolbars = []; + + this.listenTo( editor.ui, 'update', () => { + this._updateToolbars(); + } ); + + // UI#update is not fired after focus is back in editor, we need to check if balloon panel should be visible. + this.listenTo( editor.ui.focusTracker, 'change:isFocused', () => { + this._updateToolbars(); + }, { priority: 'low' } ); + } + + add( { toolbarItems, isSelected, balloonClassName = 'ck-toolbar-container' } ) { + const editor = this.editor; + + if ( !toolbarItems ) { + return; + } + + const toolbarView = new ToolbarView(); + + toolbarView.fillFromConfig( toolbarItems, editor.ui.componentFactory ); + + this._toolbars.push( { + view: toolbarView, + isSelected, + balloonClassName, + } ); + } + + _updateToolbars() { + for ( const toolbar of this._toolbars ) { + if ( !this.editor.ui.focusTracker.isFocused || !toolbar.isSelected( this.editor.editing.view.document.selection ) ) { + this._hideToolbar( toolbar ); + } else { + this._showToolbar( toolbar ); + } + } + } + + _hideToolbar( toolbar ) { + if ( !this._isVisible( toolbar ) ) { + return; + } + + this._balloon.remove( toolbar.view ); + } + + _showToolbar( toolbar ) { + if ( this._isVisible( toolbar ) ) { + repositionContextualBalloon( this.editor ); + } else if ( !this._balloon.hasView( toolbar.view ) ) { + this._balloon.add( { + view: toolbar.view, + position: getBalloonPositionData( this.editor ), + balloonClassName: toolbar.balloonClassName, + } ); + } + } + + _isVisible( toolbar ) { + return this._balloon.visibleView == toolbar.view; + } +} + +function repositionContextualBalloon( editor ) { + const balloon = editor.plugins.get( 'ContextualBalloon' ); + const position = getBalloonPositionData( editor ); + + balloon.updatePosition( position ); +} + +function getBalloonPositionData( editor ) { + const editingView = editor.editing.view; + const defaultPositions = BalloonPanelView.defaultPositions; + let widget = getParentWidget( editingView.document.selection ); + + return { + target: editingView.domConverter.viewToDom( widget ), + positions: [ + defaultPositions.northArrowSouth, + defaultPositions.northArrowSouthWest, + defaultPositions.northArrowSouthEast, + defaultPositions.southArrowNorth, + defaultPositions.southArrowNorthWest, + defaultPositions.southArrowNorthEast + ] + }; +} + +function getParentWidget( selection ) { + const selectedElement = selection.getSelectedElement(); + + if ( selectedElement && isWidget( selectedElement ) ) { + return selectedElement; + } + + const position = selection.getFirstPosition(); + let parent = position.parent; + + while ( parent ) { + if ( isWidget( parent ) ) { + return parent; + } + + parent = parent.parent; + } +} + +function isWidgetSelected( selection ) { + const viewElement = selection.getSelectedElement(); + + return !!( viewElement && isWidget( viewElement ) ); +} From 0b68ce1529a3acfb29c8d522d05eae8392d0e683 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Thu, 13 Sep 2018 18:40:55 +0200 Subject: [PATCH 02/27] Added API docs for the WidgetToolbar. --- src/widgettoolbar.js | 70 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js index 6a618cf0..8320258d 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbar.js @@ -4,6 +4,34 @@ import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview'; import { isWidget } from './utils'; +const defaultBalloonClassName = 'ck-toolbar-container'; + +/** + * Widget toolbar plugin. It ease the process of creating widget toolbars by handling the whole rendering process and providing concise API. + * + * Creating toolbar for the widget bases on the {@link ~add()} method. TODO. + * + * This plugin added to the plugin list directly or indirectly prevents showing up + * the {@link module:ui/toolbar/balloontoolbar~BalloonToolbar} toolbar and the widget toolbar at the same time. + * + * Usage example comes from {@link module:image/imagetoolbar~ImageToolbar}: + * + * class ImageToolbar extends Plugin { + * static get requires() { + * return [ WidgetToolbar ]; + * } + * + * afterInit() { + * const editor = this.editor; + * const widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); + * + * widgetToolbar.add( { + * toolbarItems: editor.config.get( 'image.toolbar' ) + * isSelected: isImageWidgetSelected + * } ); + * } + * } + */ export default class WidgetToolbar extends Plugin { /** * @inheritDoc @@ -38,16 +66,29 @@ export default class WidgetToolbar extends Plugin { this._toolbars = []; this.listenTo( editor.ui, 'update', () => { - this._updateToolbars(); + this._updateToolbarsVisibility(); } ); // UI#update is not fired after focus is back in editor, we need to check if balloon panel should be visible. this.listenTo( editor.ui.focusTracker, 'change:isFocused', () => { - this._updateToolbars(); + this._updateToolbarsVisibility(); }, { priority: 'low' } ); } - add( { toolbarItems, isSelected, balloonClassName = 'ck-toolbar-container' } ) { + /** + * Adds toolbar to the WidgetToolbar's collection. It renders it in the `ContextualBalloon` based on the value of the invoked + * `isSelected` function. Toolbar items are gathered from `toolbarItems` array. + * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. + * + * Note: This method should be called in the `module:core/plugin/Plugin~afterInit` to make sure that plugins for toolbar items + * will be already loaded and available in the UI component factory. + * + * @param {Object} options + * @param {Array.} options.toolbarItems Array of toolbar items. + * @param {Function} options.isSelected Callback which specifies when the toolbar should be visible for the widget. + * @param {String} [options.balloonClassName] CSS class for the widget balloon. + */ + add( { toolbarItems, isSelected, balloonClassName = defaultBalloonClassName } ) { const editor = this.editor; if ( !toolbarItems ) { @@ -65,7 +106,12 @@ export default class WidgetToolbar extends Plugin { } ); } - _updateToolbars() { + /** + * Iterates over stored toolbars and makes them visible or hidden. + * + * @private + */ + _updateToolbarsVisibility() { for ( const toolbar of this._toolbars ) { if ( !this.editor.ui.focusTracker.isFocused || !toolbar.isSelected( this.editor.editing.view.document.selection ) ) { this._hideToolbar( toolbar ); @@ -75,6 +121,12 @@ export default class WidgetToolbar extends Plugin { } } + /** + * Hides given toolbar. + * + * @private + * @param {Object} toolbar + */ _hideToolbar( toolbar ) { if ( !this._isVisible( toolbar ) ) { return; @@ -83,6 +135,12 @@ export default class WidgetToolbar extends Plugin { this._balloon.remove( toolbar.view ); } + /** + * Shows up or repositions given toolbar. + * + * @private + * @param {Object} toolbar + */ _showToolbar( toolbar ) { if ( this._isVisible( toolbar ) ) { repositionContextualBalloon( this.editor ); @@ -95,6 +153,10 @@ export default class WidgetToolbar extends Plugin { } } + /** + * @private + * @param {Object} toolbar + */ _isVisible( toolbar ) { return this._balloon.visibleView == toolbar.view; } From b16ff1b6d28f12de4e7d18ee35e19b67ef143256 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 14 Sep 2018 11:52:19 +0200 Subject: [PATCH 03/27] Various fixes to widget toolbar. --- src/widgettoolbar.js | 78 ++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js index 8320258d..1e151713 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbar.js @@ -9,7 +9,7 @@ const defaultBalloonClassName = 'ck-toolbar-container'; /** * Widget toolbar plugin. It ease the process of creating widget toolbars by handling the whole rendering process and providing concise API. * - * Creating toolbar for the widget bases on the {@link ~add()} method. TODO. + * Creating toolbar for the widget bases on the {@link ~add()} method. TODO * * This plugin added to the plugin list directly or indirectly prevents showing up * the {@link module:ui/toolbar/balloontoolbar~BalloonToolbar} toolbar and the widget toolbar at the same time. @@ -27,7 +27,7 @@ const defaultBalloonClassName = 'ck-toolbar-container'; * * widgetToolbar.add( { * toolbarItems: editor.config.get( 'image.toolbar' ) - * isSelected: isImageWidgetSelected + * isVisible: isImageWidgetSelected * } ); * } * } @@ -54,17 +54,28 @@ export default class WidgetToolbar extends Plugin { const editor = this.editor; const balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); - // Disable the default balloon toolbar for all widgets. - this.listenTo( balloonToolbar, 'show', evt => { - if ( isWidgetSelected( editor.editing.view.document.selection ) ) { - evt.stop(); - } - }, { priority: 'high' } ); + // Disables the default balloon toolbar for all widgets. + if ( balloonToolbar ) { + this.listenTo( balloonToolbar, 'show', evt => { + if ( isWidgetSelected( editor.editing.view.document.selection ) ) { + evt.stop(); + } + }, { priority: 'high' } ); + } + /** + * A map of toolbars. + * + * @protected + * @member {Map.} #_toolbars + */ + this._toolbars = new Map(); + + /** + * @private + */ this._balloon = this.editor.plugins.get( 'ContextualBalloon' ); - this._toolbars = []; - this.listenTo( editor.ui, 'update', () => { this._updateToolbarsVisibility(); } ); @@ -77,43 +88,60 @@ export default class WidgetToolbar extends Plugin { /** * Adds toolbar to the WidgetToolbar's collection. It renders it in the `ContextualBalloon` based on the value of the invoked - * `isSelected` function. Toolbar items are gathered from `toolbarItems` array. + * `isVisible` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * * Note: This method should be called in the `module:core/plugin/Plugin~afterInit` to make sure that plugins for toolbar items * will be already loaded and available in the UI component factory. * + * @param {String} toolbarId An id for the toolbar. Used to * @param {Object} options * @param {Array.} options.toolbarItems Array of toolbar items. - * @param {Function} options.isSelected Callback which specifies when the toolbar should be visible for the widget. + * @param {Function} options.isVisible Callback which specifies when the toolbar should be visible for the widget. * @param {String} [options.balloonClassName] CSS class for the widget balloon. */ - add( { toolbarItems, isSelected, balloonClassName = defaultBalloonClassName } ) { + add( toolbarId, { toolbarItems, isVisible, balloonClassName = defaultBalloonClassName } ) { const editor = this.editor; - - if ( !toolbarItems ) { - return; - } - const toolbarView = new ToolbarView(); toolbarView.fillFromConfig( toolbarItems, editor.ui.componentFactory ); - this._toolbars.push( { + if ( this._toolbars.has( toolbarId ) ) { + /** + * Toolbar with the given id was already added. + * + * @error + */ + throw new Error( 'duplicated-toolbar: Toolbar with the given id was already added.', { toolbarId } ) + } + + this._toolbars.set( toolbarId, { view: toolbarView, - isSelected, + isVisible, balloonClassName, } ); } + /** + * Removes toolbar of the given toolbarId. + * + * @param {String} toolbarId Toolbar identificator. + */ + remove( toolbarId ) { + const toolbar = this._toolbars.get( toolbarId ); + + this._hideToolbar( toolbar ); + this._toolbars.delete( toolbarId ); + } + /** * Iterates over stored toolbars and makes them visible or hidden. * * @private */ _updateToolbarsVisibility() { - for ( const toolbar of this._toolbars ) { - if ( !this.editor.ui.focusTracker.isFocused || !toolbar.isSelected( this.editor.editing.view.document.selection ) ) { + for ( const toolbar of this._toolbars.values() ) { + if ( !this.editor.ui.focusTracker.isFocused || !toolbar.isVisible( this.editor.editing.view.document.selection ) ) { this._hideToolbar( toolbar ); } else { this._showToolbar( toolbar ); @@ -122,7 +150,7 @@ export default class WidgetToolbar extends Plugin { } /** - * Hides given toolbar. + * Hides the given toolbar. * * @private * @param {Object} toolbar @@ -136,7 +164,7 @@ export default class WidgetToolbar extends Plugin { } /** - * Shows up or repositions given toolbar. + * Shows up or repositions the given toolbar. * * @private * @param {Object} toolbar @@ -198,7 +226,7 @@ function getParentWidget( selection ) { let parent = position.parent; while ( parent ) { - if ( isWidget( parent ) ) { + if ( parent.is( 'element' ) && isWidget( parent ) ) { return parent; } From fc8219871120c1e4c30b16a8ade9fe409fedb1c8 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 12:19:10 +0200 Subject: [PATCH 04/27] Added simple integration tests. --- src/widgettoolbar.js | 4 +- tests/widgettoolbar.js | 168 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 tests/widgettoolbar.js diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js index 1e151713..cccf201f 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbar.js @@ -110,9 +110,9 @@ export default class WidgetToolbar extends Plugin { /** * Toolbar with the given id was already added. * - * @error + * @error duplicated-widget-toolbar */ - throw new Error( 'duplicated-toolbar: Toolbar with the given id was already added.', { toolbarId } ) + throw new Error( 'duplicated-widget-toolbar: Toolbar with the given id was already added.', { toolbarId } ) } this._toolbars.set( toolbarId, { diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js new file mode 100644 index 00000000..1bc769a0 --- /dev/null +++ b/tests/widgettoolbar.js @@ -0,0 +1,168 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* global document */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import global from '@ckeditor/ckeditor5-utils/src/dom/global'; +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import Range from '@ckeditor/ckeditor5-engine/src/model/range'; +import View from '@ckeditor/ckeditor5-ui/src/view'; +import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import env from '@ckeditor/ckeditor5-utils/src/env'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import WidgetToolbar from '../src/widgettoolbar'; +import Widget from '../src/widget'; +import { isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; +import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; +import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters'; + +describe( 'WidgetToolbar', () => { + let editor, model, doc, toolbar, balloon, widgetToolbar, editorElement; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + editorElement = global.document.createElement( 'div' ); + global.document.body.appendChild( editorElement ); + + return ClassicEditor + .create( editorElement, { + plugins: [ Paragraph, Image, FakeButton, WidgetToolbar, FakeWidget ], + fake: { + toolbar: [ 'fake_button' ] + } + } ) + .then( newEditor => { + editor = newEditor; + model = newEditor.model; + doc = model.document; + widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); + balloon = editor.plugins.get( 'ContextualBalloon' ); + } ); + } ); + + afterEach( () => { + editorElement.remove(); + + return editor.destroy(); + } ); + + it( 'should be loaded', () => { + expect( editor.plugins.get( WidgetToolbar ) ).to.be.instanceOf( WidgetToolbar ); + } ); + + describe( 'add()', () => { + it( 'should create a widget toolbar and add it to the collection', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false, + } ); + + expect( widgetToolbar._toolbars.size ).to.equal( 1 ); + expect( widgetToolbar._toolbars.get( 'fake' ) ).to.be.an( 'object' ); + } ); + + it( 'should throw when adding two times widget with the same id', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + + expect( () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + } ).to.throw( /duplicated-widget-toolbar/ ); + } ); + + it( 'should show widget toolbar when the `isVisible` callback returns true', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: selection => { + const el = selection.getSelectedElement(); + + return el && isWidget( el ); + } + } ); + + editor.ui.focusTracker.isFocused = true; + + setData( model, 'foo[]' ); + + const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view + + expect( fakeWidgetToolbarView ).to.equal( balloon.visibleView ) + } ); + } ); + + // Plugin that adds fake_button to editor's component factory. + class FakeButton extends Plugin { + init() { + this.editor.ui.componentFactory.add( 'fake_button', locale => { + const view = new ButtonView( locale ); + + view.set( { + label: 'fake button' + } ); + + return view; + } ); + } + } + + // Simple widget plugin + class FakeWidget extends Plugin { + static get requires() { + return [ Widget ]; + } + + init() { + const editor = this.editor; + const schema = editor.model.schema; + + schema.register( 'fake-widget', { + isObject: true, + isBlock: true, + allowWhere: '$block', + } ); + + const conversion = editor.conversion; + + conversion.for( 'dataDowncast' ).add( downcastElementToElement( { + model: 'fake-widget', + view: ( modelElement, viewWriter ) => { + const fakeWidget = viewWriter.createContainerElement( 'div' ); + // fakeWidget.getFillerOffset = () => null; + + return fakeWidget; + } + } ) ); + + conversion.for( 'editingDowncast' ).add( downcastElementToElement( { + model: 'fake-widget', + view: ( modelElement, viewWriter ) => { + const fakeWidget = viewWriter.createContainerElement( 'div' ); + // fakeWidget.getFillerOffset = () => null; + + return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } ); + } + } ) ); + + conversion.for( 'upcast' ) + .add( upcastElementToElement( { + view: { + name: 'div' + }, + model: ( viewMedia, modelWriter ) => { + return modelWriter.createElement( 'fake-widget' ); + } + } ) ) + } + } +} ); From 08b3d46dfdde46cafecc1b2585e3fe0dc94e367d Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 13:45:01 +0200 Subject: [PATCH 05/27] Added tests for remove() method. --- tests/widgettoolbar.js | 53 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js index 1bc769a0..203b0e40 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbar.js @@ -95,9 +95,56 @@ describe( 'WidgetToolbar', () => { setData( model, 'foo[]' ); - const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view + const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; - expect( fakeWidgetToolbarView ).to.equal( balloon.visibleView ) + expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); + } ); + + it( 'should hide widget toolbar when the `isVisible` callback returns false', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: selection => { + const el = selection.getSelectedElement(); + + return el && isWidget( el ); + } + } ); + + editor.ui.focusTracker.isFocused = true; + + setData( model, '[foo]' ); + + expect( balloon.visibleView ).to.equal( null ); + } ); + + it( 'should hide widget toolbar when the `isVisible` callback returns false', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: selection => { + const el = selection.getSelectedElement(); + + return el && isWidget( el ); + } + } ); + + editor.ui.focusTracker.isFocused = true; + + setData( model, '[foo]' ); + + expect( balloon.visibleView ).to.equal( null ); + } ); + } ); + + describe( 'remove', () => { + it( 'should remove given widget toolbar', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + + widgetToolbar.remove( 'fake' ); + + expect( widgetToolbar._toolbars.size ).to.equal( 0 ); } ); } ); @@ -138,7 +185,6 @@ describe( 'WidgetToolbar', () => { model: 'fake-widget', view: ( modelElement, viewWriter ) => { const fakeWidget = viewWriter.createContainerElement( 'div' ); - // fakeWidget.getFillerOffset = () => null; return fakeWidget; } @@ -148,7 +194,6 @@ describe( 'WidgetToolbar', () => { model: 'fake-widget', view: ( modelElement, viewWriter ) => { const fakeWidget = viewWriter.createContainerElement( 'div' ); - // fakeWidget.getFillerOffset = () => null; return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } ); } From fa920a2ec3b1f82690fa006eebea8ada497a92fa Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 15:57:25 +0200 Subject: [PATCH 06/27] Added more tests. --- src/widgettoolbar.js | 28 +++++++++-- tests/widgettoolbar.js | 112 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 18 deletions(-) diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js index cccf201f..3ebe13dd 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbar.js @@ -110,9 +110,9 @@ export default class WidgetToolbar extends Plugin { /** * Toolbar with the given id was already added. * - * @error duplicated-widget-toolbar + * @error widget-toolbar-duplicated */ - throw new Error( 'duplicated-widget-toolbar: Toolbar with the given id was already added.', { toolbarId } ) + throw new Error( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ) } this._toolbars.set( toolbarId, { @@ -130,10 +130,28 @@ export default class WidgetToolbar extends Plugin { remove( toolbarId ) { const toolbar = this._toolbars.get( toolbarId ); + if ( !toolbar ) { + /** + * Toolbar with the given id was already added. + * + * @error widget-toolbar-does-not-exist + */ + throw new Error( 'widget-toolbar-does-not-exist' ); + } + this._hideToolbar( toolbar ); this._toolbars.delete( toolbarId ); } + /** + * Returns `true` when a toolbar with the given id is present in the toolbar collection. + * + * @param {String} toolbarId Toolbar identificator. + */ + has( toolbarId ) { + return this._toolbars.has( toolbarId ); + } + /** * Iterates over stored toolbars and makes them visible or hidden. * @@ -156,7 +174,7 @@ export default class WidgetToolbar extends Plugin { * @param {Object} toolbar */ _hideToolbar( toolbar ) { - if ( !this._isVisible( toolbar ) ) { + if ( !this._isToolbarVisible( toolbar ) ) { return; } @@ -170,7 +188,7 @@ export default class WidgetToolbar extends Plugin { * @param {Object} toolbar */ _showToolbar( toolbar ) { - if ( this._isVisible( toolbar ) ) { + if ( this._isToolbarVisible( toolbar ) ) { repositionContextualBalloon( this.editor ); } else if ( !this._balloon.hasView( toolbar.view ) ) { this._balloon.add( { @@ -185,7 +203,7 @@ export default class WidgetToolbar extends Plugin { * @private * @param {Object} toolbar */ - _isVisible( toolbar ) { + _isToolbarVisible( toolbar ) { return this._balloon.visibleView == toolbar.view; } } diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js index 203b0e40..65f68c46 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbar.js @@ -78,7 +78,57 @@ describe( 'WidgetToolbar', () => { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); - } ).to.throw( /duplicated-widget-toolbar/ ); + } ).to.throw( /widget-toolbar-duplicated/ ); + } ); + } ); + + describe( 'remove', () => { + it( 'should remove given widget toolbar', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + + widgetToolbar.remove( 'fake' ); + + expect( widgetToolbar._toolbars.size ).to.equal( 0 ); + } ); + + it( 'should throw an error if a toolbar does not exist', () => { + widgetToolbar.add( 'foo', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + + expect( () => { + widgetToolbar.remove( 'bar' ); + } ).to.throw( /widget-toolbar-does-not-exist/ ); + } ); + } ); + + describe( 'has', () => { + it( 'should return `true` when a toolbar with given id was added', () => { + widgetToolbar.add( 'foo', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + + expect( widgetToolbar.has( 'foo' ) ).to.be.true; + } ); + + it( 'should return `false` when a toolbar with given id was not added', () => { + widgetToolbar.add( 'foo', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: () => false + } ); + + expect( widgetToolbar.has( 'bar' ) ).to.be.false; + } ); + } ); + + describe( 'integration tests', () => { + beforeEach( () => { + editor.ui.focusTracker.isFocused = true; } ); it( 'should show widget toolbar when the `isVisible` callback returns true', () => { @@ -91,8 +141,6 @@ describe( 'WidgetToolbar', () => { } } ); - editor.ui.focusTracker.isFocused = true; - setData( model, 'foo[]' ); const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; @@ -110,8 +158,6 @@ describe( 'WidgetToolbar', () => { } } ); - editor.ui.focusTracker.isFocused = true; - setData( model, '[foo]' ); expect( balloon.visibleView ).to.equal( null ); @@ -127,24 +173,62 @@ describe( 'WidgetToolbar', () => { } } ); - editor.ui.focusTracker.isFocused = true; + setData( model, 'foo[]' ); - setData( model, '[foo]' ); + model.change( writer => { + // Select the paragraph content. + writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' ); + } ); expect( balloon.visibleView ).to.equal( null ); } ); - } ); - describe( 'remove', () => { - it( 'should remove given widget toolbar', () => { + it( 'should update toolbar position when other widget is being selected', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + isVisible: selection => { + const el = selection.getSelectedElement(); + + return el && isWidget( el ); + } } ); - widgetToolbar.remove( 'fake' ); + setData( model, '[]' ); - expect( widgetToolbar._toolbars.size ).to.equal( 0 ); + model.change( writer => { + // Select the second widget. + writer.setSelection( model.document.getRoot().getChild( 1 ), 'on' ); + } ); + + const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + + expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); + } ); + + it( 'should be able to show toolbar for elements inside the widget', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: selection => { + const pos = selection.getFirstPosition(); + let node = pos.parent; + + while( node ) { + if ( node.is( 'element' ) && isWidget( node ) ) { + return true; + } + + node = node.parent; + } + + return false; + } + } ); + + setData( model, '[foo]' ); + + const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + + expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); } ); @@ -179,6 +263,8 @@ describe( 'WidgetToolbar', () => { allowWhere: '$block', } ); + schema.extend( '$text', { allowIn: 'fake-widget' } ); + const conversion = editor.conversion; conversion.for( 'dataDowncast' ).add( downcastElementToElement( { From 69927c962e3c683ddb651d9f9cb6abea064dfb6e Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 18:00:06 +0200 Subject: [PATCH 07/27] Improved tests. --- tests/widgettoolbar.js | 272 +++++++++++++++++++++++++++-------------- 1 file changed, 178 insertions(+), 94 deletions(-) diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js index 65f68c46..6df32666 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbar.js @@ -10,19 +10,19 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import Range from '@ckeditor/ckeditor5-engine/src/model/range'; -import View from '@ckeditor/ckeditor5-ui/src/view'; import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; -import env from '@ckeditor/ckeditor5-utils/src/env'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import WidgetToolbar from '../src/widgettoolbar'; import Widget from '../src/widget'; import { isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters'; +import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor'; +import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; +import View from '@ckeditor/ckeditor5-ui/src/view'; describe( 'WidgetToolbar', () => { - let editor, model, doc, toolbar, balloon, widgetToolbar, editorElement; + let editor, model, balloon, widgetToolbar, editorElement; testUtils.createSinonSandbox(); @@ -40,7 +40,6 @@ describe( 'WidgetToolbar', () => { .then( newEditor => { editor = newEditor; model = newEditor.model; - doc = model.document; widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); balloon = editor.plugins.get( 'ContextualBalloon' ); } ); @@ -131,14 +130,10 @@ describe( 'WidgetToolbar', () => { editor.ui.focusTracker.isFocused = true; } ); - it( 'should show widget toolbar when the `isVisible` callback returns true', () => { + it( 'toolbar should be visible when the `isVisible` callback returns true', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: selection => { - const el = selection.getSelectedElement(); - - return el && isWidget( el ); - } + isVisible: isWidgetSelected } ); setData( model, 'foo[]' ); @@ -148,14 +143,10 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); - it( 'should hide widget toolbar when the `isVisible` callback returns false', () => { + it( 'toolbar should be hidden when the `isVisible` callback returns false', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: selection => { - const el = selection.getSelectedElement(); - - return el && isWidget( el ); - } + isVisible: isWidgetSelected } ); setData( model, '[foo]' ); @@ -163,34 +154,26 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( null ); } ); - it( 'should hide widget toolbar when the `isVisible` callback returns false', () => { + it( 'toolbar should be hidden when the `isVisible` callback returns false #2', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: selection => { - const el = selection.getSelectedElement(); - - return el && isWidget( el ); - } + isVisible: isWidgetSelected } ); setData( model, 'foo[]' ); model.change( writer => { - // Select the paragraph content. + // Select the foo. writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' ); } ); expect( balloon.visibleView ).to.equal( null ); } ); - it( 'should update toolbar position when other widget is being selected', () => { + it( 'toolbar should update its position when other widget is selected', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: selection => { - const el = selection.getSelectedElement(); - - return el && isWidget( el ); - } + isVisible: isWidgetSelected } ); setData( model, '[]' ); @@ -205,95 +188,196 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); - it( 'should be able to show toolbar for elements inside the widget', () => { + it( 'it should be possible to create a widget toolbar for content inside the widget', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: selection => { - const pos = selection.getFirstPosition(); - let node = pos.parent; + isVisible: doesWidgetContainSelection + } ); - while( node ) { - if ( node.is( 'element' ) && isWidget( node ) ) { - return true; - } + setData( model, '[foo]' ); - node = node.parent; - } + const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; - return false; - } - } ); + expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); + } ); - setData( model, '[foo]' ); + it( 'toolbar should not engage when is in the balloon yet invisible', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: isWidgetSelected + } ); const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + setData( model, '[]' ); + expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); + + const lastView = new View(); + lastView.element = document.createElement( 'div' ); + + balloon.add( { + view: lastView, + position: { + target: document.body + } + } ); + + expect( balloon.visibleView ).to.equal( lastView ); + + editor.ui.fire( 'update' ); + + expect( balloon.visibleView ).to.equal( lastView ); } ); } ); +} ); - // Plugin that adds fake_button to editor's component factory. - class FakeButton extends Plugin { - init() { - this.editor.ui.componentFactory.add( 'fake_button', locale => { - const view = new ButtonView( locale ); +describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { + let clock, editor, model, balloon, balloonToolbar, widgetToolbar, editorElement; - view.set( { - label: 'fake button' - } ); + testUtils.createSinonSandbox(); - return view; + beforeEach( () => { + editorElement = global.document.createElement( 'div' ); + global.document.body.appendChild( editorElement ); + clock = testUtils.sinon.useFakeTimers(); + + return BalloonEditor + .create( editorElement, { + plugins: [ Paragraph, Image, FakeButton, WidgetToolbar, FakeWidget, Bold ], + balloonToolbar: [ 'bold' ], + fake: { + toolbar: [ 'fake_button' ] + } + } ) + .then( newEditor => { + editor = newEditor; + model = newEditor.model; + widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); + balloon = editor.plugins.get( 'ContextualBalloon' ); + balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); + + editor.editing.view.document.isFocused = true; } ); - } - } + } ); + + afterEach( () => { + editorElement.remove(); + + return editor.destroy(); + } ); + + it( 'balloon toolbar should be hidden when the widget is selected', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: isWidgetSelected, + } ); + + const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + + setData( model, '[]foo' ); + + clock.tick( 200 ); + + expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); + } ); - // Simple widget plugin - class FakeWidget extends Plugin { - static get requires() { - return [ Widget ]; + it( 'balloon toolbar should be visible when the widget is not selected', () => { + widgetToolbar.add( 'fake', { + toolbarItems: editor.config.get( 'fake.toolbar' ), + isVisible: isWidgetSelected + } ); + + setData( model, '[foo]' ); + + clock.tick( 200 ); + + expect( balloon.visibleView ).to.equal( balloonToolbar.toolbarView ); + } ); +} ); + +function isWidgetSelected( selection ) { + const viewElement = selection.getSelectedElement(); + + return !!( viewElement && isWidget( viewElement ) ); +} + +function doesWidgetContainSelection( selection ) { + const pos = selection.getFirstPosition(); + let node = pos.parent; + + while ( node ) { + if ( node.is( 'element' ) && isWidget( node ) ) { + return true; } - init() { - const editor = this.editor; - const schema = editor.model.schema; + node = node.parent; + } - schema.register( 'fake-widget', { - isObject: true, - isBlock: true, - allowWhere: '$block', - } ); + return false; +} - schema.extend( '$text', { allowIn: 'fake-widget' } ); +// Plugin that adds fake_button to editor's component factory. +class FakeButton extends Plugin { + init() { + this.editor.ui.componentFactory.add( 'fake_button', locale => { + const view = new ButtonView( locale ); - const conversion = editor.conversion; + view.set( { + label: 'fake button' + } ); - conversion.for( 'dataDowncast' ).add( downcastElementToElement( { - model: 'fake-widget', - view: ( modelElement, viewWriter ) => { - const fakeWidget = viewWriter.createContainerElement( 'div' ); + return view; + } ); + } +} + +// Simple widget plugin +// It registers `` block in model and represents `div` in the view. +// It allows having text inside self. +class FakeWidget extends Plugin { + static get requires() { + return [ Widget ]; + } - return fakeWidget; - } - } ) ); + init() { + const editor = this.editor; + const schema = editor.model.schema; - conversion.for( 'editingDowncast' ).add( downcastElementToElement( { - model: 'fake-widget', - view: ( modelElement, viewWriter ) => { - const fakeWidget = viewWriter.createContainerElement( 'div' ); + schema.register( 'fake-widget', { + isObject: true, + isBlock: true, + allowWhere: '$block', + } ); - return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } ); + schema.extend( '$text', { allowIn: 'fake-widget' } ); + + const conversion = editor.conversion; + + conversion.for( 'dataDowncast' ).add( downcastElementToElement( { + model: 'fake-widget', + view: ( modelElement, viewWriter ) => { + return viewWriter.createContainerElement( 'div' ); + } + } ) ); + + conversion.for( 'editingDowncast' ).add( downcastElementToElement( { + model: 'fake-widget', + view: ( modelElement, viewWriter ) => { + const fakeWidget = viewWriter.createContainerElement( 'div' ); + + return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } ); + } + } ) ); + + conversion.for( 'upcast' ) + .add( upcastElementToElement( { + view: { + name: 'div' + }, + model: ( viewMedia, modelWriter ) => { + return modelWriter.createElement( 'fake-widget' ); } - } ) ); - - conversion.for( 'upcast' ) - .add( upcastElementToElement( { - view: { - name: 'div' - }, - model: ( viewMedia, modelWriter ) => { - return modelWriter.createElement( 'fake-widget' ); - } - } ) ) - } + } ) ) } -} ); +} From 4a0cfef1e0ee7255a23a492146892c768cd97c97 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 18:03:40 +0200 Subject: [PATCH 08/27] Code style improvements. --- src/widgettoolbar.js | 2 +- tests/widgettoolbar.js | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js index 3ebe13dd..2e2ab66e 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbar.js @@ -218,7 +218,7 @@ function repositionContextualBalloon( editor ) { function getBalloonPositionData( editor ) { const editingView = editor.editing.view; const defaultPositions = BalloonPanelView.defaultPositions; - let widget = getParentWidget( editingView.document.selection ); + const widget = getParentWidget( editingView.document.selection ); return { target: editingView.domConverter.viewToDom( widget ), diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js index 6df32666..7a8bdd30 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbar.js @@ -32,7 +32,7 @@ describe( 'WidgetToolbar', () => { return ClassicEditor .create( editorElement, { - plugins: [ Paragraph, Image, FakeButton, WidgetToolbar, FakeWidget ], + plugins: [ Paragraph, FakeButton, WidgetToolbar, FakeWidget ], fake: { toolbar: [ 'fake_button' ] } @@ -244,7 +244,7 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { return BalloonEditor .create( editorElement, { - plugins: [ Paragraph, Image, FakeButton, WidgetToolbar, FakeWidget, Bold ], + plugins: [ Paragraph, FakeButton, WidgetToolbar, FakeWidget, Bold ], balloonToolbar: [ 'bold' ], fake: { toolbar: [ 'fake_button' ] @@ -370,14 +370,13 @@ class FakeWidget extends Plugin { } } ) ); - conversion.for( 'upcast' ) - .add( upcastElementToElement( { - view: { - name: 'div' - }, - model: ( viewMedia, modelWriter ) => { - return modelWriter.createElement( 'fake-widget' ); - } - } ) ) + conversion.for( 'upcast' ).add( upcastElementToElement( { + view: { + name: 'div' + }, + model: ( view, modelWriter ) => { + return modelWriter.createElement( 'fake-widget' ); + } + } ) ); } } From a2bd3982b1036a955a1436bcd959a6c99a5613d5 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 18:15:51 +0200 Subject: [PATCH 09/27] Improved tests. --- tests/widgettoolbar.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js index 7a8bdd30..c08c3411 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbar.js @@ -133,7 +133,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should be visible when the `isVisible` callback returns true', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected + isVisible: isFakeWidgetSelected } ); setData( model, 'foo[]' ); @@ -146,7 +146,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should be hidden when the `isVisible` callback returns false', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected + isVisible: isFakeWidgetSelected } ); setData( model, '[foo]' ); @@ -157,7 +157,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should be hidden when the `isVisible` callback returns false #2', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected + isVisible: isFakeWidgetSelected } ); setData( model, 'foo[]' ); @@ -173,7 +173,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should update its position when other widget is selected', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected + isVisible: isFakeWidgetSelected } ); setData( model, '[]' ); @@ -191,7 +191,7 @@ describe( 'WidgetToolbar', () => { it( 'it should be possible to create a widget toolbar for content inside the widget', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: doesWidgetContainSelection + isVisible: isFakeWidgetContentSelected } ); setData( model, '[foo]' ); @@ -204,7 +204,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should not engage when is in the balloon yet invisible', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected + isVisible: isFakeWidgetSelected } ); const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; @@ -270,7 +270,7 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { it( 'balloon toolbar should be hidden when the widget is selected', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected, + isVisible: isFakeWidgetSelected, } ); const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; @@ -285,7 +285,7 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { it( 'balloon toolbar should be visible when the widget is not selected', () => { widgetToolbar.add( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isWidgetSelected + isVisible: isFakeWidgetSelected } ); setData( model, '[foo]' ); @@ -296,18 +296,20 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { } ); } ); -function isWidgetSelected( selection ) { +const fakeWidgetSymbol = Symbol( 'fakeWidget' ); + +function isFakeWidgetSelected( selection ) { const viewElement = selection.getSelectedElement(); - return !!( viewElement && isWidget( viewElement ) ); + return !!viewElement && isWidget( viewElement ) && !!viewElement.getCustomProperty( fakeWidgetSymbol ); } -function doesWidgetContainSelection( selection ) { +function isFakeWidgetContentSelected( selection ) { const pos = selection.getFirstPosition(); let node = pos.parent; while ( node ) { - if ( node.is( 'element' ) && isWidget( node ) ) { + if ( node.is( 'element' ) && isWidget( node ) && node.getCustomProperty( fakeWidgetSymbol ) ) { return true; } @@ -365,6 +367,7 @@ class FakeWidget extends Plugin { model: 'fake-widget', view: ( modelElement, viewWriter ) => { const fakeWidget = viewWriter.createContainerElement( 'div' ); + viewWriter.setCustomProperty( fakeWidgetSymbol, true, fakeWidget ); return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } ); } From b2f47b2df41a8797ae136ed761d5d417a7a4d8df Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 18:53:08 +0200 Subject: [PATCH 10/27] Code style improvements. --- src/widgettoolbar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgettoolbar.js b/src/widgettoolbar.js index 2e2ab66e..0920bd6d 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbar.js @@ -112,7 +112,7 @@ export default class WidgetToolbar extends Plugin { * * @error widget-toolbar-duplicated */ - throw new Error( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ) + throw new Error( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ); } this._toolbars.set( toolbarId, { @@ -136,7 +136,7 @@ export default class WidgetToolbar extends Plugin { * * @error widget-toolbar-does-not-exist */ - throw new Error( 'widget-toolbar-does-not-exist' ); + throw new Error( 'widget-toolbar-does-not-exist', { toolbarId } ); } this._hideToolbar( toolbar ); From 39eba17a687ae29b9e48aa5892ada1b40b9d1cb9 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 18 Sep 2018 19:05:01 +0200 Subject: [PATCH 11/27] Cleaned up. --- package.json | 1 + tests/widgettoolbar.js | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index d778b279..a41ed02b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@ckeditor/ckeditor5-basic-styles": "^10.0.2", + "@ckeditor/ckeditor5-editor-balloon": "^11.0.0", "@ckeditor/ckeditor5-editor-classic": "^11.0.0", "@ckeditor/ckeditor5-essentials": "^10.1.1", "@ckeditor/ckeditor5-paragraph": "^10.0.2", diff --git a/tests/widgettoolbar.js b/tests/widgettoolbar.js index c08c3411..c08d224b 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbar.js @@ -5,32 +5,32 @@ /* global document */ -import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; -import global from '@ckeditor/ckeditor5-utils/src/dom/global'; +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; -import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; -import WidgetToolbar from '../src/widgettoolbar'; +import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; import Widget from '../src/widget'; -import { isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; +import WidgetToolbar from '../src/widgettoolbar'; +import { isWidget, toWidget } from '../src/utils'; import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters'; -import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor'; -import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import View from '@ckeditor/ckeditor5-ui/src/view'; +import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + describe( 'WidgetToolbar', () => { let editor, model, balloon, widgetToolbar, editorElement; testUtils.createSinonSandbox(); beforeEach( () => { - editorElement = global.document.createElement( 'div' ); - global.document.body.appendChild( editorElement ); + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); - return ClassicEditor + return ClassicTestEditor .create( editorElement, { plugins: [ Paragraph, FakeButton, WidgetToolbar, FakeWidget ], fake: { @@ -238,8 +238,8 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { testUtils.createSinonSandbox(); beforeEach( () => { - editorElement = global.document.createElement( 'div' ); - global.document.body.appendChild( editorElement ); + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); clock = testUtils.sinon.useFakeTimers(); return BalloonEditor From 95ac5f2c1e1a6256e98f26e089efeeb6610ceb60 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 13:38:55 +0200 Subject: [PATCH 12/27] Changed WidgetToolbar to WidgetToolbarRepository. --- ...ttoolbar.js => widgettoolbarrepository.js} | 33 ++++---- ...ttoolbar.js => widgettoolbarrepository.js} | 78 +++++++++---------- 2 files changed, 57 insertions(+), 54 deletions(-) rename src/{widgettoolbar.js => widgettoolbarrepository.js} (84%) rename tests/{widgettoolbar.js => widgettoolbarrepository.js} (79%) diff --git a/src/widgettoolbar.js b/src/widgettoolbarrepository.js similarity index 84% rename from src/widgettoolbar.js rename to src/widgettoolbarrepository.js index 0920bd6d..71b02d81 100644 --- a/src/widgettoolbar.js +++ b/src/widgettoolbarrepository.js @@ -7,32 +7,33 @@ import { isWidget } from './utils'; const defaultBalloonClassName = 'ck-toolbar-container'; /** - * Widget toolbar plugin. It ease the process of creating widget toolbars by handling the whole rendering process and providing concise API. + * Widget toolbar repository plugin. A central point for creating widget toolbars. This plugin handles the whole + * toolbar rendering process and exposes concise API. * - * Creating toolbar for the widget bases on the {@link ~add()} method. TODO + * Creating toolbar for the widget bases on the {@link ~register()} method. * - * This plugin added to the plugin list directly or indirectly prevents showing up + * This plugin adds to the plugin list directly or indirectly prevents showing up * the {@link module:ui/toolbar/balloontoolbar~BalloonToolbar} toolbar and the widget toolbar at the same time. * * Usage example comes from {@link module:image/imagetoolbar~ImageToolbar}: * * class ImageToolbar extends Plugin { * static get requires() { - * return [ WidgetToolbar ]; + * return [ WidgetToolbarRepository ]; * } * * afterInit() { * const editor = this.editor; - * const widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); + * const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository ); * - * widgetToolbar.add( { + * widgetToolbarRepository.add( { * toolbarItems: editor.config.get( 'image.toolbar' ) * isVisible: isImageWidgetSelected * } ); * } * } */ -export default class WidgetToolbar extends Plugin { +export default class WidgetToolbarRepository extends Plugin { /** * @inheritDoc */ @@ -44,7 +45,7 @@ export default class WidgetToolbar extends Plugin { * @inheritDoc */ static get pluginName() { - return 'WidgetToolbar'; + return 'WidgetToolbarRepository'; } /** @@ -87,11 +88,11 @@ export default class WidgetToolbar extends Plugin { } /** - * Adds toolbar to the WidgetToolbar's collection. It renders it in the `ContextualBalloon` based on the value of the invoked + * Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked * `isVisible` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * - * Note: This method should be called in the `module:core/plugin/Plugin~afterInit` to make sure that plugins for toolbar items + * Note: This method should be called in the {@link module:core/plugin/Plugin~afterInit} to make sure that plugins for toolbar items * will be already loaded and available in the UI component factory. * * @param {String} toolbarId An id for the toolbar. Used to @@ -100,7 +101,7 @@ export default class WidgetToolbar extends Plugin { * @param {Function} options.isVisible Callback which specifies when the toolbar should be visible for the widget. * @param {String} [options.balloonClassName] CSS class for the widget balloon. */ - add( toolbarId, { toolbarItems, isVisible, balloonClassName = defaultBalloonClassName } ) { + register( toolbarId, { toolbarItems, isVisible, balloonClassName = defaultBalloonClassName } ) { const editor = this.editor; const toolbarView = new ToolbarView(); @@ -111,6 +112,7 @@ export default class WidgetToolbar extends Plugin { * Toolbar with the given id was already added. * * @error widget-toolbar-duplicated + * @param toolbarId Toolbar id. */ throw new Error( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ); } @@ -123,11 +125,11 @@ export default class WidgetToolbar extends Plugin { } /** - * Removes toolbar of the given toolbarId. + * Removes toolbar with the given toolbarId. * * @param {String} toolbarId Toolbar identificator. */ - remove( toolbarId ) { + deregister( toolbarId ) { const toolbar = this._toolbars.get( toolbarId ); if ( !toolbar ) { @@ -135,6 +137,7 @@ export default class WidgetToolbar extends Plugin { * Toolbar with the given id was already added. * * @error widget-toolbar-does-not-exist + * @param toolbarId Toolbar id. */ throw new Error( 'widget-toolbar-does-not-exist', { toolbarId } ); } @@ -144,11 +147,11 @@ export default class WidgetToolbar extends Plugin { } /** - * Returns `true` when a toolbar with the given id is present in the toolbar collection. + * Returns `true` when a toolbar with the given id is present in the widget toolbar repository. * * @param {String} toolbarId Toolbar identificator. */ - has( toolbarId ) { + isRegistered( toolbarId ) { return this._toolbars.has( toolbarId ); } diff --git a/tests/widgettoolbar.js b/tests/widgettoolbarrepository.js similarity index 79% rename from tests/widgettoolbar.js rename to tests/widgettoolbarrepository.js index c08d224b..344abff9 100644 --- a/tests/widgettoolbar.js +++ b/tests/widgettoolbarrepository.js @@ -11,7 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; import Widget from '../src/widget'; -import WidgetToolbar from '../src/widgettoolbar'; +import WidgetToolbarRepository from '../src/widgettoolbarrepository'; import { isWidget, toWidget } from '../src/utils'; import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters'; @@ -22,7 +22,7 @@ import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; describe( 'WidgetToolbar', () => { - let editor, model, balloon, widgetToolbar, editorElement; + let editor, model, balloon, widgetToolbarRepository, editorElement; testUtils.createSinonSandbox(); @@ -32,7 +32,7 @@ describe( 'WidgetToolbar', () => { return ClassicTestEditor .create( editorElement, { - plugins: [ Paragraph, FakeButton, WidgetToolbar, FakeWidget ], + plugins: [ Paragraph, FakeButton, WidgetToolbarRepository, FakeWidget ], fake: { toolbar: [ 'fake_button' ] } @@ -40,7 +40,7 @@ describe( 'WidgetToolbar', () => { .then( newEditor => { editor = newEditor; model = newEditor.model; - widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); + widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository ); balloon = editor.plugins.get( 'ContextualBalloon' ); } ); } ); @@ -52,28 +52,28 @@ describe( 'WidgetToolbar', () => { } ); it( 'should be loaded', () => { - expect( editor.plugins.get( WidgetToolbar ) ).to.be.instanceOf( WidgetToolbar ); + expect( editor.plugins.get( WidgetToolbarRepository ) ).to.be.instanceOf( WidgetToolbarRepository ); } ); - describe( 'add()', () => { + describe( 'register()', () => { it( 'should create a widget toolbar and add it to the collection', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false, } ); - expect( widgetToolbar._toolbars.size ).to.equal( 1 ); - expect( widgetToolbar._toolbars.get( 'fake' ) ).to.be.an( 'object' ); + expect( widgetToolbarRepository._toolbars.size ).to.equal( 1 ); + expect( widgetToolbarRepository._toolbars.get( 'fake' ) ).to.be.an( 'object' ); } ); it( 'should throw when adding two times widget with the same id', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); expect( () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); @@ -81,47 +81,47 @@ describe( 'WidgetToolbar', () => { } ); } ); - describe( 'remove', () => { + describe( 'deregister', () => { it( 'should remove given widget toolbar', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); - widgetToolbar.remove( 'fake' ); + widgetToolbarRepository.deregister( 'fake' ); - expect( widgetToolbar._toolbars.size ).to.equal( 0 ); + expect( widgetToolbarRepository._toolbars.size ).to.equal( 0 ); } ); it( 'should throw an error if a toolbar does not exist', () => { - widgetToolbar.add( 'foo', { + widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); expect( () => { - widgetToolbar.remove( 'bar' ); + widgetToolbarRepository.deregister( 'bar' ); } ).to.throw( /widget-toolbar-does-not-exist/ ); } ); } ); - describe( 'has', () => { + describe( 'isRegistered', () => { it( 'should return `true` when a toolbar with given id was added', () => { - widgetToolbar.add( 'foo', { + widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); - expect( widgetToolbar.has( 'foo' ) ).to.be.true; + expect( widgetToolbarRepository.isRegistered( 'foo' ) ).to.be.true; } ); it( 'should return `false` when a toolbar with given id was not added', () => { - widgetToolbar.add( 'foo', { + widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: () => false } ); - expect( widgetToolbar.has( 'bar' ) ).to.be.false; + expect( widgetToolbarRepository.isRegistered( 'bar' ) ).to.be.false; } ); } ); @@ -131,20 +131,20 @@ describe( 'WidgetToolbar', () => { } ); it( 'toolbar should be visible when the `isVisible` callback returns true', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected } ); setData( model, 'foo[]' ); - const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); it( 'toolbar should be hidden when the `isVisible` callback returns false', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected } ); @@ -155,7 +155,7 @@ describe( 'WidgetToolbar', () => { } ); it( 'toolbar should be hidden when the `isVisible` callback returns false #2', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected } ); @@ -171,7 +171,7 @@ describe( 'WidgetToolbar', () => { } ); it( 'toolbar should update its position when other widget is selected', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected } ); @@ -183,31 +183,31 @@ describe( 'WidgetToolbar', () => { writer.setSelection( model.document.getRoot().getChild( 1 ), 'on' ); } ); - const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); it( 'it should be possible to create a widget toolbar for content inside the widget', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetContentSelected } ); setData( model, '[foo]' ); - const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); it( 'toolbar should not engage when is in the balloon yet invisible', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected } ); - const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; setData( model, '[]' ); @@ -232,8 +232,8 @@ describe( 'WidgetToolbar', () => { } ); } ); -describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { - let clock, editor, model, balloon, balloonToolbar, widgetToolbar, editorElement; +describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => { + let clock, editor, model, balloon, balloonToolbar, widgetToolbarRepository, editorElement; testUtils.createSinonSandbox(); @@ -244,7 +244,7 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { return BalloonEditor .create( editorElement, { - plugins: [ Paragraph, FakeButton, WidgetToolbar, FakeWidget, Bold ], + plugins: [ Paragraph, FakeButton, WidgetToolbarRepository, FakeWidget, Bold ], balloonToolbar: [ 'bold' ], fake: { toolbar: [ 'fake_button' ] @@ -253,7 +253,7 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { .then( newEditor => { editor = newEditor; model = newEditor.model; - widgetToolbar = editor.plugins.get( 'WidgetToolbar' ); + widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository ); balloon = editor.plugins.get( 'ContextualBalloon' ); balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); @@ -268,12 +268,12 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { } ); it( 'balloon toolbar should be hidden when the widget is selected', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected, } ); - const fakeWidgetToolbarView = widgetToolbar._toolbars.get( 'fake' ).view; + const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; setData( model, '[]foo' ); @@ -283,7 +283,7 @@ describe( 'WidgetToolbar - integration with the BalloonToolbar', () => { } ); it( 'balloon toolbar should be visible when the widget is not selected', () => { - widgetToolbar.add( 'fake', { + widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), isVisible: isFakeWidgetSelected } ); From 261ef605f5f784cf226852e57d617a93e447126d Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 13:41:12 +0200 Subject: [PATCH 13/27] Simplified default balloon class name. --- src/widgettoolbarrepository.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 71b02d81..0c0c98d8 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -4,8 +4,6 @@ import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview'; import { isWidget } from './utils'; -const defaultBalloonClassName = 'ck-toolbar-container'; - /** * Widget toolbar repository plugin. A central point for creating widget toolbars. This plugin handles the whole * toolbar rendering process and exposes concise API. @@ -99,9 +97,9 @@ export default class WidgetToolbarRepository extends Plugin { * @param {Object} options * @param {Array.} options.toolbarItems Array of toolbar items. * @param {Function} options.isVisible Callback which specifies when the toolbar should be visible for the widget. - * @param {String} [options.balloonClassName] CSS class for the widget balloon. + * @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon. */ - register( toolbarId, { toolbarItems, isVisible, balloonClassName = defaultBalloonClassName } ) { + register( toolbarId, { toolbarItems, isVisible, balloonClassName = 'ck-toolbar-container' } ) { const editor = this.editor; const toolbarView = new ToolbarView(); From ab05ffe54a6c6138ba94845df5c97b236a270081 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 13:44:05 +0200 Subject: [PATCH 14/27] Changed isVisible to whenVisible. --- src/widgettoolbarrepository.js | 12 +++++------ tests/widgettoolbarrepository.js | 36 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 0c0c98d8..fd4d0d07 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -26,7 +26,7 @@ import { isWidget } from './utils'; * * widgetToolbarRepository.add( { * toolbarItems: editor.config.get( 'image.toolbar' ) - * isVisible: isImageWidgetSelected + * whenVisible: isImageWidgetSelected * } ); * } * } @@ -87,7 +87,7 @@ export default class WidgetToolbarRepository extends Plugin { /** * Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked - * `isVisible` function. Toolbar items are gathered from `toolbarItems` array. + * `whenVisible` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * * Note: This method should be called in the {@link module:core/plugin/Plugin~afterInit} to make sure that plugins for toolbar items @@ -96,10 +96,10 @@ export default class WidgetToolbarRepository extends Plugin { * @param {String} toolbarId An id for the toolbar. Used to * @param {Object} options * @param {Array.} options.toolbarItems Array of toolbar items. - * @param {Function} options.isVisible Callback which specifies when the toolbar should be visible for the widget. + * @param {Function} options.whenVisible Callback which specifies when the toolbar should be visible for the widget. * @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon. */ - register( toolbarId, { toolbarItems, isVisible, balloonClassName = 'ck-toolbar-container' } ) { + register( toolbarId, { toolbarItems, whenVisible, balloonClassName = 'ck-toolbar-container' } ) { const editor = this.editor; const toolbarView = new ToolbarView(); @@ -117,7 +117,7 @@ export default class WidgetToolbarRepository extends Plugin { this._toolbars.set( toolbarId, { view: toolbarView, - isVisible, + whenVisible, balloonClassName, } ); } @@ -160,7 +160,7 @@ export default class WidgetToolbarRepository extends Plugin { */ _updateToolbarsVisibility() { for ( const toolbar of this._toolbars.values() ) { - if ( !this.editor.ui.focusTracker.isFocused || !toolbar.isVisible( this.editor.editing.view.document.selection ) ) { + if ( !this.editor.ui.focusTracker.isFocused || !toolbar.whenVisible( this.editor.editing.view.document.selection ) ) { this._hideToolbar( toolbar ); } else { this._showToolbar( toolbar ); diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index 344abff9..fea83c78 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -59,7 +59,7 @@ describe( 'WidgetToolbar', () => { it( 'should create a widget toolbar and add it to the collection', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false, + whenVisible: () => false, } ); expect( widgetToolbarRepository._toolbars.size ).to.equal( 1 ); @@ -69,13 +69,13 @@ describe( 'WidgetToolbar', () => { it( 'should throw when adding two times widget with the same id', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + whenVisible: () => false } ); expect( () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + whenVisible: () => false } ); } ).to.throw( /widget-toolbar-duplicated/ ); } ); @@ -85,7 +85,7 @@ describe( 'WidgetToolbar', () => { it( 'should remove given widget toolbar', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + whenVisible: () => false } ); widgetToolbarRepository.deregister( 'fake' ); @@ -96,7 +96,7 @@ describe( 'WidgetToolbar', () => { it( 'should throw an error if a toolbar does not exist', () => { widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + whenVisible: () => false } ); expect( () => { @@ -109,7 +109,7 @@ describe( 'WidgetToolbar', () => { it( 'should return `true` when a toolbar with given id was added', () => { widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + whenVisible: () => false } ); expect( widgetToolbarRepository.isRegistered( 'foo' ) ).to.be.true; @@ -118,7 +118,7 @@ describe( 'WidgetToolbar', () => { it( 'should return `false` when a toolbar with given id was not added', () => { widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: () => false + whenVisible: () => false } ); expect( widgetToolbarRepository.isRegistered( 'bar' ) ).to.be.false; @@ -130,10 +130,10 @@ describe( 'WidgetToolbar', () => { editor.ui.focusTracker.isFocused = true; } ); - it( 'toolbar should be visible when the `isVisible` callback returns true', () => { + it( 'toolbar should be visible when the `whenVisible` callback returns true', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected + whenVisible: isFakeWidgetSelected } ); setData( model, 'foo[]' ); @@ -143,10 +143,10 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); - it( 'toolbar should be hidden when the `isVisible` callback returns false', () => { + it( 'toolbar should be hidden when the `whenVisible` callback returns false', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected + whenVisible: isFakeWidgetSelected } ); setData( model, '[foo]' ); @@ -154,10 +154,10 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( null ); } ); - it( 'toolbar should be hidden when the `isVisible` callback returns false #2', () => { + it( 'toolbar should be hidden when the `whenVisible` callback returns false #2', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected + whenVisible: isFakeWidgetSelected } ); setData( model, 'foo[]' ); @@ -173,7 +173,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should update its position when other widget is selected', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected + whenVisible: isFakeWidgetSelected } ); setData( model, '[]' ); @@ -191,7 +191,7 @@ describe( 'WidgetToolbar', () => { it( 'it should be possible to create a widget toolbar for content inside the widget', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetContentSelected + whenVisible: isFakeWidgetContentSelected } ); setData( model, '[foo]' ); @@ -204,7 +204,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should not engage when is in the balloon yet invisible', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected + whenVisible: isFakeWidgetSelected } ); const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; @@ -270,7 +270,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => it( 'balloon toolbar should be hidden when the widget is selected', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected, + whenVisible: isFakeWidgetSelected, } ); const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; @@ -285,7 +285,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => it( 'balloon toolbar should be visible when the widget is not selected', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - isVisible: isFakeWidgetSelected + whenVisible: isFakeWidgetSelected } ); setData( model, '[foo]' ); From 875bb8bfeb13f1c5783c423fb468bfa87ad2040b Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 13:48:03 +0200 Subject: [PATCH 15/27] Fixed errors. --- src/widgettoolbarrepository.js | 5 +++-- tests/widgettoolbarrepository.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index fd4d0d07..3caf8a90 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -3,6 +3,7 @@ import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextu import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview'; import { isWidget } from './utils'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; /** * Widget toolbar repository plugin. A central point for creating widget toolbars. This plugin handles the whole @@ -112,7 +113,7 @@ export default class WidgetToolbarRepository extends Plugin { * @error widget-toolbar-duplicated * @param toolbarId Toolbar id. */ - throw new Error( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ); + throw new CKEditorError( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ); } this._toolbars.set( toolbarId, { @@ -137,7 +138,7 @@ export default class WidgetToolbarRepository extends Plugin { * @error widget-toolbar-does-not-exist * @param toolbarId Toolbar id. */ - throw new Error( 'widget-toolbar-does-not-exist', { toolbarId } ); + throw new CKEditorError( 'widget-toolbar-does-not-exist', { toolbarId } ); } this._hideToolbar( toolbar ); diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index fea83c78..937015bd 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -20,6 +20,7 @@ import View from '@ckeditor/ckeditor5-ui/src/view'; import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; describe( 'WidgetToolbar', () => { let editor, model, balloon, widgetToolbarRepository, editorElement; @@ -77,7 +78,7 @@ describe( 'WidgetToolbar', () => { toolbarItems: editor.config.get( 'fake.toolbar' ), whenVisible: () => false } ); - } ).to.throw( /widget-toolbar-duplicated/ ); + } ).to.throw( CKEditorError, /^widget-toolbar-duplicated/ ); } ); } ); @@ -101,7 +102,7 @@ describe( 'WidgetToolbar', () => { expect( () => { widgetToolbarRepository.deregister( 'bar' ); - } ).to.throw( /widget-toolbar-does-not-exist/ ); + } ).to.throw( CKEditorError, /^widget-toolbar-does-not-exist/ ); } ); } ); From 6d5bf112bc39d5e50762f457e86d7f90e3a10101 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 14:39:12 +0200 Subject: [PATCH 16/27] Fixed option name. --- src/widgettoolbarrepository.js | 12 +++++------ tests/widgettoolbarrepository.js | 36 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 3caf8a90..60c5232b 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -27,7 +27,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; * * widgetToolbarRepository.add( { * toolbarItems: editor.config.get( 'image.toolbar' ) - * whenVisible: isImageWidgetSelected + * visibleWhen: isImageWidgetSelected * } ); * } * } @@ -88,7 +88,7 @@ export default class WidgetToolbarRepository extends Plugin { /** * Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked - * `whenVisible` function. Toolbar items are gathered from `toolbarItems` array. + * `visibleWhen` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * * Note: This method should be called in the {@link module:core/plugin/Plugin~afterInit} to make sure that plugins for toolbar items @@ -97,10 +97,10 @@ export default class WidgetToolbarRepository extends Plugin { * @param {String} toolbarId An id for the toolbar. Used to * @param {Object} options * @param {Array.} options.toolbarItems Array of toolbar items. - * @param {Function} options.whenVisible Callback which specifies when the toolbar should be visible for the widget. + * @param {Function} options.visibleWhen Callback which specifies when the toolbar should be visible for the widget. * @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon. */ - register( toolbarId, { toolbarItems, whenVisible, balloonClassName = 'ck-toolbar-container' } ) { + register( toolbarId, { toolbarItems, visibleWhen, balloonClassName = 'ck-toolbar-container' } ) { const editor = this.editor; const toolbarView = new ToolbarView(); @@ -118,7 +118,7 @@ export default class WidgetToolbarRepository extends Plugin { this._toolbars.set( toolbarId, { view: toolbarView, - whenVisible, + visibleWhen, balloonClassName, } ); } @@ -161,7 +161,7 @@ export default class WidgetToolbarRepository extends Plugin { */ _updateToolbarsVisibility() { for ( const toolbar of this._toolbars.values() ) { - if ( !this.editor.ui.focusTracker.isFocused || !toolbar.whenVisible( this.editor.editing.view.document.selection ) ) { + if ( !this.editor.ui.focusTracker.isFocused || !toolbar.visibleWhen( this.editor.editing.view.document.selection ) ) { this._hideToolbar( toolbar ); } else { this._showToolbar( toolbar ); diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index 937015bd..fc580674 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -60,7 +60,7 @@ describe( 'WidgetToolbar', () => { it( 'should create a widget toolbar and add it to the collection', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false, + visibleWhen: () => false, } ); expect( widgetToolbarRepository._toolbars.size ).to.equal( 1 ); @@ -70,13 +70,13 @@ describe( 'WidgetToolbar', () => { it( 'should throw when adding two times widget with the same id', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false + visibleWhen: () => false } ); expect( () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false + visibleWhen: () => false } ); } ).to.throw( CKEditorError, /^widget-toolbar-duplicated/ ); } ); @@ -86,7 +86,7 @@ describe( 'WidgetToolbar', () => { it( 'should remove given widget toolbar', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false + visibleWhen: () => false } ); widgetToolbarRepository.deregister( 'fake' ); @@ -97,7 +97,7 @@ describe( 'WidgetToolbar', () => { it( 'should throw an error if a toolbar does not exist', () => { widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false + visibleWhen: () => false } ); expect( () => { @@ -110,7 +110,7 @@ describe( 'WidgetToolbar', () => { it( 'should return `true` when a toolbar with given id was added', () => { widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false + visibleWhen: () => false } ); expect( widgetToolbarRepository.isRegistered( 'foo' ) ).to.be.true; @@ -119,7 +119,7 @@ describe( 'WidgetToolbar', () => { it( 'should return `false` when a toolbar with given id was not added', () => { widgetToolbarRepository.register( 'foo', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: () => false + visibleWhen: () => false } ); expect( widgetToolbarRepository.isRegistered( 'bar' ) ).to.be.false; @@ -131,10 +131,10 @@ describe( 'WidgetToolbar', () => { editor.ui.focusTracker.isFocused = true; } ); - it( 'toolbar should be visible when the `whenVisible` callback returns true', () => { + it( 'toolbar should be visible when the `visibleWhen` callback returns true', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected + visibleWhen: isFakeWidgetSelected } ); setData( model, 'foo[]' ); @@ -144,10 +144,10 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView ); } ); - it( 'toolbar should be hidden when the `whenVisible` callback returns false', () => { + it( 'toolbar should be hidden when the `visibleWhen` callback returns false', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected + visibleWhen: isFakeWidgetSelected } ); setData( model, '[foo]' ); @@ -155,10 +155,10 @@ describe( 'WidgetToolbar', () => { expect( balloon.visibleView ).to.equal( null ); } ); - it( 'toolbar should be hidden when the `whenVisible` callback returns false #2', () => { + it( 'toolbar should be hidden when the `visibleWhen` callback returns false #2', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected + visibleWhen: isFakeWidgetSelected } ); setData( model, 'foo[]' ); @@ -174,7 +174,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should update its position when other widget is selected', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected + visibleWhen: isFakeWidgetSelected } ); setData( model, '[]' ); @@ -192,7 +192,7 @@ describe( 'WidgetToolbar', () => { it( 'it should be possible to create a widget toolbar for content inside the widget', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetContentSelected + visibleWhen: isFakeWidgetContentSelected } ); setData( model, '[foo]' ); @@ -205,7 +205,7 @@ describe( 'WidgetToolbar', () => { it( 'toolbar should not engage when is in the balloon yet invisible', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected + visibleWhen: isFakeWidgetSelected } ); const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; @@ -271,7 +271,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => it( 'balloon toolbar should be hidden when the widget is selected', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected, + visibleWhen: isFakeWidgetSelected, } ); const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; @@ -286,7 +286,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => it( 'balloon toolbar should be visible when the widget is not selected', () => { widgetToolbarRepository.register( 'fake', { toolbarItems: editor.config.get( 'fake.toolbar' ), - whenVisible: isFakeWidgetSelected + visibleWhen: isFakeWidgetSelected } ); setData( model, '[foo]' ); From e4b1151d4d951ba0d109459ecf9e924631798e95 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 14:54:56 +0200 Subject: [PATCH 17/27] Removed deregister and isRegistered functions. --- src/widgettoolbarrepository.js | 31 ---------------------- tests/widgettoolbarrepository.js | 44 -------------------------------- 2 files changed, 75 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 60c5232b..6068bbcb 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -123,37 +123,6 @@ export default class WidgetToolbarRepository extends Plugin { } ); } - /** - * Removes toolbar with the given toolbarId. - * - * @param {String} toolbarId Toolbar identificator. - */ - deregister( toolbarId ) { - const toolbar = this._toolbars.get( toolbarId ); - - if ( !toolbar ) { - /** - * Toolbar with the given id was already added. - * - * @error widget-toolbar-does-not-exist - * @param toolbarId Toolbar id. - */ - throw new CKEditorError( 'widget-toolbar-does-not-exist', { toolbarId } ); - } - - this._hideToolbar( toolbar ); - this._toolbars.delete( toolbarId ); - } - - /** - * Returns `true` when a toolbar with the given id is present in the widget toolbar repository. - * - * @param {String} toolbarId Toolbar identificator. - */ - isRegistered( toolbarId ) { - return this._toolbars.has( toolbarId ); - } - /** * Iterates over stored toolbars and makes them visible or hidden. * diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index fc580674..6761f43a 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -82,50 +82,6 @@ describe( 'WidgetToolbar', () => { } ); } ); - describe( 'deregister', () => { - it( 'should remove given widget toolbar', () => { - widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), - visibleWhen: () => false - } ); - - widgetToolbarRepository.deregister( 'fake' ); - - expect( widgetToolbarRepository._toolbars.size ).to.equal( 0 ); - } ); - - it( 'should throw an error if a toolbar does not exist', () => { - widgetToolbarRepository.register( 'foo', { - toolbarItems: editor.config.get( 'fake.toolbar' ), - visibleWhen: () => false - } ); - - expect( () => { - widgetToolbarRepository.deregister( 'bar' ); - } ).to.throw( CKEditorError, /^widget-toolbar-does-not-exist/ ); - } ); - } ); - - describe( 'isRegistered', () => { - it( 'should return `true` when a toolbar with given id was added', () => { - widgetToolbarRepository.register( 'foo', { - toolbarItems: editor.config.get( 'fake.toolbar' ), - visibleWhen: () => false - } ); - - expect( widgetToolbarRepository.isRegistered( 'foo' ) ).to.be.true; - } ); - - it( 'should return `false` when a toolbar with given id was not added', () => { - widgetToolbarRepository.register( 'foo', { - toolbarItems: editor.config.get( 'fake.toolbar' ), - visibleWhen: () => false - } ); - - expect( widgetToolbarRepository.isRegistered( 'bar' ) ).to.be.false; - } ); - } ); - describe( 'integration tests', () => { beforeEach( () => { editor.ui.focusTracker.isFocused = true; From 9d46782ec6b159c4c61752e8b3691d27d3d1e6c6 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 15:24:49 +0200 Subject: [PATCH 18/27] Improved API docs. --- src/widgettoolbarrepository.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 6068bbcb..ec9e9035 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -25,9 +25,9 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; * const editor = this.editor; * const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository ); * - * widgetToolbarRepository.add( { - * toolbarItems: editor.config.get( 'image.toolbar' ) - * visibleWhen: isImageWidgetSelected + * widgetToolbarRepository.register( { + * toolbarItems: editor.config.get( 'image.toolbar' ), + * visibleWhen: viewSelection => isImageWidgetSelected( viewSelection ) * } ); * } * } From d7e9b2c209740cdbf7cf23c0c839c511d9119c29 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 15:40:07 +0200 Subject: [PATCH 19/27] API docs fixes. --- src/widgettoolbarrepository.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index ec9e9035..f1053ca9 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -25,7 +25,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; * const editor = this.editor; * const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository ); * - * widgetToolbarRepository.register( { + * widgetToolbarRepository.register( 'image', { * toolbarItems: editor.config.get( 'image.toolbar' ), * visibleWhen: viewSelection => isImageWidgetSelected( viewSelection ) * } ); @@ -91,8 +91,8 @@ export default class WidgetToolbarRepository extends Plugin { * `visibleWhen` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * - * Note: This method should be called in the {@link module:core/plugin/Plugin~afterInit} to make sure that plugins for toolbar items - * will be already loaded and available in the UI component factory. + * Note: This method should be called in the {@link module:core/plugin/Plugin#afterInit `Plugin#afterInit()`} callback (or later) + * to make sure that the given toolbar items were already registered by other plugins. * * @param {String} toolbarId An id for the toolbar. Used to * @param {Object} options From 4e9e58784d7fcf9eb3013707cb31f2e4d54bb70d Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 15:54:43 +0200 Subject: [PATCH 20/27] API docs fixes. --- src/widgettoolbarrepository.js | 15 ++++++++++++--- tests/widgettoolbarrepository.js | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index f1053ca9..b6204a53 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -1,3 +1,12 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module widget/widgettoolbarrepository + */ + import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; @@ -9,10 +18,10 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; * Widget toolbar repository plugin. A central point for creating widget toolbars. This plugin handles the whole * toolbar rendering process and exposes concise API. * - * Creating toolbar for the widget bases on the {@link ~register()} method. + * Creating toolbar for the widget bases on the {@link ~WidgetToolbarRepository#register} method. * * This plugin adds to the plugin list directly or indirectly prevents showing up - * the {@link module:ui/toolbar/balloontoolbar~BalloonToolbar} toolbar and the widget toolbar at the same time. + * the {@link module:ui/toolbar/balloon/balloontoolbar~BalloonToolbar} toolbar and the widget toolbar at the same time. * * Usage example comes from {@link module:image/imagetoolbar~ImageToolbar}: * @@ -91,7 +100,7 @@ export default class WidgetToolbarRepository extends Plugin { * `visibleWhen` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * - * Note: This method should be called in the {@link module:core/plugin/Plugin#afterInit `Plugin#afterInit()`} callback (or later) + * Note: This method should be called in the {@link module:core/plugin/plugin~Plugin#afterInit `Plugin#afterInit()`} callback (or later) * to make sure that the given toolbar items were already registered by other plugins. * * @param {String} toolbarId An id for the toolbar. Used to diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index 6761f43a..af1c45fc 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -233,6 +233,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; setData( model, '[]foo' ); + editor.ui.fire( 'update' ); clock.tick( 200 ); @@ -246,6 +247,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => } ); setData( model, '[foo]' ); + editor.ui.fire( 'update' ); clock.tick( 200 ); From 8c1bd967473d9e4eaecfca5364ad03e47e19726a Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 15:59:50 +0200 Subject: [PATCH 21/27] API docs improvements. --- src/widgettoolbarrepository.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index b6204a53..f340a483 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -15,15 +15,12 @@ import { isWidget } from './utils'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; /** - * Widget toolbar repository plugin. A central point for creating widget toolbars. This plugin handles the whole - * toolbar rendering process and exposes concise API. + * Widget toolbar repository plugin. A central point for registering widget toolbars. This plugin handles the whole + * toolbar rendering process and exposes a concise API. * - * Creating toolbar for the widget bases on the {@link ~WidgetToolbarRepository#register} method. + * To add a toolbar for your widget use the {@link ~WidgetToolbarRepository#register `WidgetToolbarRepository#register()`} method. * - * This plugin adds to the plugin list directly or indirectly prevents showing up - * the {@link module:ui/toolbar/balloon/balloontoolbar~BalloonToolbar} toolbar and the widget toolbar at the same time. - * - * Usage example comes from {@link module:image/imagetoolbar~ImageToolbar}: + * The following example comes from the {@link module:image/imagetoolbar~ImageToolbar} plugin: * * class ImageToolbar extends Plugin { * static get requires() { From 4c7222169c92798f4adeaac08433d239a5fe1931 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 16:02:50 +0200 Subject: [PATCH 22/27] Added missing docs. --- docs/api/widget.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api/widget.md b/docs/api/widget.md index 4ad8a0b6..da1688f5 100644 --- a/docs/api/widget.md +++ b/docs/api/widget.md @@ -19,6 +19,11 @@ The widget API consists of two layers: * The {@link module:widget/widget~Widget} plugin which enables base support for this feature. Usually, your plugin which implements a specific widget will define its reliance on the `Widget` plugin via its {@link module:core/plugin~Plugin.requires `Plugin.requires`} property. * The {@link module:widget/utils~toWidget `toWidget()`} {@link module:widget/utils~toWidgetEditable `toWidgetEditable()`} functions which need to be used during the conversion in order to make a specific element either a widget or a widget's nested editable. See their documentation for more details. +Besides the above mentioned core functionalities, this package implements the following utils: + +* The {@link module:widget/widgettoolbarrepository~WidgetToolbarRepository `WidgetToolbarRepository`} plugin which exposes a nice API for registering widget toolbars. +* A couple of helper functions for managing widgets in the {@link module:widget/utils `@ckeditor/ckeditor5-widget/utils`} module. + The widget API is proposed in a very different way than it was in CKEditor 4. It is just a set of utilities that allow you to implement typical object-like entities. Most of the work actually happens in the {@link api/engine engine} and this API's role is only to properly conduct the engine. From e2cc5fc07189e90706f5c16580347c581f56aad6 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 16:16:28 +0200 Subject: [PATCH 23/27] Fixed failing tests on FF. --- src/widgettoolbarrepository.js | 4 ++-- tests/widgettoolbarrepository.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index f340a483..9642aa4a 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -97,8 +97,8 @@ export default class WidgetToolbarRepository extends Plugin { * `visibleWhen` function. Toolbar items are gathered from `toolbarItems` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * - * Note: This method should be called in the {@link module:core/plugin/plugin~Plugin#afterInit `Plugin#afterInit()`} callback (or later) - * to make sure that the given toolbar items were already registered by other plugins. + * Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`} + * callback (or later) to make sure that the given toolbar items were already registered by other plugins. * * @param {String} toolbarId An id for the toolbar. Used to * @param {Object} options diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index af1c45fc..eda2bdc5 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -233,7 +233,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => const fakeWidgetToolbarView = widgetToolbarRepository._toolbars.get( 'fake' ).view; setData( model, '[]foo' ); - editor.ui.fire( 'update' ); + editor.ui.focusTracker.isFocused = true; clock.tick( 200 ); @@ -247,7 +247,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => } ); setData( model, '[foo]' ); - editor.ui.fire( 'update' ); + editor.ui.focusTracker.isFocused = true; clock.tick( 200 ); From 3fe23d3951822b3df8694b28dd1d4db9f96d5190 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 18:03:01 +0200 Subject: [PATCH 24/27] Fixed description in tests. --- tests/widgettoolbarrepository.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index eda2bdc5..71da5de5 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -22,7 +22,7 @@ import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; -describe( 'WidgetToolbar', () => { +describe( 'WidgetToolbarRepository', () => { let editor, model, balloon, widgetToolbarRepository, editorElement; testUtils.createSinonSandbox(); From 1711a70c776f1ce29225ef2faf449bf04e32cae1 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Wed, 19 Sep 2018 18:26:34 +0200 Subject: [PATCH 25/27] Changed order in the register function. --- src/widgettoolbarrepository.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 9642aa4a..28b5dada 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -110,8 +110,6 @@ export default class WidgetToolbarRepository extends Plugin { const editor = this.editor; const toolbarView = new ToolbarView(); - toolbarView.fillFromConfig( toolbarItems, editor.ui.componentFactory ); - if ( this._toolbars.has( toolbarId ) ) { /** * Toolbar with the given id was already added. @@ -122,6 +120,8 @@ export default class WidgetToolbarRepository extends Plugin { throw new CKEditorError( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ); } + toolbarView.fillFromConfig( toolbarItems, editor.ui.componentFactory ); + this._toolbars.set( toolbarId, { view: toolbarView, visibleWhen, From 0d72ab3f2e357c1a1cebd3a62218d03e234def42 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Thu, 20 Sep 2018 10:50:18 +0200 Subject: [PATCH 26/27] Renamed toolbarItems to items. --- src/widgettoolbarrepository.js | 10 +++++----- tests/widgettoolbarrepository.js | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 28b5dada..69ff6e04 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -32,7 +32,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; * const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository ); * * widgetToolbarRepository.register( 'image', { - * toolbarItems: editor.config.get( 'image.toolbar' ), + * items: editor.config.get( 'image.toolbar' ), * visibleWhen: viewSelection => isImageWidgetSelected( viewSelection ) * } ); * } @@ -94,7 +94,7 @@ export default class WidgetToolbarRepository extends Plugin { /** * Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked - * `visibleWhen` function. Toolbar items are gathered from `toolbarItems` array. + * `visibleWhen` function. Toolbar items are gathered from `items` array. * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. * * Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`} @@ -102,11 +102,11 @@ export default class WidgetToolbarRepository extends Plugin { * * @param {String} toolbarId An id for the toolbar. Used to * @param {Object} options - * @param {Array.} options.toolbarItems Array of toolbar items. + * @param {Array.} options.items Array of toolbar items. * @param {Function} options.visibleWhen Callback which specifies when the toolbar should be visible for the widget. * @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon. */ - register( toolbarId, { toolbarItems, visibleWhen, balloonClassName = 'ck-toolbar-container' } ) { + register( toolbarId, { items, visibleWhen, balloonClassName = 'ck-toolbar-container' } ) { const editor = this.editor; const toolbarView = new ToolbarView(); @@ -120,7 +120,7 @@ export default class WidgetToolbarRepository extends Plugin { throw new CKEditorError( 'widget-toolbar-duplicated: Toolbar with the given id was already added.', { toolbarId } ); } - toolbarView.fillFromConfig( toolbarItems, editor.ui.componentFactory ); + toolbarView.fillFromConfig( items, editor.ui.componentFactory ); this._toolbars.set( toolbarId, { view: toolbarView, diff --git a/tests/widgettoolbarrepository.js b/tests/widgettoolbarrepository.js index 71da5de5..81ea441a 100644 --- a/tests/widgettoolbarrepository.js +++ b/tests/widgettoolbarrepository.js @@ -59,7 +59,7 @@ describe( 'WidgetToolbarRepository', () => { describe( 'register()', () => { it( 'should create a widget toolbar and add it to the collection', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: () => false, } ); @@ -69,13 +69,13 @@ describe( 'WidgetToolbarRepository', () => { it( 'should throw when adding two times widget with the same id', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: () => false } ); expect( () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: () => false } ); } ).to.throw( CKEditorError, /^widget-toolbar-duplicated/ ); @@ -89,7 +89,7 @@ describe( 'WidgetToolbarRepository', () => { it( 'toolbar should be visible when the `visibleWhen` callback returns true', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected } ); @@ -102,7 +102,7 @@ describe( 'WidgetToolbarRepository', () => { it( 'toolbar should be hidden when the `visibleWhen` callback returns false', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected } ); @@ -113,7 +113,7 @@ describe( 'WidgetToolbarRepository', () => { it( 'toolbar should be hidden when the `visibleWhen` callback returns false #2', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected } ); @@ -129,7 +129,7 @@ describe( 'WidgetToolbarRepository', () => { it( 'toolbar should update its position when other widget is selected', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected } ); @@ -147,7 +147,7 @@ describe( 'WidgetToolbarRepository', () => { it( 'it should be possible to create a widget toolbar for content inside the widget', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetContentSelected } ); @@ -160,7 +160,7 @@ describe( 'WidgetToolbarRepository', () => { it( 'toolbar should not engage when is in the balloon yet invisible', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected } ); @@ -226,7 +226,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => it( 'balloon toolbar should be hidden when the widget is selected', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected, } ); @@ -242,7 +242,7 @@ describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => it( 'balloon toolbar should be visible when the widget is not selected', () => { widgetToolbarRepository.register( 'fake', { - toolbarItems: editor.config.get( 'fake.toolbar' ), + items: editor.config.get( 'fake.toolbar' ), visibleWhen: isFakeWidgetSelected } ); From 031e37014487dd4359d4972e23c38a5c59d8a78b Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Thu, 20 Sep 2018 11:13:41 +0200 Subject: [PATCH 27/27] Added API docs to the _showToolbar() method. --- src/widgettoolbarrepository.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/widgettoolbarrepository.js b/src/widgettoolbarrepository.js index 69ff6e04..5497d8b4 100644 --- a/src/widgettoolbarrepository.js +++ b/src/widgettoolbarrepository.js @@ -159,7 +159,11 @@ export default class WidgetToolbarRepository extends Plugin { } /** - * Shows up or repositions the given toolbar. + * Shows up the toolbar if the toolbar is not visible and repositions the toolbar's balloon when toolbar's + * view is the most top view in balloon stack. + * + * It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view + * should be still visible after the {@link module:core/editor/editorui~EditorUI#event:update}. * * @private * @param {Object} toolbar