Преглед изворни кода

Merge branch 'master' into i/7956-reconversion-trigger-by

Maciej Gołaszewski пре 5 година
родитељ
комит
213cb9db9a
22 измењених фајлова са 227 додато и 70 уклоњено
  1. 6 1
      docs/_snippets/examples/document-editor.js
  2. 1 1
      docs/_snippets/examples/multi-root-editor.html
  3. 3 3
      docs/_snippets/examples/multi-root-editor.js
  4. 3 2
      docs/framework/guides/contributing/testing-environment.md
  5. 6 4
      package.json
  6. 3 3
      packages/ckeditor5-image/docs/features/image.md
  7. 21 20
      packages/ckeditor5-image/lang/contexts.json
  8. 11 10
      packages/ckeditor5-image/src/imageupload.js
  9. 4 1
      packages/ckeditor5-image/src/imageupload/imageuploadui.js
  10. 2 2
      packages/ckeditor5-image/src/imageupload/ui/imageuploadformrowview.js
  11. 20 8
      packages/ckeditor5-image/src/imageupload/ui/imageuploadpanelview.js
  12. 4 4
      packages/ckeditor5-image/src/imageupload/utils.js
  13. 61 0
      packages/ckeditor5-image/tests/imageupload/imageuploadui.js
  14. 5 5
      packages/ckeditor5-image/tests/imageupload/ui/imageuploadpanelview.js
  15. 1 1
      packages/ckeditor5-image/tests/manual/imageuploadviaurl.js
  16. 1 1
      packages/ckeditor5-paste-from-office/src/filters/list.js
  17. 1 0
      packages/ckeditor5-paste-from-office/tests/_data/paste-from-google-docs/lists/empty-list-item/input.html
  18. 1 0
      packages/ckeditor5-paste-from-office/tests/_data/paste-from-google-docs/lists/empty-list-item/model.html
  19. 7 2
      packages/ckeditor5-paste-from-office/tests/_data/paste-from-google-docs/lists/index.js
  20. 3 1
      packages/ckeditor5-theme-lark/theme/ckeditor5-ui/components/panel/balloonpanel.css
  21. 20 0
      packages/ckeditor5-utils/src/env.js
  22. 43 1
      packages/ckeditor5-utils/tests/env.js

+ 6 - 1
docs/_snippets/examples/document-editor.js

@@ -6,11 +6,16 @@
 /* globals console, window, document */
 
 import DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document/src/ckeditor';
