8
0
Pārlūkot izejas kodu

Merge branch 'master' into t/ckeditor5-dev/278

Piotrek Koszuliński 8 gadi atpakaļ
vecāks
revīzija
330808c178

+ 1 - 1
packages/ckeditor5-image/docs/features/image.md

@@ -14,7 +14,7 @@ The [`@ckeditor/ckeditor5-image`](https://www.npmjs.com/package/@ckeditor/ckedit
 * {@link module:upload/imageupload~ImageUpload} adds support for uploading dropped or pasted images (note: it is currently located in the [`@ckeditor/ckeditor5-upload`](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload) package but will be moved to the `@ckeditor/ckeditor5-image` package).
 * {@link module:upload/imageupload~ImageUpload} adds support for uploading dropped or pasted images (note: it is currently located in the [`@ckeditor/ckeditor5-upload`](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload) package but will be moved to the `@ckeditor/ckeditor5-image` package).
 
 
 <info-box info>
 <info-box info>
-	The first four features listed above (so all except upload support) are enabled by default in all builds. They are also included in the {@link module:presets/article~Article Article preset}.
+	The first four features listed above (so all except the upload support) are enabled by default in all builds.
 </info-box>
 </info-box>
 
 
 ## Base image support
 ## Base image support

+ 1 - 1
packages/ckeditor5-image/package.json

@@ -14,7 +14,7 @@
   "devDependencies": {
   "devDependencies": {
     "@ckeditor/ckeditor5-basic-styles": "^0.9.0",
     "@ckeditor/ckeditor5-basic-styles": "^0.9.0",
     "@ckeditor/ckeditor5-clipboard": "^0.7.0",
     "@ckeditor/ckeditor5-clipboard": "^0.7.0",
-    "@ckeditor/ckeditor5-dev-lint": "^3.1.0",
+    "@ckeditor/ckeditor5-dev-lint": "^3.1.1",
     "@ckeditor/ckeditor5-editor-classic": "^0.8.0",
     "@ckeditor/ckeditor5-editor-classic": "^0.8.0",
     "@ckeditor/ckeditor5-enter": "^0.10.0",
     "@ckeditor/ckeditor5-enter": "^0.10.0",
     "@ckeditor/ckeditor5-essentials": "^0.3.0",
     "@ckeditor/ckeditor5-essentials": "^0.3.0",

+ 2 - 4
packages/ckeditor5-image/src/image.js

@@ -11,7 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ImageEngine from './image/imageengine';
 import ImageEngine from './image/imageengine';
 import Widget from '@ckeditor/ckeditor5-widget/src/widget';
 import Widget from '@ckeditor/ckeditor5-widget/src/widget';
 import ImageTextAlternative from './imagetextalternative';
 import ImageTextAlternative from './imagetextalternative';
-import { isImageWidget } from './image/utils';
+import { isImageWidgetSelected } from './image/utils';
 
 
 import '../theme/theme.scss';
 import '../theme/theme.scss';
 
 
@@ -49,9 +49,7 @@ export default class Image extends Plugin {
 		// https://github.com/ckeditor/ckeditor5-image/issues/110
 		// https://github.com/ckeditor/ckeditor5-image/issues/110
 		if ( contextualToolbar ) {
 		if ( contextualToolbar ) {
 			this.listenTo( contextualToolbar, 'show', evt => {
 			this.listenTo( contextualToolbar, 'show', evt => {
-				const selectedElement = editor.editing.view.selection.getSelectedElement();
-
-				if ( selectedElement && isImageWidget( selectedElement ) ) {
+				if ( isImageWidgetSelected( editor.editing.view.selection ) ) {
 					evt.stop();
 					evt.stop();
 				}
 				}
 			}, { priority: 'high' } );
 			}, { priority: 'high' } );

+ 2 - 4
packages/ckeditor5-image/src/image/ui/utils.js

@@ -8,7 +8,7 @@
  */
  */
 
 
 import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
 import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
-import { isImageWidget } from '../utils';
+import { isImageWidgetSelected } from '../utils';
 
 
 /**
 /**
  * A helper utility which positions the
  * A helper utility which positions the
@@ -18,11 +18,9 @@ import { isImageWidget } from '../utils';
  * @param {module:core/editor/editor~Editor} editor The editor instance.
  * @param {module:core/editor/editor~Editor} editor The editor instance.
  */
  */
 export function repositionContextualBalloon( editor ) {
 export function repositionContextualBalloon( editor ) {
-	const editingView = editor.editing.view;
 	const balloon = editor.plugins.get( 'ContextualBalloon' );
 	const balloon = editor.plugins.get( 'ContextualBalloon' );
-	const selectedElement = editingView.selection.getSelectedElement();
 
 
-	if ( selectedElement && isImageWidget( selectedElement ) ) {
+	if ( isImageWidgetSelected( editor.editing.view.selection ) ) {
 		const position = getBalloonPositionData( editor );
 		const position = getBalloonPositionData( editor );
 
 
 		balloon.updatePosition( position );
 		balloon.updatePosition( position );

+ 12 - 0
packages/ckeditor5-image/src/image/utils.js

@@ -45,6 +45,18 @@ export function isImageWidget( viewElement ) {
 }
 }
 
 
 /**
 /**
+ * Checks if an image widget is the only selected element.
+ *
+ * @param {module:engine/view/selection~Selection} viewSelection
+ * @returns {Boolean}
+ */
+export function isImageWidgetSelected( viewSelection ) {
+	const viewElement = viewSelection.getSelectedElement();
+
+	return !!( viewElement && isImageWidget( viewElement ) );
+}
+
+/**
  * Checks if the provided model element is an instance of {@link module:engine/model/element~Element Element} and its name
  * Checks if the provided model element is an instance of {@link module:engine/model/element~Element Element} and its name
  * is `image`.
  * is `image`.
  *
  *

+ 6 - 3
packages/ckeditor5-image/src/imagetextalternative.js

@@ -15,6 +15,7 @@ import TextAlternativeFormView from './imagetextalternative/ui/textalternativefo
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 import textAlternativeIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg';
 import textAlternativeIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg';
 import { repositionContextualBalloon, getBalloonPositionData } from './image/ui/utils';
 import { repositionContextualBalloon, getBalloonPositionData } from './image/ui/utils';
+import { isImageWidgetSelected } from './image/utils';
 
 
 import '../theme/imagetextalternative/theme.scss';
 import '../theme/imagetextalternative/theme.scss';
 
 
@@ -119,9 +120,11 @@ export default class ImageTextAlternative extends Plugin {
 			cancel();
 			cancel();
 		} );
 		} );
 
 
-		// Reposition the balloon upon #render.
+		// Reposition the balloon or hide the form if an image widget is no longer selected.
 		this.listenTo( editingView, 'render', () => {
 		this.listenTo( editingView, 'render', () => {
-			if ( this._isVisible ) {
+			if ( !isImageWidgetSelected( editingView.selection ) ) {
+				this._hideForm( true );
+			} else if ( this._isVisible ) {
 				repositionContextualBalloon( editor );
 				repositionContextualBalloon( editor );
 			}
 			}
 		}, { priority: 'low' } );
 		}, { priority: 'low' } );
@@ -176,7 +179,7 @@ export default class ImageTextAlternative extends Plugin {
 	/**
 	/**
 	 * Removes the {@link #_form} from the {@link #_balloon}.
 	 * Removes the {@link #_form} from the {@link #_balloon}.
 	 *
 	 *
-	 * @param {Boolean} focusEditable Controls whether the editing view is focused afterwards.
+	 * @param {Boolean} [focusEditable=false] Controls whether the editing view is focused afterwards.
 	 * @private
 	 * @private
 	 */
 	 */
 	_hideForm( focusEditable ) {
 	_hideForm( focusEditable ) {

+ 2 - 5
packages/ckeditor5-image/src/imagetoolbar.js

@@ -11,7 +11,7 @@ import Template from '@ckeditor/ckeditor5-ui/src/template';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
 import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
-import { isImageWidget } from './image/utils';
+import { isImageWidgetSelected } from './image/utils';
 import { repositionContextualBalloon, getBalloonPositionData } from './image/ui/utils';
 import { repositionContextualBalloon, getBalloonPositionData } from './image/ui/utils';
 
 
 const balloonClassName = 'ck-toolbar-container ck-editor-toolbar-container';
 const balloonClassName = 'ck-toolbar-container ck-editor-toolbar-container';
@@ -103,10 +103,7 @@ export default class ImageToolbar extends Plugin {
 		if ( !editor.ui.focusTracker.isFocused ) {
 		if ( !editor.ui.focusTracker.isFocused ) {
 			this._hideToolbar();
 			this._hideToolbar();
 		} else {
 		} else {
-			const editingView = editor.editing.view;
-			const selectedElement = editingView.selection.getSelectedElement();
-
-			if ( selectedElement && isImageWidget( selectedElement ) ) {
+			if ( isImageWidgetSelected( editor.editing.view.selection ) ) {
 				this._showToolbar();
 				this._showToolbar();
 			} else {
 			} else {
 				this._hideToolbar();
 				this._hideToolbar();

+ 38 - 1
packages/ckeditor5-image/tests/image/utils.js

@@ -4,8 +4,11 @@
  */
  */
 
 
 import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
 import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
+import ViewSelection from '@ckeditor/ckeditor5-engine/src/view/selection';
+import ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment';
+import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
 import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
 import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
-import { toImageWidget, isImageWidget, isImage } from '../../src/image/utils';
+import { toImageWidget, isImageWidget, isImageWidgetSelected, isImage } from '../../src/image/utils';
 import { isWidget, getLabel } from '@ckeditor/ckeditor5-widget/src/utils';
 import { isWidget, getLabel } from '@ckeditor/ckeditor5-widget/src/utils';
 
 
 describe( 'image widget utils', () => {
 describe( 'image widget utils', () => {
@@ -49,6 +52,40 @@ describe( 'image widget utils', () => {
 		} );
 		} );
 	} );
 	} );
 
 
+	describe( 'isImageWidgetSelected()', () => {
+		let frag;
+
+		it( 'should return true when image widget is the only element in the selection', () => {
+			// We need to create a container for the element to be able to create a Range on this element.
+			frag = new ViewDocumentFragment( [ element ] );
+
+			const selection = new ViewSelection( [ ViewRange.createOn( element ) ] );
+
+			expect( isImageWidgetSelected( selection ) ).to.be.true;
+		} );
+
+		it( 'should return false when non-widgetized elements is the only element in the selection', () => {
+			const notWidgetizedElement = new ViewElement( 'p' );
+
+			// We need to create a container for the element to be able to create a Range on this element.
+			frag = new ViewDocumentFragment( [ notWidgetizedElement ] );
+
+			const selection = new ViewSelection( [ ViewRange.createOn( notWidgetizedElement ) ] );
+
+			expect( isImageWidgetSelected( selection ) ).to.be.false;
+		} );
+
+		it( 'should return false when widget element is not the only element in the selection', () => {
+			const notWidgetizedElement = new ViewElement( 'p' );
+
+			frag = new ViewDocumentFragment( [ element, notWidgetizedElement ] );
+
+			const selection = new ViewSelection( [ ViewRange.createIn( frag ) ] );
+
+			expect( isImageWidgetSelected( selection ) ).to.be.false;
+		} );
+	} );
+
 	describe( 'isImage', () => {
 	describe( 'isImage', () => {
 		it( 'should return true for image element', () => {
 		it( 'should return true for image element', () => {
 			const image = new ModelElement( 'image' );
 			const image = new ModelElement( 'image' );

+ 16 - 0
packages/ckeditor5-image/tests/imagetextalternative.js

@@ -168,6 +168,22 @@ describe( 'ImageTextAlternative', () => {
 				editingView.fire( 'render' );
 				editingView.fire( 'render' );
 				sinon.assert.calledOnce( spy );
 				sinon.assert.calledOnce( spy );
 			} );
 			} );
+
+			it( 'should hide the form and focus editable when image widget has been removed by external change', () => {
+				setData( doc, '[<image src=""></image>]' );
+				button.fire( 'execute' );
+
+				const remveSpy = sinon.spy( balloon, 'remove' );
+				const focusSpy = sinon.spy( editor.editing.view, 'focus' );
+
+				// EnqueueChanges automatically fires #render event.
+				doc.enqueueChanges( () => {
+					doc.batch( 'transparent' ).remove( doc.selection.getFirstRange() );
+				} );
+
+				sinon.assert.calledWithExactly( remveSpy, form );
+				sinon.assert.calledOnce( focusSpy );
+			} );
 		} );
 		} );
 
 
 		describe( 'integration with the editor focus', () => {
 		describe( 'integration with the editor focus', () => {

+ 7 - 2
packages/ckeditor5-image/tests/manual/tickets/106/1.js

@@ -108,9 +108,14 @@ function startExternalDelete( editor ) {
 	const document = editor.document;
 	const document = editor.document;
 	const bath = document.batch( 'transparent' );
 	const bath = document.batch( 'transparent' );
 
 
-	wait( 3000 ).then( () => {
+	function removeSecondBlock() {
 		document.enqueueChanges( () => {
 		document.enqueueChanges( () => {
 			bath.remove( Range.createFromPositionAndShift( new Position( document.getRoot(), [ 1 ] ), 1 ) );
 			bath.remove( Range.createFromPositionAndShift( new Position( document.getRoot(), [ 1 ] ), 1 ) );
 		} );
 		} );
-	} );
+	}
+
+	wait( 3000 )
+		.then( () => removeSecondBlock() )
+		.then( () => wait( 3000 ) )
+		.then( () => removeSecondBlock() );
 }
 }

+ 1 - 0
packages/ckeditor5-image/tests/manual/tickets/106/1.md

@@ -11,3 +11,4 @@
 
 
 1. Click **Start external changes** then quickly select the image in the content, then from the toolbar open the **text alternative** editing balloon.
 1. Click **Start external changes** then quickly select the image in the content, then from the toolbar open the **text alternative** editing balloon.
 2. Observe if the balloon remains attached to the image as the content is deleted.
 2. Observe if the balloon remains attached to the image as the content is deleted.
+3. Wait a bit more and observe if the balloon is removed together with an image.