浏览代码

Allowed BlockToolbar to be displayed next to $block elements.

Oskar Wróbel 7 年之前
父节点
当前提交
b4f9ea7e15

+ 26 - 52
packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js

@@ -15,7 +15,6 @@ import BalloonPanelView from '../../panel/balloon/balloonpanelview';
 import ToolbarView from '../toolbarview';
 
 import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
-import ContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
 import clickOutsideHandler from '../../bindings/clickoutsidehandler';
 
 import { getOptimalPosition } from '@ckeditor/ckeditor5-utils/src/dom/position';
@@ -95,14 +94,6 @@ export default class BlockToolbar extends Plugin {
 		 */
 		this.buttonView = this._createButtonView();
 
-		/**
-		 * List of block element names that allow displaying toolbar next to it.
-		 * This list will be updated by {@link ~BlockToolbar#afterInit} method.
-		 *
-		 * @type {Array<String>}
-		 */
-		this._allowedElements = [];
-
 		// Close #panelView on click out of the plugin UI.
 		clickOutsideHandler( {
 			emitter: this.panelView,
@@ -144,8 +135,6 @@ export default class BlockToolbar extends Plugin {
 		for ( const item of this.toolbarView.items ) {
 			item.on( 'execute', () => this._hidePanel( true ), { priority: 'high' } );
 		}
-
-		this._allowedElements = this._getAllowedElements();
 	}
 
 	/**
@@ -206,48 +195,31 @@ export default class BlockToolbar extends Plugin {
 	}
 
 	/**
-	 * Returns list of element names that allow displaying block button next to it.
-	 *
-	 * @private
-	 * @returns {Array<String>}
-	 */
-	_getAllowedElements() {
-		const config = this.editor.config;
-
-		const elements = [ 'p', 'li' ];
-
-		for ( const item of config.get( 'heading.options' ) || [] ) {
-			if ( item.view ) {
-				elements.push( item.view );
-			}
-		}
-
-		return elements;
-	}
-
-	/**
-	 * Checks if block button is allowed for displaying next to given element.
+	 * Checks if block button is allowed for displaying next to given element
+	 * (when element is a $block and is not an object).
 	 *
 	 * Fires {@link #event:checkAllowed} event which can be handled and overridden to apply custom validation.
 	 *
-	 * Example how to disallow button for `h1` element:
+	 * Example how to disallow button for `h2` element:
 	 *
 	 * 		const blockToolbar = editor.plugins.get( 'BlockToolbar' );
 	 *
 	 * 		blockToolbar.on( 'checkAllowed', ( evt, args ) => {
-	 *			const viewElement = args[ 0 ];
+	 *			const modelElement = args[ 0 ];
 	 *
-	 *			if ( viewElement.name === 'h1' ) {
+	 *			if ( modelElement && modelElement.name === 'heading1' ) {
 	 *				evt.return = false;
 	 *			}
 	 * 		}, { priority: 'high' } );
 	 *
 	 * @fires checkAllowed
-	 * @param {module:engine/view/containerelement~ContainerElement} viewElement Container element where selection is.
-	 * @returns {Boolean} `true` when block button is allowed to display `false` otherwise.
+	 * @param {module:engine/model/element~Element} modelElement Element where the selection is.
+	 * @returns {Boolean} `true` when block button is allowed to be displayed `false` otherwise.
 	 */
-	checkAllowed( viewElement ) {
-		return this._allowedElements.includes( viewElement.name );
+	checkAllowed( modelElement ) {
+		const schema = this.editor.model.schema;
+
+		return modelElement && schema.isBlock( modelElement ) && !schema.isObject( modelElement );
 	}
 
 	/**
@@ -257,9 +229,9 @@ export default class BlockToolbar extends Plugin {
 	 */
 	_enable() {
 		const editor = this.editor;
+		const model = editor.model;
 		const view = editor.editing.view;
-		const viewDocument = view.document;
-		let targetElement, targetDomElement;
+		let modelTarget, domTarget;
 
 		// Hides panel on a direct selection change.
 		this.listenTo( editor.model.document.selection, 'change:range', ( evt, data ) => {
@@ -269,24 +241,24 @@ export default class BlockToolbar extends Plugin {
 		} );
 
 		this.listenTo( view, 'render', () => {
-			// Get selection parent container, block button will be attached to this element.
-			targetElement = getParentContainer( viewDocument.selection.getFirstPosition() );
+			// Get selection closest parent block element, button will be attached to this element.
+			modelTarget = getParentBlock( model.document.selection.getFirstPosition(), model.schema );
 
 			// Do not attach block button when is not allowed for given target element.
-			if ( !this.checkAllowed( targetElement ) ) {
+			if ( !this.checkAllowed( modelTarget ) ) {
 				this.buttonView.isVisible = false;
 
 				return;
 			}
 
-			// Get target DOM node.
-			targetDomElement = view.domConverter.mapViewToDom( targetElement );
+			// Get DOM target element.
+			domTarget = view.domConverter.mapViewToDom( editor.editing.mapper.toViewElement( modelTarget ) );
 
 			// Show block button.
 			this.buttonView.isVisible = true;
 
 			// Attach block button to target DOM element.
-			this._attachButtonToElement( targetDomElement );
+			this._attachButtonToElement( domTarget );
 
 			// When panel is opened then refresh it position to be properly aligned with block button.
 			if ( this.panelView.isVisible ) {
@@ -297,7 +269,7 @@ export default class BlockToolbar extends Plugin {
 		this.listenTo( this.buttonView, 'change:isVisible', ( evt, name, isVisible ) => {
 			if ( isVisible ) {
 				// Keep correct position of button and panel on window#resize.
-				this.buttonView.listenTo( window, 'resize', () => this._attachButtonToElement( targetDomElement ) );
+				this.buttonView.listenTo( window, 'resize', () => this._attachButtonToElement( domTarget ) );
 			} else {
 				// Stop repositioning button when is hidden.
 				this.buttonView.stopListening( window, 'resize' );
@@ -391,12 +363,14 @@ export default class BlockToolbar extends Plugin {
 	 */
 }
 
-// Because the engine.view.writer.getParentContainer is not exported here is a copy.
-// See: https://github.com/ckeditor/ckeditor5-engine/issues/628
-function getParentContainer( position ) {
+function getParentBlock( position, schema ) {
 	let parent = position.parent;
 
-	while ( !( parent instanceof ContainerElement ) ) {
+	if ( parent.is( 'rootElement' ) ) {
+		return null;
+	}
+
+	while ( !( schema.isBlock( parent ) ) ) {
 		parent = parent.parent;
 	}
 

+ 17 - 42
packages/ckeditor5-ui/tests/toolbar/block/blocktoolbar.js

@@ -17,6 +17,8 @@ import HeadingButtonsUI from '@ckeditor/ckeditor5-heading/src/headingbuttonsui';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import ParagraphButtonUI from '@ckeditor/ckeditor5-paragraph/src/paragraphbuttonui';
 import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
+import Image from '@ckeditor/ckeditor5-image/src/image';
+import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
 import List from '@ckeditor/ckeditor5-list/src/list';
 
 import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
@@ -33,7 +35,7 @@ describe( 'BlockToolbar', () => {
 		document.body.appendChild( element );
 
 		return ClassicTestEditor.create( element, {
-			plugins: [ BlockToolbar, Heading, HeadingButtonsUI, Paragraph, ParagraphButtonUI, BlockQuote, List ],
+			plugins: [ BlockToolbar, Heading, HeadingButtonsUI, Paragraph, ParagraphButtonUI, BlockQuote, Image, ImageCaption ],
 			blockToolbar: [ 'paragraph', 'heading1', 'heading2', 'blockQuote' ]
 		} ).then( newEditor => {
 			editor = newEditor;
@@ -213,56 +215,32 @@ describe( 'BlockToolbar', () => {
 	} );
 
 	describe( 'allowed elements', () => {
-		it( 'should display button when selection is placed in a paragraph', () => {
-			setData( editor.model, '<paragraph>foo[]bar</paragraph>' );
-
-			expect( blockToolbar.buttonView.isVisible ).to.true;
-		} );
+		it( 'should display button when selection is placed in a block element', () => {
+			editor.model.schema.register( 'foo', { inheritAllFrom: '$block' } );
+			editor.conversion.elementToElement( { model: 'foo', view: 'foo' } );
 
-		it( 'should display button when selection is placed in a heading1', () => {
-			setData( editor.model, '<heading1>foo[]bar</heading1>' );
+			setData( editor.model, '<foo>foo[]bar</foo>' );
 
 			expect( blockToolbar.buttonView.isVisible ).to.true;
 		} );
 
-		it( 'should display button when selection is placed in a heading2', () => {
-			setData( editor.model, '<heading2>foo[]bar</heading2>' );
+		it( 'should not display button when selection is placed in an object', () => {
+			setData( editor.model, '<image src="foo.jpg"><caption>fo[]o</caption></image>' );
 
-			expect( blockToolbar.buttonView.isVisible ).to.true;
-		} );
-
-		it( 'should display button when selection is placed in a heading3', () => {
-			setData( editor.model, '<heading3>foo[]bar</heading3>' );
-
-			expect( blockToolbar.buttonView.isVisible ).to.true;
-		} );
-
-		it( 'should display button when selection is placed in a list item', () => {
-			setData( editor.model, '<listItem type="numbered" indent="0">foo[]bar</listItem>' );
-
-			expect( blockToolbar.buttonView.isVisible ).to.true;
-		} );
-
-		it( 'should display button when selection is placed in a allowed element in a blockQuote', () => {
-			setData( editor.model, '<blockQuote><paragraph>foo[]bar</paragraph></blockQuote>' );
-
-			expect( blockToolbar.buttonView.isVisible ).to.true;
+			expect( blockToolbar.buttonView.isVisible ).to.false;
 		} );
 
-		it( 'should not display button when selection is placed in not allowed element', () => {
-			editor.model.schema.register( 'table', { inheritAllFrom: '$block' } );
-			editor.conversion.elementToElement( { model: 'table', view: 'table' } );
-
-			setData( editor.model, '<table>foo[]bar</table>' );
+		it( 'should not display button when selection is placed in a root element', () => {
+			setData( editor.model, '[<image src="foo.jpg"></image>]' );
 
 			expect( blockToolbar.buttonView.isVisible ).to.false;
 		} );
 
 		it( 'should make it possible to provide custom validation', () => {
 			blockToolbar.on( 'checkAllowed', ( evt, args ) => {
-				const viewElement = args[ 0 ];
+				const modelElement = args[ 0 ];
 
-				if ( viewElement.name === 'h2' ) {
+				if ( modelElement.name === 'heading1' ) {
 					evt.return = false;
 				}
 			} );
@@ -411,18 +389,15 @@ describe( 'BlockToolbar', () => {
 		} );
 
 		it( 'should update button position on browser resize only when button is visible', () => {
-			editor.model.schema.register( 'table', { inheritAllFrom: '$block' } );
-			editor.conversion.elementToElement( { model: 'table', view: 'table' } );
-
 			const spy = testUtils.sinon.spy( blockToolbar, '_attachButtonToElement' );
 
-			setData( editor.model, '<table>fo[]o</table><paragraph>bar</paragraph>' );
+			setData( editor.model, '[<image src="foo.jpg"></image>]<paragraph>bar</paragraph>' );
 
 			window.dispatchEvent( new Event( 'resize' ) );
 
 			sinon.assert.notCalled( spy );
 
-			setData( editor.model, '<table>foo</table><paragraph>ba[]r</paragraph>' );
+			setData( editor.model, '<image src="foo.jpg"></image><paragraph>ba[]r</paragraph>' );
 
 			spy.resetHistory();
 
@@ -430,7 +405,7 @@ describe( 'BlockToolbar', () => {
 
 			sinon.assert.called( spy );
 
-			setData( editor.model, '<table>fo[]o</table><paragraph>bar</paragraph>' );
+			setData( editor.model, '[<image src="foo.jpg"></image>]<paragraph>bar</paragraph>' );
 
 			spy.resetHistory();