+import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize';
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
 DecoupledEditor
 	.create( document.querySelector( '.document-editor__editable' ), {
-		cloudServices: CS_CONFIG
+		extraPlugins: [ ImageResize ],
+		cloudServices: CS_CONFIG,
+		image: {
+			toolbar: [ 'imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' ]
+		}
 	} )
 	.then( editor => {
 		const toolbarContainer = document.querySelector( '.document-editor__toolbar' );

+ 1 - 1
docs/_snippets/examples/multi-root-editor.html

@@ -11,7 +11,7 @@
 
 		<h4>Valletta</h4>
 
-		<figure class="image image-style-align-right">
+		<figure class="image image-style-side">
 			<img alt="Picture of a sunlit facade of a Maltan building." src="%BASE_PATH%/assets/img/malta.jpg">
 			<figcaption>It's siesta time in Valletta.</figcaption>
 		</figure>

+ 3 - 3
docs/_snippets/examples/multi-root-editor.js

@@ -404,8 +404,9 @@ MultirootEditor
 		toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'imageUpload', 'blockQuote',
 			'insertTable', 'mediaEmbed', 'undo', 'redo' ],
 		image: {
-			toolbar: [ 'imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' ],
-			styles: [ 'full', 'alignLeft', 'alignRight' ]
+			toolbar: [ 'imageTextAlternative', '|', 'imageStyle:full',
+				'imageStyle:side' ],
+			styles: [ 'full', 'side' ]
 		},
 		table: {
 			contentToolbar: [
@@ -430,4 +431,3 @@ MultirootEditor
 	.catch( err => {
 		console.error( err.stack );
 	} );
-

+ 3 - 2
docs/framework/guides/contributing/testing-environment.md

@@ -54,11 +54,12 @@ In order to start the manual tests server, use the `yarn run manual` task.
 
 The task accepts the following options:
 
-* `--source-map` (alias `-s`) &ndash; Whether to generate useful source maps for the code.
 * `--files` &ndash; Specifies test files to run. Accepts a package name or a glob. For example `--files=ckeditor5` will only run tests from the CKEditor 5 main package. Read more about the [rules for converting the `--files` option to a glob pattern](https://github.com/ckeditor/ckeditor5-dev/tree/master/packages/ckeditor5-dev-tests#rules-for-converting---files-option-to-glob-pattern).
-* `--additionalLanguages="ar,pl,..."` &ndash; Specifies extra languages to the [CKEditor 5 webpack plugin](https://www.npmjs.com/package/@ckeditor/ckeditor5-dev-webpack-plugin). Check out the {@link features/ui-language UI language guide} to learn more.
+* `--language="pl"` &ndash; The main language build in into all test editors, passed to the [CKEditor 5 webpack plugin](https://www.npmjs.com/package/@ckeditor/ckeditor5-dev-webpack-plugin).  Check out the {@link features/ui-language UI language guide} to learn more. When unspecified, `'en'` is passed to the test runner.
+* `--additionalLanguages="ar,pl,..."` &ndash; Specifies extra languages passed to the [CKEditor 5 webpack plugin](https://www.npmjs.com/package/@ckeditor/ckeditor5-dev-webpack-plugin). Check out the {@link features/ui-language UI language guide} to learn more.
 * `--debug` (alias `-d`) &ndash; Allows specifying custom debug flags. For example, the `--debug engine` option uncomments the `// @if CK_DEBUG_ENGINE //` lines in the code. Note that by default `--debug` is set to `true` even if you did not specify it. This enables the base set of debug logs (`// @if CK_DEBUG //`) which should always be enabled in the testing environment. You can completely turn off the debug mode by setting the `--debug false` option.
 * `--port` &ndash; Specifies the port for the server to use. Defaults to `8125`.
+* `--identityFile="/path/to/file.js"` &ndash; Path to the file containing the license key(s) for closed–source features.
 
 It starts the server available at http://localhost:8125.
 

+ 6 - 4
package.json

@@ -76,16 +76,18 @@
     "@ckeditor/ckeditor5-word-count": "^22.0.0"
   },
   "devDependencies": {
-    "@ckeditor/ckeditor5-comments": "^21.0.0",
+    "@ckeditor/ckeditor5-comments": "^22.0.0",
     "@ckeditor/ckeditor5-dev-docs": "^23.2.0",
     "@ckeditor/ckeditor5-dev-env": "^23.2.0",
-    "@ckeditor/ckeditor5-dev-tests": "^23.2.0",
+    "@ckeditor/ckeditor5-dev-tests": "^23.3.0",
     "@ckeditor/ckeditor5-dev-utils": "^23.2.0",
     "@ckeditor/ckeditor5-dev-webpack-plugin": "^23.2.0",
+    "@ckeditor/ckeditor5-export-pdf": "^1.0.0",
+    "@ckeditor/ckeditor5-export-word": "^0.0.2",
     "@ckeditor/ckeditor5-inspector": "^2.2.0",
     "@ckeditor/ckeditor5-react": "^2.1.0",
-    "@ckeditor/ckeditor5-real-time-collaboration": "^21.0.0",
-    "@ckeditor/ckeditor5-track-changes": "^21.0.0",
+    "@ckeditor/ckeditor5-real-time-collaboration": "^22.0.0",
+    "@ckeditor/ckeditor5-track-changes": "^22.0.0",
     "@webspellchecker/wproofreader-ckeditor5": "^1.0.5",
     "@wiris/mathtype-ckeditor5": "7.20.0",
     "babel-standalone": "^6.26.0",

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

@@ -84,9 +84,9 @@ See the {@link features/image-upload Image upload} guide.
 
 ## Inserting images via source URL
 
-Besides the ability to insert images by uploading them directly from your disk or via CKFinder, you can also configure the editor to allow inserting images via source URL.
+Besides the ability to insert images by uploading them directly from your disk or via CKFinder, you can also configure CKEditor 5 to allow inserting images via source URL.
 
-In order to enable this option configure `image.upload.panel.items` like below:
+In order to enable this option, configure {@link module:image/imageupload~ImageUploadPanelConfig#items `image.upload.panel.items`} like below:
 
 ```js
 ClassicEditor
@@ -103,7 +103,7 @@ ClassicEditor
 	} )
 ```
 
-This will extend the standalone **Insert image** button in the toolbar by adding the dropdown panel with the new feature (click the arrow next to the button). To see how it works, please take a look at the demo below:
+This will extend the standalone **Insert image** button in the toolbar by adding a dropdown panel with the new feature. To open the panel and add the image URL, click the arrow next to the image button. Check the demo below to insert a new image via URL or update an existing image by selecting it, opening the dropdown panel and pasting a new URL.
 
 {@snippet features/image-insert-via-url}
 

+ 21 - 20
packages/ckeditor5-image/lang/contexts.json

@@ -1,23 +1,24 @@
 {
-	"image widget": "Label for the image widget.",
-	"Side image": "Label for the Side image option.",
-	"Full size image": "Label for the Full size image option.",
-	"Left aligned image": "Label for the Left aligned image option",
-	"Centered image": "Label for the Centered image option",
-	"Right aligned image": "Label for the Right aligned image option",
-	"Change image text alternative": "Label for the Change image text alternative button.",
-	"Text alternative": "Label for the image text alternative input.",
-	"Enter image caption": "Placeholder text for image caption displayed when caption is empty.",
-	"Insert image": "Label for the insert image toolbar button.",
-	"Upload failed": "Title of the notification displayed when upload fails.",
+	"image widget": "The label for the image widget.",
+	"Side image": "The label for the Side image option.",
+	"Full size image": "The label for the Full size image option.",
+	"Left aligned image": "The label for the Left aligned image option.",
+	"Centered image": "The label for the Centered image option.",
+	"Right aligned image": "The label for the Right aligned image option.",
+	"Change image text alternative": "The label for the Change image text alternative button.",
+	"Text alternative": "The label for the image text alternative input.",
+	"Enter image caption": "The placeholder text for the image caption displayed when the caption is empty.",
+	"Insert image": "The label for the insert image toolbar button.",
+	"Upload failed": "The title of the notification displayed when upload fails.",
 	"Image toolbar": "The label used by assistive technologies describing an image toolbar attached to an image widget.",
-	"Resize image": "The label used for the dropdown in the image toolbar containing defined resize options",
-	"Resize image to %0": "The label used for the standalone resize options buttons in the image toolbar",
-	"Resize image to the original size": "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers",
-	"Original": "Default label for the resize option that resets the size of the image.",
-	"Image resize list": "The accessibility label of the image resize dropdown list for the screen readers.",
-	"Insert": "The label of submit form button if image src URL input has no value",
-	"Update": "The label of submit form button if image src URL input has value",
-	"Insert image via URL": "The input label for the Insert image via URL form",
-	"Paste the image source URL.": "The tip label below the Insert image via URL form"
+	"Resize image": "The label used for the dropdown in the image toolbar containing defined resize options.",
+	"Resize image to %0": "The label used for the standalone resize options buttons in the image toolbar.",
+	"Resize image to the original size": "The accessibility label of the standalone image resize reset option button in the image toolbar for screen readers.",
+	"Original": "The default label for the resize option that resets the size of the image.",
+	"Image resize list": "The accessibility label of the image resize dropdown for screen readers.",
+	"Insert": "The label of the form submit button if the image source URL input has no value.",
+	"Update": "The label of the form submit button if the image source URL input has a value.",
+	"Insert image via URL": "The input label for the Insert image via URL form.",
+	"Update image URL": "The input label for the Insert image via URL form for a pre-existing image.",
+	"Paste the image source URL.": "The content of the tip below the Insert image via URL form."
 }

+ 11 - 10
packages/ckeditor5-image/src/imageupload.js

@@ -42,7 +42,7 @@ export default class ImageUpload extends Plugin {
 }
 
 /**
- * Image upload configuration.
+ * The image upload configuration.
  *
  * @member {module:image/imageupload~ImageUploadConfig} module:image/image~ImageConfig#upload
  */
@@ -75,7 +75,7 @@ export default class ImageUpload extends Plugin {
  *		};
  *
  * The type string should match [one of the sub-types](https://www.iana.org/assignments/media-types/media-types.xhtml#image)
- * of the image MIME type. E.g. for the `image/jpeg` MIME type, add `'jpeg'` to your image upload configuration.
+ * of the image MIME type. For example, for the `image/jpeg` MIME type, add `'jpeg'` to your image upload configuration.
  *
  * **Note:** This setting only restricts some image types to be selected and uploaded through the CKEditor UI and commands. Image type
  * recognition and filtering should also be implemented on the server which accepts image uploads.
@@ -85,7 +85,7 @@ export default class ImageUpload extends Plugin {
  */
 
 /**
- * Image upload panel view configuration.
+ * The image upload panel view configuration.
  *
  * @protected
  * @member {module:image/imageupload~ImageUploadPanelConfig} module:image/imageupload~ImageUploadConfig#panel
@@ -98,7 +98,7 @@ export default class ImageUpload extends Plugin {
  *			.create( editorElement, {
  * 				image: {
  * 					upload: {
- * 						panel: ... // panel settings goes here
+ * 						panel: ... // Panel settings go here.
  * 					}
  * 				}
  *			} )
@@ -114,12 +114,13 @@ export default class ImageUpload extends Plugin {
 /**
  * The list of {@link module:image/imageupload~ImageUpload} integrations.
  *
- * The option accepts string tokens.
- * * for predefined integrations, we have two special strings: `insertImageViaUrl` and `openCKFinder`.
- * The former adds **Insert image via URL** feature, while the latter adds built-in **CKFinder** integration.
- * * for custom integrations, each string should be a name of the already registered component.
- * If you have a plugin `PluginX` that registers `pluginXButton` component, then the integration token
- * in that case should be `pluginXButton`.
+ * This option accepts string tokens:
+ * * For predefined integrations, there are two special strings: `insertImageViaUrl` and `openCKFinder`.
+ * The former adds the {@glink features/image#inserting-images-via-source-url Insert image via URL} feature, while the latter adds the
+ * built-in {@glink features/image-upload/ckfinder CKFinder} integration.
+ * * For custom integrations, each string should be a name of the already registered component.
+ * If you have a plugin `PluginX` that registers the `pluginXButton` component, then in that case the integration token
+ * should be `pluginXButton`.
  *
  *		// Add `insertImageViaUrl`, `openCKFinder` and custom `pluginXButton` integrations.
  *		const imageUploadConfig = {

+ 4 - 1
packages/ckeditor5-image/src/imageupload/imageuploadui.js

@@ -64,6 +64,7 @@ export default class ImageUploadUI extends Plugin {
 		const editor = this.editor;
 		const t = editor.t;
 		const insertButtonView = imageUploadView.insertButtonView;
+		const insertImageViaUrlForm = imageUploadView.getIntegration( 'insertImageViaUrl' );
 
 		dropdownView.bind( 'isEnabled' ).to( command );
 
@@ -76,9 +77,11 @@ export default class ImageUploadUI extends Plugin {
 				if ( isImage( selectedElement ) ) {
 					imageUploadView.imageURLInputValue = selectedElement.getAttribute( 'src' );
 					insertButtonView.label = t( 'Update' );
+					insertImageViaUrlForm.label = t( 'Update image URL' );
 				} else {
 					imageUploadView.imageURLInputValue = '';
 					insertButtonView.label = t( 'Insert' );
+					insertImageViaUrlForm.label = t( 'Insert image via URL' );
 				}
 			}
 		} );
@@ -142,7 +145,7 @@ export default class ImageUploadUI extends Plugin {
 	}
 
 	/**
-	 * Creates and sets up file dialog button view.
+	 * Creates and sets up the file dialog button view.
 	 *
 	 * @param {module:utils/locale~Locale} locale The localization services instance.
 	 *

+ 2 - 2
packages/ckeditor5-image/src/imageupload/ui/imageuploadformrowview.js

@@ -15,8 +15,8 @@ import '../../../theme/imageuploadformrowview.css';
  * The class representing a single row in a complex form,
  * used by {@link module:image/imageupload/ui/imageuploadpanelview~ImageUploadPanelView}.
  *
- * **Note**: For now this class is private. When more use cases arrive (beyond ckeditor5-table and ckeditor5-image),
- * it will become a component in ckeditor5-ui.
+ * **Note**: For now this class is private. When more use cases appear (beyond `ckeditor5-table` and `ckeditor5-image`),
+ * it will become a component in `ckeditor5-ui`.
  *
  * @private
  * @extends module:ui/view~View

+ 20 - 8
packages/ckeditor5-image/src/imageupload/ui/imageuploadpanelview.js

@@ -38,8 +38,8 @@ export default class ImageUploadPanelView extends View {
 	/**
 	 * Creates a view for the dropdown panel of {@link module:image/imageupload/imageuploadui~ImageUploadUI}.
 	 *
-	 * @param {module:utils/locale~Locale} [locale] The localization services instance..
-	 * @param {Object} [integrations] Integrations object that contain
+	 * @param {module:utils/locale~Locale} [locale] The localization services instance.
+	 * @param {Object} [integrations] An integrations object that contains
 	 * components (or tokens for components) to be shown in the panel view.
 	 */
 	constructor( locale, integrations ) {
@@ -69,7 +69,7 @@ export default class ImageUploadPanelView extends View {
 		this.dropdownView = this._createDropdownView( locale );
 
 		/**
-		 * Value of the URL input.
+		 * The value of the URL input.
 		 *
 		 * @member {String} #imageURLInputValue
 		 * @observable
@@ -122,7 +122,7 @@ export default class ImageUploadPanelView extends View {
 		} );
 
 		/**
-		 * Collection of the defined integrations for inserting the images.
+		 * A collection of the defined integrations for inserting the images.
 		 *
 		 * @private
 		 * @member {module:utils/collection~Collection}
@@ -139,6 +139,8 @@ export default class ImageUploadPanelView extends View {
 					} );
 				}
 
+				integrationView.name = integration;
+
 				this._integrations.add( integrationView );
 			}
 		}
@@ -214,7 +216,17 @@ export default class ImageUploadPanelView extends View {
 	}
 
 	/**
-	 * Creates dropdown view.
+	 * Returns a view of the integration.
+	 *
+	 * @param {string} name The name of the integration.
+	 * @returns {module:ui/view~View}
+	 */
+	getIntegration( name ) {
+		return this._integrations.find( integration => integration.name === name );
+	}
+
+	/**
+	 * Creates the dropdown view.
 	 *
 	 * @param {module:utils/locale~Locale} locale The localization services instance.
 	 *
@@ -282,7 +294,7 @@ export default class ImageUploadPanelView extends View {
 	}
 
 	/**
-	 * Focuses the fist {@link #_focusables} in the form.
+	 * Focuses the first {@link #_focusables focusable} in the form.
 	 */
 	focus() {
 		this._focusCycler.focusFirst();
@@ -291,13 +303,13 @@ export default class ImageUploadPanelView extends View {
 
 /**
  * Fired when the form view is submitted (when one of the children triggered the submit event),
- * e.g. click on {@link #insertButtonView}.
+ * e.g. by a click on {@link #insertButtonView}.
  *
  * @event submit
  */
 
 /**
- * Fired when the form view is canceled, e.g. click on {@link #cancelButtonView}.
+ * Fired when the form view is canceled, e.g. by a click on {@link #cancelButtonView}.
  *
  * @event cancel
  */

+ 4 - 4
packages/ckeditor5-image/src/imageupload/utils.js

@@ -87,12 +87,12 @@ function getImageMimeType( blob, src ) {
 }
 
 /**
- * Creates integrations object that will be passed to the
+ * Creates an integrations object that will be passed to the
  * {@link module:image/imageupload/ui/imageuploadpanelview~ImageUploadPanelView}.
  *
- * @param {module:core/editor/editor~Editor} editor Editor instance.
+ * @param {module:core/editor/editor~Editor} editor The editor instance.
  *
- * @returns {Object.<String, module:ui/view~View>} Integrations object.
+ * @returns {Object.<String, module:ui/view~View>} The integrations object.
  */
 export function prepareIntegrations( editor ) {
 	const panelItems = editor.config.get( 'image.upload.panel.items' );
@@ -133,7 +133,7 @@ export function prepareIntegrations( editor ) {
 }
 
 /**
- * Creates labeled field view.
+ * Creates a labeled field view.
  *
  * @param {module:utils/locale~Locale} locale The localization services instance.
  *

+ 61 - 0
packages/ckeditor5-image/tests/imageupload/imageuploadui.js

@@ -438,6 +438,67 @@ describe( 'ImageUploadUI', () => {
 			} );
 		} );
 
+		describe( 'dropdown panel integrations', () => {
+			describe( 'insert image via URL form', () => {
+				it( 'should have "Insert image via URL" label on inserting new image', () => {
+					const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+					const viewDocument = editor.editing.view.document;
+
+					editor.setData( '<p>test</p>' );
+
+					editor.editing.view.change( writer => {
+						writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'end' );
+					} );
+
+					const el = viewDocument.selection.getSelectedElement();
+
+					const data = fakeEventData();
+					const eventInfo = new EventInfo( el, 'click' );
+					const domEventDataMock = new DomEventData( viewDocument, eventInfo, data );
+
+					viewDocument.fire( 'click', domEventDataMock );
+
+					dropdown.isOpen = true;
+
+					const inputValue = dropdown.panelView.children.first.imageURLInputValue;
+
+					const insertImageViaUrlForm = dropdown.panelView.children.first.getIntegration( 'insertImageViaUrl' );
+
+					expect( dropdown.isOpen ).to.be.true;
+					expect( inputValue ).to.equal( '' );
+					expect( insertImageViaUrlForm.label ).to.equal( 'Insert image via URL' );
+				} );
+
+				it( 'should have "Update image URL" label on updating the image source URL', () => {
+					const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+					const viewDocument = editor.editing.view.document;
+
+					editor.setData( '<figure><img src="/assets/sample.png" /></figure>' );
+
+					editor.editing.view.change( writer => {
+						writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'on' );
+					} );
+
+					const el = viewDocument.selection.getSelectedElement();
+
+					const data = fakeEventData();
+					const eventInfo = new EventInfo( el, 'click' );
+					const domEventDataMock = new DomEventData( viewDocument, eventInfo, data );
+
+					viewDocument.fire( 'click', domEventDataMock );
+
+					dropdown.isOpen = true;
+
+					const inputValue = dropdown.panelView.children.first.imageURLInputValue;
+					const insertImageViaUrlForm = dropdown.panelView.children.first.getIntegration( 'insertImageViaUrl' );
+
+					expect( dropdown.isOpen ).to.be.true;
+					expect( inputValue ).to.equal( '/assets/sample.png' );
+					expect( insertImageViaUrlForm.label ).to.equal( 'Update image URL' );
+				} );
+			} );
+		} );
+
 		it( 'should remove all attributes from model except "src" when updating the image source URL', () => {
 			const viewDocument = editor.editing.view.document;
 			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );

+ 5 - 5
packages/ckeditor5-image/tests/imageupload/ui/imageuploadpanelview.js

@@ -178,7 +178,7 @@ describe( 'ImageUploadPanelView', () => {
 			} );
 			view.render();
 
-			sinon.assert.calledWithExactly( spy.getCall( 0 ), view._integrations.get( 0 ).element );
+			sinon.assert.calledWithExactly( spy.getCall( 0 ), view.getIntegration( 'insertImageViaUrl' ).element );
 			sinon.assert.calledWithExactly( spy.getCall( 1 ), view.insertButtonView.element );
 			sinon.assert.calledWithExactly( spy.getCall( 2 ), view.cancelButtonView.element );
 		} );
@@ -224,7 +224,7 @@ describe( 'ImageUploadPanelView', () => {
 
 			event.stopPropagation = spy;
 
-			view._integrations.get( 0 ).element.dispatchEvent( event );
+			view.getIntegration( 'insertImageViaUrl' ).element.dispatchEvent( event );
 			sinon.assert.calledOnce( spy );
 		} );
 
@@ -238,7 +238,7 @@ describe( 'ImageUploadPanelView', () => {
 
 				// Mock the url input is focused.
 				view.focusTracker.isFocused = true;
-				view.focusTracker.focusedElement = view._integrations.get( 0 ).element;
+				view.focusTracker.focusedElement = view.getIntegration( 'insertImageViaUrl' ).element;
 
 				const spy = sinon.spy( view.insertButtonView, 'focus' );
 
@@ -272,7 +272,7 @@ describe( 'ImageUploadPanelView', () => {
 
 	describe( 'focus()', () => {
 		it( 'should focus on the first integration', () => {
-			const spy = sinon.spy( view._integrations.get( 0 ), 'focus' );
+			const spy = sinon.spy( view.getIntegration( 'insertImageViaUrl' ), 'focus' );
 
 			view.focus();
 
@@ -282,7 +282,7 @@ describe( 'ImageUploadPanelView', () => {
 
 	describe( 'Insert image via URL integration input', () => {
 		it( 'should be bound with #imageURLInputValue', () => {
-			const form = view._integrations.get( 0 );
+			const form = view.getIntegration( 'insertImageViaUrl' );
 
 			form.fieldView.element.value = 'abc';
 			form.fieldView.fire( 'input' );

+ 1 - 1
packages/ckeditor5-image/tests/manual/imageuploadviaurl.js

@@ -47,7 +47,7 @@ ClassicEditor
 		}
 	} )
 	.then( editor => {
-		window.editor2 = editor;
+		window.editor = editor;
 
 		// Register fake adapter.
 		editor.plugins.get( 'FileRepository' ).createUploadAdapter = loader => {

+ 1 - 1
packages/ckeditor5-paste-from-office/src/filters/list.js

@@ -93,7 +93,7 @@ export function unwrapParagraphInListItem( documentFragment, writer ) {
 			// Google Docs allows on single paragraph inside LI.
 			const firstChild = element.getChild( 0 );
 
-			if ( firstChild.is( 'element', 'p' ) ) {
+			if ( firstChild && firstChild.is( 'element', 'p' ) ) {
 				writer.unwrapElement( firstChild );
 			}
 		}

+ 1 - 0
packages/ckeditor5-paste-from-office/tests/_data/paste-from-google-docs/lists/empty-list-item/input.html

@@ -0,0 +1 @@
+<meta charset='utf-8'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-7f8963f3-7fff-683a-b3de-156ff66d4860"><ul style="margin-top:0;margin-bottom:0;"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Item 1</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><br /></li></ul></b>

+ 1 - 0
packages/ckeditor5-paste-from-office/tests/_data/paste-from-google-docs/lists/empty-list-item/model.html

@@ -0,0 +1 @@
+<listItem listIndent="0" listType="bulleted">Item 1</listItem><listItem listIndent="0" listType="bulleted"></listItem>

+ 7 - 2
packages/ckeditor5-paste-from-office/tests/_data/paste-from-google-docs/lists/index.js

@@ -15,18 +15,23 @@ import repeatedlyNestedListModel from './repeatedly-nested-list/model.html';
 import partiallySelected from './partially-selected/input.html';
 import partiallySelectedModel from './partially-selected/model.html';
 
+import emptyListItem from './empty-list-item/input.html';
+import emptyListItemModel from './empty-list-item/model.html';
+
 export const fixtures = {
 	input: {
 		nestedOrderedList,
 		mixedList,
 		repeatedlyNestedList,
-		partiallySelected
+		partiallySelected,
+		emptyListItem
 	},
 	model: {
 		nestedOrderedList: nestedOrderedListModel,
 		mixedList: mixedListModel,
 		repeatedlyNestedList: repeatedlyNestedListModel,
-		partiallySelected: partiallySelectedModel
+		partiallySelected: partiallySelectedModel,
+		emptyListItem: emptyListItemModel
 	}
 };
 

+ 3 - 1
packages/ckeditor5-theme-lark/theme/ckeditor5-ui/components/panel/balloonpanel.css

@@ -10,6 +10,7 @@
 	--ck-balloon-arrow-offset: 2px;
 	--ck-balloon-arrow-height: 10px;
 	--ck-balloon-arrow-half-width: 8px;
+	--ck-balloon-arrow-drop-shadow: 0 2px 2px var(--ck-color-shadow-drop);
 }
 
 .ck.ck-balloon-panel {
@@ -53,7 +54,8 @@
 		}
 
 		&::before {
-			border-color: var(--ck-color-panel-border) transparent  transparent;
+			border-color: var(--ck-color-panel-border) transparent transparent;
+			filter: drop-shadow(var(--ck-balloon-arrow-drop-shadow));
 		}
 
 		&::after {

+ 20 - 0
packages/ckeditor5-utils/src/env.js

@@ -49,6 +49,14 @@ const env = {
 	 */
 	isAndroid: isAndroid( userAgent ),
 
+	/**
+	 * Indicates that the application is running in a browser using the Blink engine.
+	 *
+	 * @static
+	 * @type {Boolean}
+	 */
+	isBlink: isBlink( userAgent ),
+
 	/**
 	 * Environment features information.
 	 *
@@ -109,6 +117,18 @@ export function isAndroid( userAgent ) {
 	return userAgent.indexOf( 'android' ) > -1;
 }
 
+/**
+ * Checks if User Agent represented by the string is Blink engine.
+ *
+ * @param {String} userAgent **Lowercase** `navigator.userAgent` string.
+ * @returns {Boolean} Whether User Agent is Blink engine or not.
+ */
+export function isBlink( userAgent ) {
+	// The Edge browser before switching to the Blink engine used to report itself as Chrome (and "Edge/")
+	// but after switching to the Blink it replaced "Edge/" with "Edg/".
+	return userAgent.indexOf( 'chrome/' ) > -1 && userAgent.indexOf( 'edge/' ) < 0;
+}
+
 /**
  * Checks if the current environment supports ES2018 Unicode properties like `\p{P}` or `\p{L}`.
  * More information about unicode properties might be found

+ 43 - 1
packages/ckeditor5-utils/tests/env.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-import env, { isMac, isGecko, isSafari, isAndroid, isRegExpUnicodePropertySupported } from '../src/env';
+import env, { isMac, isGecko, isSafari, isAndroid, isRegExpUnicodePropertySupported, isBlink } from '../src/env';
 
 function toLowerCase( str ) {
 	return str.toLowerCase();
@@ -38,6 +38,12 @@ describe( 'Env', () => {
 		} );
 	} );
 
+	describe( 'isBlink', () => {
+		it( 'is a boolean', () => {
+			expect( env.isBlink ).to.be.a( 'boolean' );
+		} );
+	} );
+
 	describe( 'features', () => {
 		it( 'is an object', () => {
 			expect( env.features ).to.be.an( 'object' );
@@ -147,6 +153,42 @@ describe( 'Env', () => {
 		/* eslint-enable max-len */
 	} );
 
+	describe( 'isBlink()', () => {
+		/* eslint-disable max-len */
+		it( 'returns true for Blink UA strings', () => {
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
+			) ) ).to.be.true;
+
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36'
+			) ) ).to.be.true;
+
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 Edg/84.0.522.52'
+			) ) ).to.be.true;
+		} );
+
+		it( 'returns false for non-Blink UA strings', () => {
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15'
+			) ) ).to.be.false;
+
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1'
+			) ) ).to.be.false;
+
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'
+			) ) ).to.be.false;
+
+			expect( isBlink( toLowerCase(
+				'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134'
+			) ) ).to.be.false;
+		} );
+		/* eslint-enable max-len */
+	} );
+
 	describe( 'isRegExpUnicodePropertySupported()', () => {
 		it( 'should detect accessibility of unicode properties', () => {
 			// Usage of regular expression literal cause error during build (ckeditor/ckeditor5-dev#534)