Explorar el Código

Merge branch 'master' into docs-pastefromoffice-style.

Filip Tokarski hace 5 años
padre
commit
94dbaa5e38
Se han modificado 100 ficheros con 1578 adiciones y 279 borrados
  1. 1 1
      docs/_snippets/framework/tutorials/block-widget.js
  2. 1 1
      docs/_snippets/framework/tutorials/inline-widget.js
  3. 1 1
      docs/_snippets/framework/tutorials/using-react-in-widget.html
  4. 2 2
      docs/framework/guides/architecture/ui-library.md
  5. 1 1
      docs/framework/guides/deep-dive/localization.md
  6. 2 2
      docs/framework/guides/tutorials/implementing-an-inline-widget.md
  7. 2 2
      docs/framework/guides/tutorials/using-react-in-a-widget.md
  8. 1 1
      packages/ckeditor5-alignment/src/alignmentui.js
  9. 1 1
      packages/ckeditor5-basic-styles/src/bold/boldui.js
  10. 1 1
      packages/ckeditor5-basic-styles/src/code/codeediting.js
  11. 1 1
      packages/ckeditor5-basic-styles/src/code/codeui.js
  12. 1 1
      packages/ckeditor5-basic-styles/src/italic/italicui.js
  13. 1 1
      packages/ckeditor5-basic-styles/src/strikethrough/strikethroughui.js
  14. 1 1
      packages/ckeditor5-basic-styles/src/subscript/subscriptui.js
  15. 1 1
      packages/ckeditor5-basic-styles/src/superscript/superscriptui.js
  16. 1 1
      packages/ckeditor5-basic-styles/src/underline/underlineui.js
  17. 1 1
      packages/ckeditor5-basic-styles/tests/code/codeediting.js
  18. 1 1
      packages/ckeditor5-block-quote/src/blockquoteui.js
  19. 1 1
      packages/ckeditor5-ckfinder/src/ckfinderui.js
  20. 2 2
      packages/ckeditor5-code-block/src/codeblockui.js
  21. 7 0
      packages/ckeditor5-core/src/editor/editor.js
  22. 12 0
      packages/ckeditor5-core/tests/editor/editor.js
  23. 1 1
      packages/ckeditor5-core/tests/manual/readonly.js
  24. 22 0
      packages/ckeditor5-engine/tests/common.js
  25. 1 1
      packages/ckeditor5-font/src/fontfamily/fontfamilyui.js
  26. 1 1
      packages/ckeditor5-font/src/fontsize/fontsizeui.js
  27. 1 1
      packages/ckeditor5-font/src/ui/colorui.js
  28. 1 1
      packages/ckeditor5-heading/src/headingbuttonsui.js
  29. 1 1
      packages/ckeditor5-heading/src/headingui.js
  30. 2 2
      packages/ckeditor5-highlight/src/highlightui.js
  31. 1 1
      packages/ckeditor5-horizontal-line/src/horizontallineui.js
  32. 1 1
      packages/ckeditor5-html-embed/src/htmlembedui.js
  33. 1 1
      packages/ckeditor5-html-embed/theme/htmlembed.css
  34. 2 0
      packages/ckeditor5-image/docs/_snippets/features/build-image-source.js
  35. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-caption.js
  36. 3 0
      packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
  37. 20 0
      packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.js
  38. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-insert-via-url.js
  39. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-link.js
  40. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons-dropdown.js
  41. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons.js
  42. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-resize-px.js
  43. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-resize.js
  44. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-style-presentational.js
  45. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-style.js
  46. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image-toolbar.js
  47. 1 1
      packages/ckeditor5-image/docs/_snippets/features/image.js
  48. 1 0
      packages/ckeditor5-image/docs/api/image.md
  49. 17 0
      packages/ckeditor5-image/docs/features/image.md
  50. 174 0
      packages/ckeditor5-image/src/autoimage.js
  51. 4 6
      packages/ckeditor5-image/src/image/imageinsertcommand.js
  52. 13 12
      packages/ckeditor5-image/src/image/utils.js
  53. 1 1
      packages/ckeditor5-image/src/imageinsert/imageinsertui.js
  54. 1 1
      packages/ckeditor5-image/src/imageresize/imageresizebuttons.js
  55. 38 12
      packages/ckeditor5-image/src/imageresize/imageresizehandles.js
  56. 1 1
      packages/ckeditor5-image/src/imagestyle/imagestyleui.js
  57. 1 1
      packages/ckeditor5-image/src/imagetextalternative/imagetextalternativeui.js
  58. 6 9
      packages/ckeditor5-image/src/imageupload/imageuploadcommand.js
  59. 555 0
      packages/ckeditor5-image/tests/autoimage.js
  60. 3 9
      packages/ckeditor5-image/tests/image/utils.js
  61. 37 0
      packages/ckeditor5-image/tests/imageresize/_utils/utils.js
  62. 1 4
      packages/ckeditor5-image/tests/imageresize/imageresizeediting.js
  63. 78 17
      packages/ckeditor5-image/tests/imageresize/imageresizehandles.js
  64. 3 2
      packages/ckeditor5-image/tests/manual/imageinsertviaurl.js
  65. 5 0
      packages/ckeditor5-image/tests/manual/imageinsertviaurl.md
  66. 1 1
      packages/ckeditor5-indent/src/indentui.js
  67. 2 2
      packages/ckeditor5-link/src/linkui.js
  68. 81 24
      packages/ckeditor5-list/src/liststyleediting.js
  69. 2 2
      packages/ckeditor5-list/src/liststyleui.js
  70. 1 1
      packages/ckeditor5-list/src/utils.js
  71. 2 2
      packages/ckeditor5-list/tests/liststylecommand.js
  72. 163 0
      packages/ckeditor5-list/tests/liststyleediting.js
  73. 35 1
      packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.html
  74. 20 2
      packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js
  75. 19 9
      packages/ckeditor5-markdown-gfm/docs/features/markdown.md
  76. 1 1
      packages/ckeditor5-media-embed/src/mediaembedui.js
  77. 9 11
      packages/ckeditor5-media-embed/tests/automediaembed.js
  78. 1 1
      packages/ckeditor5-mention/src/mentionui.js
  79. 1 1
      packages/ckeditor5-page-break/src/pagebreakui.js
  80. 96 7
      packages/ckeditor5-paste-from-office/src/filters/list.js
  81. 2 2
      packages/ckeditor5-paste-from-office/tests/_data/list/heading1/model.word2016.html
  82. 2 2
      packages/ckeditor5-paste-from-office/tests/_data/list/heading3-styled/model.word2016.html
  83. 1 1
      packages/ckeditor5-paste-from-office/tests/_data/list/heading3-styled/normalized.safari.word2016.html
  84. 1 1
      packages/ckeditor5-paste-from-office/tests/_data/list/heading3-styled/normalized.word2016.html
  85. 1 1
      packages/ckeditor5-paste-from-office/tests/_data/list/heading7/model.word2016.html
  86. 12 12
      packages/ckeditor5-paste-from-office/tests/_data/list/many-one-item/model.word2016.html
  87. 8 8
      packages/ckeditor5-paste-from-office/tests/_data/list/many-one-item/normalized.safari.word2016.html
  88. 8 8
      packages/ckeditor5-paste-from-office/tests/_data/list/many-one-item/normalized.word2016.html
  89. 4 4
      packages/ckeditor5-paste-from-office/tests/_data/list/multiple-combined/model.word2016.html
  90. 3 3
      packages/ckeditor5-paste-from-office/tests/_data/list/multiple/model.word2016.html
  91. 1 1
      packages/ckeditor5-paste-from-office/tests/_data/list/multiple/normalized.safari.word2016.html
  92. 1 1
      packages/ckeditor5-paste-from-office/tests/_data/list/multiple/normalized.word2016.html
  93. 6 6
      packages/ckeditor5-paste-from-office/tests/_data/list/nested-mixed/model.word2016.html
  94. 2 2
      packages/ckeditor5-paste-from-office/tests/_data/list/nested-mixed/normalized.word2016.html
  95. 9 9
      packages/ckeditor5-paste-from-office/tests/_data/list/nested-multiple/model.word2016.html
  96. 6 6
      packages/ckeditor5-paste-from-office/tests/_data/list/nested-multiple/normalized.safari.word2016.html
  97. 5 5
      packages/ckeditor5-paste-from-office/tests/_data/list/nested-multiple/normalized.word2016.html
  98. 7 7
      packages/ckeditor5-paste-from-office/tests/_data/list/nested/model.word2016.html
  99. 2 2
      packages/ckeditor5-paste-from-office/tests/_data/list/nested/normalized.word2016.html
  100. 12 12
      packages/ckeditor5-paste-from-office/tests/_data/list/resume-template/model.safari.word2016.html

+ 1 - 1
docs/_snippets/framework/tutorials/block-widget.js

@@ -54,7 +54,7 @@ class SimpleBoxUI extends Plugin {
 			// Execute the command when the button is clicked (executed).
 			this.listenTo( buttonView, 'execute', () => {
 				editor.execute( 'insertSimpleBox' );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return buttonView;

+ 1 - 1
docs/_snippets/framework/tutorials/inline-widget.js

@@ -82,7 +82,7 @@ class PlaceholderUI extends Plugin {
 			// Execute the command when the dropdown item is clicked (executed).
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( 'placeholder', { value: evt.source.commandParam } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;

+ 1 - 1
docs/_snippets/framework/tutorials/using-react-in-widget.html

@@ -311,7 +311,7 @@ class App extends React.Component {
 				products={this.props.products}
 				onClick={( id ) => {
 					this.editor.execute( 'insertProduct', id );
-					this.editor.editing.view.focus();
+					this.editor.focus();
 				}}
 			/>
 		];

+ 2 - 2
docs/framework/guides/architecture/ui-library.md

@@ -443,13 +443,13 @@ dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled',
 
 ### Best practices
 
-It is advised that for the best user experience the editing view gets {@link module:engine/view/view~View#focus focused} upon any user action (e.g. executing a command) to make sure the editor retains focus:
+It is advised that for the best user experience the editor gets {@link module:core/editor/editor~Editor#focus focused} upon any user action (e.g. executing a command):
 
 ```js
 // Execute some action on dropdown#execute event.
 dropdownView.buttonView.on( 'execute', () => {
 	editor.execute( 'command', { value: ... } );
-	editor.editing.view.focus();
+	editor.focus();
 } );
 ```
 

+ 1 - 1
docs/framework/guides/deep-dive/localization.md

@@ -103,7 +103,7 @@ editor.ui.componentFactory.add( 'smilingFaceEmoji', locale => {
 
 	buttonView.on( 'execute', () => {
 		editor.execute( 'insertSmilingFaceEmoji' );
-		editor.editing.view.focus();
+		editor.focus();
 	} );
 } );
 // ...

+ 2 - 2
docs/framework/guides/tutorials/implementing-an-inline-widget.md

@@ -591,7 +591,7 @@ export default class PlaceholderUI extends Plugin {
 			// Execute the command when the dropdown item is clicked (executed).
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( 'placeholder', { value: evt.source.commandParam } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;
@@ -819,7 +819,7 @@ class PlaceholderUI extends Plugin {
 			// Execute the command when the dropdown item is clicked (executed).
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( 'placeholder', { value: evt.source.commandParam } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;

+ 2 - 2
docs/framework/guides/tutorials/using-react-in-a-widget.md

@@ -636,7 +636,7 @@ class App extends React.Component {
 				products={this.props.products}
 				onClick={( id ) => {
 					this.editor.execute( 'insertProduct', id );
-					this.editor.editing.view.focus();
+					this.editor.focus();
 				}}
 			/>
 		];
@@ -1047,7 +1047,7 @@ class App extends React.Component {
 				products={this.props.products}
 				onClick={( id ) => {
 					this.editor.execute( 'insertProduct', id );
-					this.editor.editing.view.focus();
+					this.editor.focus();
 				}}
 			/>
 		];

+ 1 - 1
packages/ckeditor5-alignment/src/alignmentui.js

@@ -152,7 +152,7 @@ export default class AlignmentUI extends Plugin {
 			// Execute command.
 			this.listenTo( buttonView, 'execute', () => {
 				editor.execute( 'alignment', { value: option } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return buttonView;

+ 1 - 1
packages/ckeditor5-basic-styles/src/bold/boldui.js

@@ -45,7 +45,7 @@ export default class BoldUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( BOLD );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/src/code/codeediting.js

@@ -48,7 +48,7 @@ export default class CodeEditing extends Plugin {
 		editor.model.schema.extend( '$text', { allowAttributes: CODE } );
 		editor.model.schema.setAttributeProperties( CODE, {
 			isFormatting: true,
-			copyOnEnter: true
+			copyOnEnter: false
 		} );
 
 		editor.conversion.attributeToElement( {

+ 1 - 1
packages/ckeditor5-basic-styles/src/code/codeui.js

@@ -46,7 +46,7 @@ export default class CodeUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( CODE );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/src/italic/italicui.js

@@ -45,7 +45,7 @@ export default class ItalicUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( ITALIC );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/src/strikethrough/strikethroughui.js

@@ -45,7 +45,7 @@ export default class StrikethroughUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( STRIKETHROUGH );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/src/subscript/subscriptui.js

@@ -44,7 +44,7 @@ export default class SubscriptUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( SUBSCRIPT );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/src/superscript/superscriptui.js

@@ -44,7 +44,7 @@ export default class SuperscriptUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( SUPERSCRIPT );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/src/underline/underlineui.js

@@ -45,7 +45,7 @@ export default class UnderlineUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( UNDERLINE );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-basic-styles/tests/code/codeediting.js

@@ -54,7 +54,7 @@ describe( 'CodeEditing', () => {
 
 	it( 'its attribute is marked with a copOnEnter property', () => {
 		expect( model.schema.getAttributeProperties( 'code' ) ).to.include( {
-			copyOnEnter: true
+			copyOnEnter: false
 		} );
 	} );
 

+ 1 - 1
packages/ckeditor5-block-quote/src/blockquoteui.js

@@ -45,7 +45,7 @@ export default class BlockQuoteUI extends Plugin {
 			// Execute command.
 			this.listenTo( buttonView, 'execute', () => {
 				editor.execute( 'blockQuote' );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return buttonView;

+ 1 - 1
packages/ckeditor5-ckfinder/src/ckfinderui.js

@@ -48,7 +48,7 @@ export default class CKFinderUI extends Plugin {
 
 			button.on( 'execute', () => {
 				editor.execute( 'ckfinder' );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return button;

+ 2 - 2
packages/ckeditor5-code-block/src/codeblockui.js

@@ -54,7 +54,7 @@ export default class CodeBlockUI extends Plugin {
 					language: defaultLanguageDefinition.language
 				} );
 
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			dropdownView.on( 'execute', evt => {
@@ -63,7 +63,7 @@ export default class CodeBlockUI extends Plugin {
 					forceValue: true
 				} );
 
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			dropdownView.class = 'ck-code-block-dropdown';

+ 7 - 0
packages/ckeditor5-core/src/editor/editor.js

@@ -296,6 +296,13 @@ export default class Editor {
 		}
 	}
 
+	/**
+	 * Puts the focus in the editor's editing view.
+	 */
+	focus() {
+		this.editing.view.focus();
+	}
+
 	/**
 	 * Creates and initializes a new editor instance.
 	 *

+ 12 - 0
packages/ckeditor5-core/tests/editor/editor.js

@@ -556,6 +556,18 @@ describe( 'Editor', () => {
 		} );
 	} );
 
+	describe( 'focus()', () => {
+		it( 'should call view\'s focus() method', () => {
+			const editor = new TestEditor();
+			const focusSpy = sinon.spy( editor.editing.view, 'focus' );
+
+			editor.editing.view.document.isFocused = true;
+			editor.focus();
+
+			expect( focusSpy.calledOnce ).to.be.true;
+		} );
+	} );
+
 	describe( 'create()', () => {
 		it( 'should return a promise that resolves properly', () => {
 			const promise = TestEditor.create();

+ 1 - 1
packages/ckeditor5-core/tests/manual/readonly.js

@@ -28,7 +28,7 @@ ClassicEditor
 			editor.isReadOnly = !editor.isReadOnly;
 			button.textContent = editor.isReadOnly ? 'Turn off read-only mode' : 'Turn on read-only mode';
 
-			editor.editing.view.focus();
+			editor.focus();
 		} );
 	} )
 	.catch( err => {

+ 22 - 0
packages/ckeditor5-engine/tests/common.js

@@ -0,0 +1,22 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global console */
+
+// eslint-disable-next-line mocha/no-top-level-hooks
+before( () => {
+	// This is a temporary special handling for https://github.com/ckeditor/ckeditor5/issues/8263
+	// The goal is to show which test case(s) exactly causes the "Selection change observer detected an infinite rendering loop." warn
+	// and reduced engine code coverage.
+	const originalWarn = console.warn;
+
+	console.warn = function( ...args ) {
+		if ( args[ 0 ] == 'Selection change observer detected an infinite rendering loop.' ) {
+			throw new Error( 'Detected unwelcome "Selection change observer detected an infinite rendering loop." warning.' );
+		}
+
+		return originalWarn.apply( args );
+	};
+} );

+ 1 - 1
packages/ckeditor5-font/src/fontfamily/fontfamilyui.js

@@ -55,7 +55,7 @@ export default class FontFamilyUI extends Plugin {
 			// Execute command when an item from the dropdown is selected.
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( evt.source.commandName, { value: evt.source.commandParam } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;

+ 1 - 1
packages/ckeditor5-font/src/fontsize/fontsizeui.js

@@ -60,7 +60,7 @@ export default class FontSizeUI extends Plugin {
 			// Execute command when an item from the dropdown is selected.
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( evt.source.commandName, { value: evt.source.commandParam } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;

+ 1 - 1
packages/ckeditor5-font/src/ui/colorui.js

@@ -126,7 +126,7 @@ export default class ColorUI extends Plugin {
 
 			dropdownView.on( 'execute', ( evt, data ) => {
 				editor.execute( this.commandName, data );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			dropdownView.on( 'change:isOpen', ( evt, name, isVisible ) => {

+ 1 - 1
packages/ckeditor5-heading/src/headingbuttonsui.js

@@ -94,7 +94,7 @@ export default class HeadingButtonsUI extends Plugin {
 
 			view.on( 'execute', () => {
 				editor.execute( 'heading', { value: option.model } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-heading/src/headingui.js

@@ -101,7 +101,7 @@ export default class HeadingUI extends Plugin {
 			// Execute command when an item from the dropdown is selected.
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( evt.source.commandName, evt.source.commandValue ? { value: evt.source.commandValue } : undefined );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;

+ 2 - 2
packages/ckeditor5-highlight/src/highlightui.js

@@ -149,7 +149,7 @@ export default class HighlightUI extends Plugin {
 
 			buttonView.on( 'execute', () => {
 				editor.execute( 'highlight', { value } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			// Add additional behavior for buttonView.
@@ -228,7 +228,7 @@ export default class HighlightUI extends Plugin {
 			// Execute current action from dropdown's split button action button.
 			splitButtonView.on( 'execute', () => {
 				editor.execute( 'highlight', { value: splitButtonView.commandValue } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			// Returns active highlighter option depending on current command value.

+ 1 - 1
packages/ckeditor5-horizontal-line/src/horizontallineui.js

@@ -37,7 +37,7 @@ export default class HorizontalLineUI extends Plugin {
 			// Execute the command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( 'horizontalLine' );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-html-embed/src/htmlembedui.js

@@ -38,7 +38,7 @@ export default class HtmlEmbedUI extends Plugin {
 			// Execute the command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( 'insertHtmlEmbed' );
-				editor.editing.view.focus();
+				editor.focus();
 
 				const widgetWrapper = editor.editing.view.document.selection.getSelectedElement();
 

+ 1 - 1
packages/ckeditor5-html-embed/theme/htmlembed.css

@@ -8,7 +8,7 @@
 	/* Give the embed some air. */
 	margin: 1em auto;
 	position: relative;
-	clear: both;
+	display: flow-root;
 
 	/* ----- Emebed label in the upper left corner ----------------------------------------------- */
 

+ 2 - 0
packages/ckeditor5-image/docs/_snippets/features/build-image-source.js

@@ -9,10 +9,12 @@ import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
 
 import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize';
 import ImageInsert from '@ckeditor/ckeditor5-image/src/imageinsert';
+import AutoImage from '@ckeditor/ckeditor5-image/src/autoimage';
 import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage';
 
 ClassicEditor.builtinPlugins.push( ImageResize );
 ClassicEditor.builtinPlugins.push( ImageInsert );
 ClassicEditor.builtinPlugins.push( LinkImage );
+ClassicEditor.builtinPlugins.push( AutoImage );
 
 window.ClassicEditor = ClassicEditor;

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-caption.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-caption' ), {
-		removePlugins: [ 'ImageStyle', 'ImageResize', 'LinkImage' ],
+		removePlugins: [ 'ImageStyle', 'ImageResize', 'LinkImage', 'AutoImage' ],
 		image: {
 			toolbar: [ 'imageTextAlternative' ]
 		},

+ 3 - 0
packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html

@@ -0,0 +1,3 @@
+<div id="snippet-image-insert-via-pasting-url-into-editor">
+	<p>Insert an URL of an image: </p>
+</div>

+ 20 - 0
packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.js

@@ -0,0 +1,20 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals ClassicEditor, console, window, document */
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config.js';
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-image-insert-via-pasting-url-into-editor' ), {
+		removePlugins: [ 'ImageToolbar', 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage' ],
+		cloudServices: CS_CONFIG
+	} )
+	.then( editor => {
+		window.editorInsertImageViaPastingUrlIntoEditor = editor;
+	} )
+	.catch( err => {
+		console.error( err );
+	} );

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-insert-via-url.js

@@ -13,7 +13,7 @@ toolbarItems.splice( toolbarItems.indexOf( 'imageUpload' ), 1, 'imageInsert' );
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-insert-via-url' ), {
-		removePlugins: [ 'ImageToolbar', 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage' ],
+		removePlugins: [ 'ImageToolbar', 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage', 'AutoImage' ],
 		toolbar: {
 			items: toolbarItems,
 			viewportTopOffset: window.getViewportTopOffsetConfig()

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-link.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-link' ), {
-		removePlugins: [ 'ImageResize' ],
+		removePlugins: [ 'ImageResize', 'AutoImage' ],
 		toolbar: {
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		},

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons-dropdown.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-resize-buttons-dropdown' ), {
-		removePlugins: [ 'LinkImage' ],
+		removePlugins: [ 'LinkImage', 'AutoImage' ],
 		toolbar: {
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		},

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-resize-buttons' ), {
-		removePlugins: [ 'LinkImage' ],
+		removePlugins: [ 'LinkImage', 'AutoImage' ],
 		toolbar: {
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		},

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-resize-px.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-resize-px' ), {
-		removePlugins: [ 'LinkImage' ],
+		removePlugins: [ 'LinkImage', 'AutoImage' ],
 		image: {
 			resizeUnit: 'px',
 			resizeOptions: [

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-resize.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-resize' ), {
-		removePlugins: [ 'LinkImage' ],
+		removePlugins: [ 'LinkImage', 'AutoImage' ],
 		toolbar: {
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		},

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-style-presentational.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-style-custom' ), {
-		removePlugins: [ 'LinkImage' ],
+		removePlugins: [ 'LinkImage', 'AutoImage' ],
 		image: {
 			styles: [
 				'alignLeft',

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-style.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-style' ), {
-		removePlugins: [ 'ImageResize', 'LinkImage' ],
+		removePlugins: [ 'ImageResize', 'LinkImage', 'AutoImage' ],
 		toolbar: {
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		},

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-toolbar.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image-toolbar' ), {
-		removePlugins: [ 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage' ],
+		removePlugins: [ 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage', 'AutoImage' ],
 		image: {
 			toolbar: [ 'imageTextAlternative' ]
 		},

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image.js

@@ -9,7 +9,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-image' ), {
-		removePlugins: [ 'ImageToolbar', 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage' ],
+		removePlugins: [ 'ImageToolbar', 'ImageCaption', 'ImageStyle', 'ImageResize', 'LinkImage', 'AutoImage' ],
 		toolbar: {
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		},

+ 1 - 0
packages/ckeditor5-image/docs/api/image.md

@@ -22,6 +22,7 @@ See the {@link features/image Image feature} guide and the documentation of the
 * {@link module:image/imagetextalternative~ImageTextAlternative}
 * {@link module:image/imagetoolbar~ImageToolbar}
 * {@link module:image/imageupload~ImageUpload}
+* {@link module:image/autoimage~AutoImage}
 
 ## Installation
 

+ 17 - 0
packages/ckeditor5-image/docs/features/image.md

@@ -14,6 +14,7 @@ The [`@ckeditor/ckeditor5-image`](https://www.npmjs.com/package/@ckeditor/ckedit
 * {@link module:image/imagetextalternative~ImageTextAlternative} adds support for adding text alternative.
 * {@link module:image/imageupload~ImageUpload} adds support for {@link features/image-upload uploading dropped or pasted images}.
 * {@link module:image/imageinsert~ImageInsert} adds support for [inserting images via URL](#inserting-images-via-source-url) and other custom integrations.
+* {@link module:image/autoimage~AutoImage} adds support for [inserting images via pasting URL into editor](#inserting-images-via-pasting-url-into-editor).
 * {@link module:image/imageresize~ImageResize} adds support for [resizing images](#resizing-images).
 * {@link module:link/linkimage~LinkImage} adds support for [linking images](#linking-images).
 
@@ -105,6 +106,22 @@ This will add a new **Insert image** dropdown in the toolbar. To open the panel
 
 {@snippet features/image-insert-via-url}
 
+## Inserting images via pasting URL into editor
+
+The {@link module:image/autoimage~AutoImage} plugin recognizes image links in the pasted content and embeds them shortly after they are injected into the document to speed up the editing. Accepted image extensions: jpg, jpeg, png, gif, ico.
+
+<info-box>
+	The image URL must be the only content pasted to be properly embedded. Multiple links (`"http://image.url http://another.image.url"`) as well as bigger chunks of content (`"This link http://image.url will not be auto–embedded when pasted."`) are ignored.
+</info-box>
+
+If the automatic embedding was unexpected, for instance when the link was meant to remain in the content as text, simply undo the action (by clicking the "Undo" button in the toolbar or using the <kbd>Ctrl/⌘</kbd>+<kbd>Z</kbd> keystrokes).
+
+You can paste the image URL directly into the editor content, and it will be automatically embedded.
+
+<input class="example-input" type="text" value="https://ckeditor.com/docs/ckeditor5/latest/assets/img/malta.jpg">
+
+{@snippet features/image-insert-via-pasting-url-into-editor}
+
 ## Responsive images
 
 Support for responsive images in CKEditor 5 is brought by the {@link features/easy-image Easy Image} feature without any additional configuration. Refer to the {@link features/easy-image#responsive-images Easy Image integration} guide to learn how to use the feature in your project.

+ 174 - 0
packages/ckeditor5-image/src/autoimage.js

@@ -0,0 +1,174 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module image/autoimage
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
+import LiveRange from '@ckeditor/ckeditor5-engine/src/model/liverange';
+import LivePosition from '@ckeditor/ckeditor5-engine/src/model/liveposition';
+import Undo from '@ckeditor/ckeditor5-undo/src/undo';
+import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+import { insertImage } from './image/utils';
+
+// Implements the pattern: http(s)://(www.)example.com/path/to/resource.ext?query=params&maybe=too.
+const IMAGE_URL_REGEXP = new RegExp( String( /^(http(s)?:\/\/)?[\w-]+(\.[\w-]+)+[\w._~:/?#[\]@!$&'()*+,;=%-]+/.source +
+	/\.(jpg|jpeg|png|gif|ico|JPG|JPEG|PNG|GIF|ICO)\??[\w._~:/#[\]@!$&'()*+,;=%-]*$/.source ) );
+
+/**
+ * The auto-image plugin. It recognizes image links in the pasted content and embeds
+ * them shortly after they are injected into the document.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class AutoImage extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ Clipboard, Undo ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'AutoImage';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * The paste–to–embed `setTimeout` ID. Stored as a property to allow
+		 * cleaning of the timeout.
+		 *
+		 * @private
+		 * @member {Number} #_timeoutId
+		 */
+		this._timeoutId = null;
+
+		/**
+		 * The position where the `<image>` element will be inserted after the timeout,
+		 * determined each time the new content is pasted into the document.
+		 *
+		 * @private
+		 * @member {module:engine/model/liveposition~LivePosition} #_positionToInsert
+		 */
+		this._positionToInsert = null;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const modelDocument = editor.model.document;
+
+		// We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
+		// After pasting, the content between those positions will be checked for a URL that could be transformed
+		// into image.
+		this.listenTo( editor.plugins.get( Clipboard ), 'inputTransformation', () => {
+			const firstRange = modelDocument.selection.getFirstRange();
+
+			const leftLivePosition = LivePosition.fromPosition( firstRange.start );
+			leftLivePosition.stickiness = 'toPrevious';
+
+			const rightLivePosition = LivePosition.fromPosition( firstRange.end );
+			rightLivePosition.stickiness = 'toNext';
+
+			modelDocument.once( 'change:data', () => {
+				this._embedImageBetweenPositions( leftLivePosition, rightLivePosition );
+
+				leftLivePosition.detach();
+				rightLivePosition.detach();
+			}, { priority: 'high' } );
+		} );
+
+		editor.commands.get( 'undo' ).on( 'execute', () => {
+			if ( this._timeoutId ) {
+				global.window.clearTimeout( this._timeoutId );
+				this._positionToInsert.detach();
+
+				this._timeoutId = null;
+				this._positionToInsert = null;
+			}
+		}, { priority: 'high' } );
+	}
+
+	/**
+	 * Analyzes the part of the document between provided positions in search for an URL representing an image.
+	 * When the URL is found, it is automatically converted into an image.
+	 *
+	 * @protected
+	 * @param {module:engine/model/liveposition~LivePosition} leftPosition Left position of the selection.
+	 * @param {module:engine/model/liveposition~LivePosition} rightPosition Right position of the selection.
+	 */
+	_embedImageBetweenPositions( leftPosition, rightPosition ) {
+		const editor = this.editor;
+		// TODO: Use marker instead of LiveRange & LivePositions.
+		const urlRange = new LiveRange( leftPosition, rightPosition );
+		const walker = urlRange.getWalker( { ignoreElementEnd: true } );
+
+		let src = '';
+
+		for ( const node of walker ) {
+			if ( node.item.is( '$textProxy' ) ) {
+				src += node.item.data;
+			}
+		}
+
+		src = src.trim();
+
+		// If the URL does not match to image URL regexp, let's skip that.
+		if ( !src.match( IMAGE_URL_REGEXP ) ) {
+			urlRange.detach();
+
+			return;
+		}
+
+		// Position won't be available in the `setTimeout` function so let's clone it.
+		this._positionToInsert = LivePosition.fromPosition( leftPosition );
+
+		// This action mustn't be executed if undo was called between pasting and auto-embedding.
+		this._timeoutId = global.window.setTimeout( () => {
+			// Don't do anything if image element cannot be inserted at the current position.
+			// See https://github.com/ckeditor/ckeditor5/issues/2763.
+			// Condition must be checked after timeout - pasting may take place on an element, replacing it. The final position matters.
+			const imageCommand = editor.commands.get( 'imageInsert' );
+
+			if ( !imageCommand.isEnabled ) {
+				urlRange.detach();
+
+				return;
+			}
+
+			editor.model.change( writer => {
+				this._timeoutId = null;
+
+				writer.remove( urlRange );
+				urlRange.detach();
+
+				let insertionPosition;
+
+				// Check if position where the element should be inserted is still valid.
+				// Otherwise leave it as undefined to use the logic of insertImage().
+				if ( this._positionToInsert.root.rootName !== '$graveyard' ) {
+					insertionPosition = this._positionToInsert.toPosition();
+				}
+
+				insertImage( editor.model, { src }, insertionPosition );
+
+				this._positionToInsert.detach();
+				this._positionToInsert = null;
+			} );
+		}, 100 );
+	}
+}

+ 4 - 6
packages/ckeditor5-image/src/image/imageinsertcommand.js

@@ -50,12 +50,10 @@ export default class ImageInsertCommand extends Command {
 	execute( options ) {
 		const model = this.editor.model;
 
-		model.change( writer => {
-			const sources = Array.isArray( options.source ) ? options.source : [ options.source ];
+		const sources = Array.isArray( options.source ) ? options.source : [ options.source ];
 
-			for ( const src of sources ) {
-				insertImage( writer, model, { src } );
-			}
-		} );
+		for ( const src of sources ) {
+			insertImage( model, { src } );
+		}
 	}
 }

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

@@ -71,25 +71,26 @@ export function isImage( modelElement ) {
 /**
  * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionPosition} method.
  *
- *		model.change( writer => {
- *			insertImage( writer, model, { src: 'path/to/image.jpg' } );
- *		} );
+ *		insertImage( model, { src: 'path/to/image.jpg' } );
  *
- * @param {module:engine/model/writer~Writer} writer
  * @param {module:engine/model/model~Model} model
  * @param {Object} [attributes={}] Attributes of inserted image
+ * @param {module:engine/model/position~Position} [insertPosition] Position to insert the image. If not specified,
+ * the {@link module:widget/utils~findOptimalInsertionPosition} logic will be applied.
  */
-export function insertImage( writer, model, attributes = {} ) {
-	const imageElement = writer.createElement( 'image', attributes );
+export function insertImage( model, attributes = {}, insertPosition = null ) {
+	model.change( writer => {
+		const imageElement = writer.createElement( 'image', attributes );
 
-	const insertAtSelection = findOptimalInsertionPosition( model.document.selection, model );
+		const insertAtSelection = insertPosition || findOptimalInsertionPosition( model.document.selection, model );
 
-	model.insertContent( imageElement, insertAtSelection );
+		model.insertContent( imageElement, insertAtSelection );
 
-	// Inserting an image might've failed due to schema regulations.
-	if ( imageElement.parent ) {
-		writer.setSelection( imageElement, 'on' );
-	}
+		// Inserting an image might've failed due to schema regulations.
+		if ( imageElement.parent ) {
+			writer.setSelection( imageElement, 'on' );
+		}
+	} );
 }
 
 /**

+ 1 - 1
packages/ckeditor5-image/src/imageinsert/imageinsertui.js

@@ -145,7 +145,7 @@ export default class ImageInsertUI extends Plugin {
 		}
 
 		function closePanel() {
-			editor.editing.view.focus();
+			editor.focus();
 			dropdownView.isOpen = false;
 		}
 

+ 1 - 1
packages/ckeditor5-image/src/imageresize/imageresizebuttons.js

@@ -185,7 +185,7 @@ export default class ImageResizeButtons extends Plugin {
 			// Execute command when an item from the dropdown is selected.
 			this.listenTo( dropdownView, 'execute', evt => {
 				editor.execute( evt.source.commandName, { width: evt.source.commandValue } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return dropdownView;

+ 38 - 12
packages/ckeditor5-image/src/imageresize/imageresizehandles.js

@@ -9,6 +9,7 @@
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import WidgetResize from '@ckeditor/ckeditor5-widget/src/widgetresize';
+import ImageLoadObserver from '../image/imageloadobserver';
 
 /**
  * The image resize by handles feature.
@@ -37,21 +38,46 @@ export default class ImageResizeHandles extends Plugin {
 	 * @inheritDoc
 	 */
 	init() {
+		const command = this.editor.commands.get( 'imageResize' );
+		this.bind( 'isEnabled' ).to( command );
+
+		this._setupResizerCreator();
+	}
+
+	/**
+	 * Attaches the listeners responsible for creating a resizer for each image.
+	 *
+	 * @private
+	 */
+	_setupResizerCreator() {
 		const editor = this.editor;
-		const command = editor.commands.get( 'imageResize' );
+		const editingView = editor.editing.view;
 
-		this.bind( 'isEnabled' ).to( command );
+		editingView.addObserver( ImageLoadObserver );
+
+		this.listenTo( editingView.document, 'imageLoaded', ( evt, domEvent ) => {
+			const imageView = editor.editing.view.domConverter.domToView( domEvent.target );
+			const widgetView = imageView.findAncestor( 'figure' );
+			let resizer = this.editor.plugins.get( WidgetResize ).getResizerByViewElement( widgetView );
+
+			if ( resizer ) {
+				// There are rare cases when image will be triggered multiple times for the same widget, e.g. when
+				// image's src was changed after upload (https://github.com/ckeditor/ckeditor5/pull/8108#issuecomment-708302992).
+				resizer.redraw();
+
+				return;
+			}
 
-		editor.editing.downcastDispatcher.on( 'insert:image', ( evt, data, conversionApi ) => {
-			const widget = conversionApi.mapper.toViewElement( data.item );
+			const mapper = editor.editing.mapper;
+			const imageModel = mapper.toModelElement( widgetView );
 
-			const resizer = editor.plugins
+			resizer = editor.plugins
 				.get( WidgetResize )
 				.attachTo( {
 					unit: editor.config.get( 'image.resizeUnit' ),
 
-					modelElement: data.item,
-					viewElement: widget,
+					modelElement: imageModel,
+					viewElement: widgetView,
 					editor,
 
 					getHandleHost( domWidgetElement ) {
@@ -62,7 +88,7 @@ export default class ImageResizeHandles extends Plugin {
 					},
 					// TODO consider other positions.
 					isCentered() {
-						const imageStyle = data.item.getAttribute( 'imageStyle' );
+						const imageStyle = imageModel.getAttribute( 'imageStyle' );
 
 						return !imageStyle || imageStyle == 'full' || imageStyle == 'alignCenter';
 					},
@@ -73,14 +99,14 @@ export default class ImageResizeHandles extends Plugin {
 				} );
 
 			resizer.on( 'updateSize', () => {
-				if ( !widget.hasClass( 'image_resized' ) ) {
-					editor.editing.view.change( writer => {
-						writer.addClass( 'image_resized', widget );
+				if ( !widgetView.hasClass( 'image_resized' ) ) {
+					editingView.change( writer => {
+						writer.addClass( 'image_resized', widgetView );
 					} );
 				}
 			} );
 
 			resizer.bind( 'isEnabled' ).to( this );
-		}, { priority: 'low' } );
+		} );
 	}
 }

+ 1 - 1
packages/ckeditor5-image/src/imagestyle/imagestyleui.js

@@ -94,7 +94,7 @@ export default class ImageStyleUI extends Plugin {
 
 			this.listenTo( view, 'execute', () => {
 				editor.execute( 'imageStyle', { value: style.name } );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 1 - 1
packages/ckeditor5-image/src/imagetextalternative/imagetextalternativeui.js

@@ -202,7 +202,7 @@ export default class ImageTextAlternativeUI extends Plugin {
 		this._balloon.remove( this._form );
 
 		if ( focusEditable ) {
-			this.editor.editing.view.focus();
+			this.editor.focus();
 		}
 	}
 

+ 6 - 9
packages/ckeditor5-image/src/imageupload/imageuploadcommand.js

@@ -63,22 +63,19 @@ export default class ImageUploadCommand extends Command {
 
 		const fileRepository = editor.plugins.get( FileRepository );
 
-		model.change( writer => {
-			const filesToUpload = Array.isArray( options.file ) ? options.file : [ options.file ];
+		const filesToUpload = Array.isArray( options.file ) ? options.file : [ options.file ];
 
-			for ( const file of filesToUpload ) {
-				uploadImage( writer, model, fileRepository, file );
-			}
-		} );
+		for ( const file of filesToUpload ) {
+			uploadImage( model, fileRepository, file );
+		}
 	}
 }
 
 // Handles uploading single file.
 //
-// @param {module:engine/model/writer~writer} writer
 // @param {module:engine/model/model~Model} model
 // @param {File} file
-function uploadImage( writer, model, fileRepository, file ) {
+function uploadImage( model, fileRepository, file ) {
 	const loader = fileRepository.createLoader( file );
 
 	// Do not throw when upload adapter is not set. FileRepository will log an error anyway.
@@ -86,5 +83,5 @@ function uploadImage( writer, model, fileRepository, file ) {
 		return;
 	}
 
-	insertImage( writer, model, { uploadId: loader.id } );
+	insertImage( model, { uploadId: loader.id } );
 }

+ 555 - 0
packages/ckeditor5-image/tests/autoimage.js

@@ -0,0 +1,555 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global setTimeout */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
+import Link from '@ckeditor/ckeditor5-link/src/link';
+import Table from '@ckeditor/ckeditor5-table/src/table';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
+import Undo from '@ckeditor/ckeditor5-undo/src/undo';
+import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+import Image from '../src/image';
+import ImageCaption from '../src/imagecaption';
+import AutoImage from '../src/autoimage';
+
+describe( 'AutoImage - integration', () => {
+	let editorElement, editor;
+
+	beforeEach( () => {
+		editorElement = global.document.createElement( 'div' );
+		global.document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Typing, Paragraph, Link, Image, ImageCaption, AutoImage ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		editorElement.remove();
+
+		return editor.destroy();
+	} );
+
+	it( 'should load Clipboard plugin', () => {
+		expect( editor.plugins.get( Clipboard ) ).to.instanceOf( Clipboard );
+	} );
+
+	it( 'should load Undo plugin', () => {
+		expect( editor.plugins.get( Undo ) ).to.instanceOf( Undo );
+	} );
+
+	it( 'has proper name', () => {
+		expect( AutoImage.pluginName ).to.equal( 'AutoImage' );
+	} );
+
+	describe( 'use fake timers', () => {
+		let clock;
+
+		beforeEach( () => {
+			clock = sinon.useFakeTimers();
+		} );
+
+		afterEach( () => {
+			clock.restore();
+		} );
+
+		it( 'replaces pasted text with image element after 100ms', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>http://example.com/image.png[]</paragraph>'
+			);
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'[<image src="http://example.com/image.png"><caption></caption></image>]'
+			);
+		} );
+
+		it( 'can undo auto-embeding', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>http://example.com/image.png[]</paragraph>'
+			);
+
+			clock.tick( 100 );
+
+			editor.commands.execute( 'undo' );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>http://example.com/image.png[]</paragraph>'
+			);
+		} );
+
+		describe( 'supported URL', () => {
+			const supportedURLs = [
+				'example.com/image.png',
+				'www.example.com/image.png',
+				'https://www.example.com/image.png',
+				'https://example.com/image.png',
+				'http://www.example.com/image.png',
+				'http://example.com/image.png',
+				'http://example.com/image.jpg',
+				'http://example.com/image.jpeg',
+				'http://example.com/image.gif',
+				'http://example.com/image.ico',
+				'http://example.com/image.JPG',
+				'http://example.com%20Fimage.png',
+				'http://example.com/image.png?foo=bar',
+				'http://example.com/image.png#foo'
+			];
+
+			for ( const supportedURL of supportedURLs ) {
+				it( `should detect "${ supportedURL }" as a valid URL`, () => {
+					setData( editor.model, '<paragraph>[]</paragraph>' );
+					pasteHtml( editor, supportedURL );
+
+					clock.tick( 100 );
+
+					expect( getData( editor.model ) ).to.equal(
+						`[<image src="${ supportedURL }"><caption></caption></image>]`
+					);
+				} );
+			}
+		} );
+
+		describe( 'unsupported URL', () => {
+			const unsupportedOrInvalid = [
+				'http://www.example.com',
+				'https://example.com',
+				'http://www.example.com/image.svg',
+				'http://www.example.com/image.webp',
+				'http://www.example.com/image.mp3',
+				'http://www.example.com/image.exe',
+				'http://www.example.com/image.txt',
+				'http://example.com/image.png http://example.com/image.png',
+				'Foo bar http://example.com/image.png bar foo.',
+				'https://example.com/im age.png'
+			];
+
+			for ( const unsupportedURL of unsupportedOrInvalid ) {
+				it( `should not detect "${ unsupportedURL }" as a valid URL`, () => {
+					setData( editor.model, '<paragraph>[]</paragraph>' );
+					pasteHtml( editor, unsupportedURL );
+
+					clock.tick( 100 );
+
+					expect( getData( editor.model ) ).to.equal(
+						`<paragraph>${ unsupportedURL }[]</paragraph>`
+					);
+				} );
+			}
+		} );
+
+		it( 'works for URL that was pasted as a link', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, '<a href="http://example.com/image.png">' +
+				'http://example.com/image.png</a>' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'[<image src="http://example.com/image.png"><caption></caption></image>]'
+			);
+		} );
+
+		it( 'works for URL that contains some inline styles', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, '<b>http://example.com/image.png</b>' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'[<image src="http://example.com/image.png"><caption></caption></image>]'
+			);
+		} );
+
+		it( 'works for not collapsed selection inside single element', () => {
+			setData( editor.model, '<paragraph>[Foo]</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'[<image src="http://example.com/image.png"><caption></caption></image>]'
+			);
+		} );
+
+		it( 'works for not collapsed selection over a few elements', () => {
+			setData( editor.model, '<paragraph>Fo[o</paragraph><paragraph>Ba]r</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>Fo</paragraph>' +
+				'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+				'<paragraph>r</paragraph>'
+			);
+		} );
+
+		it( 'inserts image in-place (collapsed selection)', () => {
+			setData( editor.model, '<paragraph>Foo []Bar</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>Foo </paragraph>' +
+				'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+				'<paragraph>Bar</paragraph>'
+			);
+		} );
+
+		it( 'inserts image in-place (non-collapsed selection)', () => {
+			setData( editor.model, '<paragraph>Foo [Bar] Baz</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>Foo </paragraph>' +
+				'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+				'<paragraph> Baz</paragraph>'
+			);
+		} );
+
+		it( 'does nothing if pasted more than single node', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor,
+				'http://example.com/image.png ' +
+				'<a href="http://example.com/image.png">' +
+				'http://example.com/image.png</a>'
+			);
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model, { withoutSelection: true } ) ).to.equal(
+				'<paragraph>http://example.com/image.png ' +
+				'<$text linkHref="http://example.com/image.png">' +
+				'http://example.com/image.png</$text>' +
+				'</paragraph>'
+			);
+		} );
+
+		it( 'does nothing if pasted a paragraph with the url', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, '<p>http://example.com/image.png</p>' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>http://example.com/image.png[]</paragraph>'
+			);
+		} );
+
+		it( 'does nothing if pasted a block of content that looks like a URL', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, '<p>https://</p><p>example.com/image</p><p>.png</p>' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>https://</paragraph><paragraph>example.com/image</paragraph><paragraph>.png[]</paragraph>'
+			);
+		} );
+
+		// #47
+		it( 'does not transform a valid URL into a image if the element cannot be placed in the current position', () => {
+			setData( editor.model, '<image src="/assets/sample.png"><caption>Foo.[]</caption></image>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<image src="/assets/sample.png"><caption>' +
+				'Foo.http://example.com/image.png[]' +
+				'</caption></image>'
+			);
+		} );
+
+		it( 'replaces a URL in image if pasted a link when other image element was selected', () => {
+			setData(
+				editor.model,
+				'[<image src="http://example.com/image.png"><caption></caption></image>]'
+			);
+
+			pasteHtml( editor, 'http://example.com/image2.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'[<image src="http://example.com/image2.png"><caption></caption></image>]'
+			);
+		} );
+
+		it( 'inserts a new image element if pasted a link when other image element was selected in correct place', () => {
+			setData(
+				editor.model,
+				'<paragraph>Foo. <$text linkHref="https://cksource.com">Bar</$text></paragraph>' +
+				'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+				'<paragraph><$text>Bar</$text>.</paragraph>'
+			);
+
+			pasteHtml( editor, 'http://example.com/image2.png' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>Foo. <$text linkHref="https://cksource.com">Bar</$text></paragraph>' +
+				'[<image src="http://example.com/image2.png"><caption></caption></image>]' +
+				'<paragraph>Bar.</paragraph>'
+			);
+		} );
+	} );
+
+	describe( 'use real timers', () => {
+		const characters = Array( 10 ).fill( 1 ).map( ( x, i ) => String.fromCharCode( 65 + i ) );
+
+		it( 'undo breaks the auto-image feature (undo was done before auto-image)', done => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>http://example.com/image.png[]</paragraph>'
+			);
+
+			setTimeout( () => {
+				editor.commands.execute( 'undo' );
+
+				expect( getData( editor.model ) ).to.equal(
+					'<paragraph>[]</paragraph>'
+				);
+
+				done();
+			} );
+		} );
+
+		// Checking whether paste+typing calls the auto-image handler once.
+		it( 'pasting handler should be executed once', done => {
+			const AutoImagePlugin = editor.plugins.get( AutoImage );
+			const autoImageHandler = AutoImagePlugin._embedImageBetweenPositions;
+			let counter = 0;
+
+			AutoImagePlugin._embedImageBetweenPositions = function( ...args ) {
+				counter += 1;
+
+				return autoImageHandler.apply( this, args );
+			};
+
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, 'http://example.com/image.png' );
+			simulateTyping( 'Foo. Bar.' );
+
+			setTimeout( () => {
+				AutoImagePlugin._embedImageBetweenPositions = autoImageHandler;
+
+				expect( counter ).to.equal( 1 );
+
+				done();
+			}, 100 );
+		} );
+
+		it( 'typing before pasted link during collaboration should not blow up', done => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			for ( let i = 0; i < 10; ++i ) {
+				const rootEl = editor.model.document.getRoot();
+
+				setTimeout( () => {
+					editor.model.enqueueChange( 'transparent', writer => {
+						writer.insertText( characters[ i ], writer.createPositionFromPath( rootEl, [ 0, i ] ) );
+					} );
+				}, i * 5 );
+			}
+
+			setTimeout( () => {
+				expect( getData( editor.model ) ).to.equal(
+					'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+					'<paragraph>ABCDEFGHIJ</paragraph>'
+				);
+
+				done();
+			}, 100 );
+		} );
+
+		it( 'typing after pasted link during collaboration should not blow up', done => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			for ( let i = 0; i < 10; ++i ) {
+				setTimeout( () => {
+					editor.model.enqueueChange( 'transparent', writer => {
+						writer.insertText( characters[ i ], editor.model.document.selection.getFirstPosition() );
+					} );
+				}, i * 5 );
+			}
+
+			setTimeout( () => {
+				expect( getData( editor.model ) ).to.equal(
+					'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+					'<paragraph>ABCDEFGHIJ</paragraph>'
+				);
+
+				done();
+			}, 100 );
+		} );
+
+		it( 'should insert the image element even if parent element where the URL was pasted has been deleted', done => {
+			setData( editor.model, '<paragraph>Foo.</paragraph><paragraph>Bar.[]</paragraph>' );
+
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			editor.model.enqueueChange( 'transparent', writer => {
+				writer.remove( writer.createRangeOn( editor.model.document.getRoot().getChild( 1 ) ) );
+			} );
+
+			setTimeout( () => {
+				expect( getData( editor.model ) ).to.equal(
+					'<paragraph>Foo.</paragraph>' +
+					'[<image src="http://example.com/image.png"><caption></caption></image>]'
+				);
+
+				done();
+			}, 100 );
+		} );
+
+		it( 'should insert the image element even if new element appeared above the pasted URL', done => {
+			setData( editor.model, '<paragraph>Foo.</paragraph><paragraph>Bar.[]</paragraph>' );
+
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			editor.model.enqueueChange( 'transparent', writer => {
+				const paragraph = writer.createElement( 'paragraph' );
+				writer.insert( paragraph, writer.createPositionAfter( editor.model.document.getRoot().getChild( 0 ) ) );
+				writer.setSelection( paragraph, 'in' );
+			} );
+
+			for ( let i = 0; i < 10; ++i ) {
+				setTimeout( () => {
+					editor.model.enqueueChange( 'transparent', writer => {
+						writer.insertText( characters[ i ], editor.model.document.selection.getFirstPosition() );
+					} );
+				}, i * 5 );
+			}
+
+			setTimeout( () => {
+				expect( getData( editor.model ) ).to.equal(
+					'<paragraph>Foo.</paragraph>' +
+					'<paragraph>ABCDEFGHIJ</paragraph>' +
+					'<paragraph>Bar.</paragraph>' +
+					'[<image src="http://example.com/image.png"><caption></caption></image>]'
+				);
+
+				done();
+			}, 100 );
+		} );
+
+		it( 'should insert the image element even if new element appeared below the pasted URL', done => {
+			setData( editor.model, '<paragraph>Foo.</paragraph><paragraph>Bar.[]</paragraph>' );
+
+			pasteHtml( editor, 'http://example.com/image.png' );
+
+			editor.model.enqueueChange( 'transparent', writer => {
+				const paragraph = writer.createElement( 'paragraph' );
+				writer.insert( paragraph, writer.createPositionAfter( editor.model.document.getRoot().getChild( 1 ) ) );
+				writer.setSelection( paragraph, 'in' );
+			} );
+
+			for ( let i = 0; i < 10; ++i ) {
+				setTimeout( () => {
+					editor.model.enqueueChange( 'transparent', writer => {
+						writer.insertText( characters[ i ], editor.model.document.selection.getFirstPosition() );
+					} );
+				}, i * 5 );
+			}
+
+			setTimeout( () => {
+				expect( getData( editor.model ) ).to.equal(
+					'<paragraph>Foo.</paragraph>' +
+					'<paragraph>Bar.</paragraph>' +
+					'[<image src="http://example.com/image.png"><caption></caption></image>]' +
+					'<paragraph>ABCDEFGHIJ</paragraph>'
+				);
+
+				done();
+			}, 100 );
+		} );
+	} );
+
+	it( 'should detach LiveRange', async () => {
+		const editor = await ClassicTestEditor.create( editorElement, {
+			plugins: [ Typing, Paragraph, Link, Image, ImageCaption, Table, AutoImage ]
+		} );
+
+		setData(
+			editor.model,
+			'<table>' +
+				'<tableRow>' +
+					'[<tableCell><paragraph>foo</paragraph></tableCell>]' +
+					'[<tableCell><paragraph>bar</paragraph></tableCell>]' +
+				'</tableRow>' +
+			'</table>'
+		);
+
+		pasteHtml( editor, '<table><tr><td>one</td><td>two</td></tr></table>' );
+
+		expect( getData( editor.model, { withoutSelection: true } ) ).to.equal(
+			'<table>' +
+				'<tableRow>' +
+					'<tableCell><paragraph>one</paragraph></tableCell>' +
+					'<tableCell><paragraph>two</paragraph></tableCell>' +
+				'</tableRow>' +
+			'</table>'
+		);
+
+		expect( () => {
+			editor.setData( '' );
+		} ).not.to.throw();
+
+		await editor.destroy();
+	} );
+
+	function simulateTyping( text ) {
+		// While typing, every character is an atomic change.
+		text.split( '' ).forEach( character => {
+			editor.execute( 'input', {
+				text: character
+			} );
+		} );
+	}
+
+	function pasteHtml( editor, html ) {
+		editor.editing.view.document.fire( 'paste', {
+			dataTransfer: createDataTransfer( { 'text/html': html } ),
+			stopPropagation() {},
+			preventDefault() {}
+		} );
+	}
+
+	function createDataTransfer( data ) {
+		return {
+			getData( type ) {
+				return data[ type ];
+			}
+		};
+	}
+} );

+ 3 - 9
packages/ckeditor5-image/tests/image/utils.js

@@ -240,9 +240,7 @@ describe( 'image widget utils', () => {
 		it( 'should insert image at selection position as other widgets', () => {
 			setModelData( model, '<paragraph>f[o]o</paragraph>' );
 
-			model.change( writer => {
-				insertImage( writer, model );
-			} );
+			insertImage( model );
 
 			expect( getModelData( model ) ).to.equal( '[<image></image>]<paragraph>foo</paragraph>' );
 		} );
@@ -250,9 +248,7 @@ describe( 'image widget utils', () => {
 		it( 'should insert image with given attributes', () => {
 			setModelData( model, '<paragraph>f[o]o</paragraph>' );
 
-			model.change( writer => {
-				insertImage( writer, model, { src: 'bar' } );
-			} );
+			insertImage( model, { src: 'bar' } );
 
 			expect( getModelData( model ) ).to.equal( '[<image src="bar"></image>]<paragraph>foo</paragraph>' );
 		} );
@@ -268,9 +264,7 @@ describe( 'image widget utils', () => {
 
 			setModelData( model, '<other>[]</other>' );
 
-			model.change( writer => {
-				insertImage( writer, model );
-			} );
+			insertImage( model );
 
 			expect( getModelData( model ) ).to.equal( '<other>[]</other>' );
 		} );

+ 37 - 0
packages/ckeditor5-image/tests/imageresize/_utils/utils.js

@@ -0,0 +1,37 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import ImageLoadObserver from '../../../src/image/imageloadobserver';
+
+// A 100x50 black png image
+export const IMAGE_SRC_FIXTURE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAQAAAAAPLY1AAAAQklEQVR42u3PQREAAAgDoK1/' +
+		'aM3g14MGNJMXKiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiJysRFNMgH0RpujAAAAAElFTkSuQmCC';
+
+export async function waitForAllImagesLoaded( editor ) {
+	// Returns a promise that waits for all the images in editor to be loaded.
+	// This is needed because resizers are created only after images are loaded.
+	const root = editor.model.document.getRoot();
+	const editingView = editor.editing.view;
+	const images = new Set();
+
+	for ( const curModel of root.getChildren() ) {
+		if ( curModel.is( 'element', 'image' ) ) {
+			const imageView = editor.editing.mapper.toViewElement( curModel );
+			images.add( editingView.domConverter.viewToDom( imageView ).querySelector( 'img' ) );
+		}
+	}
+
+	editingView.addObserver( ImageLoadObserver );
+
+	return new Promise( resolve => {
+		editingView.document.on( 'imageLoaded', ( evt, domEvent ) => {
+			images.delete( domEvent.target );
+
+			if ( images.size === 0 ) {
+				resolve();
+			}
+		} );
+	} );
+}

+ 1 - 4
packages/ckeditor5-image/tests/imageresize/imageresizeediting.js

@@ -18,12 +18,9 @@ import ImageStyle from '../../src/imagestyle';
 import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 import { focusEditor } from '@ckeditor/ckeditor5-widget/tests/widgetresize/_utils/utils';
+import { IMAGE_SRC_FIXTURE } from './_utils/utils';
 
 describe( 'ImageResizeEditing', () => {
-	// 100x50 black png image
-	const IMAGE_SRC_FIXTURE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAQAAAAAPLY1AAAAQklEQVR42u3PQREAAAgDoK1/' +
-		'aM3g14MGNJMXKiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiJysRFNMgH0RpujAAAAAElFTkSuQmCC';
-
 	let editor, editorElement;
 
 	beforeEach( () => {

+ 78 - 17
packages/ckeditor5-image/tests/imageresize/imageresizehandles.js

@@ -30,12 +30,9 @@ import {
 } from '@ckeditor/ckeditor5-widget/tests/widgetresize/_utils/utils';
 
 import WidgetResize from '@ckeditor/ckeditor5-widget/src/widgetresize';
+import { IMAGE_SRC_FIXTURE, waitForAllImagesLoaded } from './_utils/utils';
 
 describe( 'ImageResizeHandles', () => {
-	// 100x50 black png image
-	const IMAGE_SRC_FIXTURE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAQAAAAAPLY1AAAAQklEQVR42u3PQREAAAgDoK1/' +
-		'aM3g14MGNJMXKiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiJysRFNMgH0RpujAAAAAElFTkSuQmCC';
-
 	let widget, editor, view, viewDocument, editorElement;
 
 	beforeEach( () => {
@@ -62,7 +59,7 @@ describe( 'ImageResizeHandles', () => {
 
 		const attachToSpy = sinon.spy( localEditor.plugins.get( WidgetResize ), 'attachTo' );
 
-		setData( localEditor.model, `[<image imageStyle="side" src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+		await setModelAndWaitForImages( localEditor, `[<image imageStyle="side" src="${ IMAGE_SRC_FIXTURE }"></image>]` );
 
 		expect( attachToSpy.args[ 0 ][ 0 ] ).to.have.a.property( 'unit', '%' );
 
@@ -79,7 +76,7 @@ describe( 'ImageResizeHandles', () => {
 		it( 'uses the command on commit', async () => {
 			const spy = sinon.spy( editor.commands.get( 'imageResize' ), 'execute' );
 
-			setData( editor.model, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+			await setModelAndWaitForImages( editor, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
 			widget = viewDocument.getRoot().getChild( 1 );
 			const domParts = getWidgetDomParts( editor, widget, 'bottom-left' );
 
@@ -92,7 +89,7 @@ describe( 'ImageResizeHandles', () => {
 		} );
 
 		it( 'disables the resizer if the command is disabled', async () => {
-			setData( editor.model, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+			await setModelAndWaitForImages( editor, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
 
 			const resizer = getSelectedImageResizer( editor );
 
@@ -124,6 +121,8 @@ describe( 'ImageResizeHandles', () => {
 				editor.model.insertContent( writer.createElement( 'image', { src: IMAGE_SRC_FIXTURE } ) );
 			} );
 
+			await waitForAllImagesLoaded( editor );
+
 			const resizer = getSelectedImageResizer( editor );
 			const resizerWrapper = editor.ui.getEditableElement().querySelector( '.ck-widget__resizer' );
 
@@ -136,7 +135,8 @@ describe( 'ImageResizeHandles', () => {
 		beforeEach( async () => {
 			editor = await createEditor();
 
-			setData( editor.model, `<paragraph>foo</paragraph>[<image imageStyle="side" src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+			await setModelAndWaitForImages( editor,
+				`<paragraph>foo</paragraph>[<image imageStyle="side" src="${ IMAGE_SRC_FIXTURE }"></image>]` );
 
 			widget = viewDocument.getRoot().getChild( 1 );
 		} );
@@ -178,7 +178,7 @@ describe( 'ImageResizeHandles', () => {
 		beforeEach( async () => {
 			editor = await createEditor();
 
-			setData( editor.model, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+			await setModelAndWaitForImages( editor, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
 
 			widget = viewDocument.getRoot().getChild( 1 );
 		} );
@@ -200,9 +200,9 @@ describe( 'ImageResizeHandles', () => {
 
 			// Toggle _visibleResizer to force synchronous redraw. Otherwise you'd need to wait ~200ms for
 			// throttled redraw to take place, making tests slower.
-			const visibleResizer = plugin._visibleResizer;
-			plugin._visibleResizer = null;
-			plugin._visibleResizer = visibleResizer;
+			for ( const [ , resizer ] of plugin._resizers.entries() ) {
+				resizer.redraw();
+			}
 
 			const resizerWrapper = document.querySelector( '.ck-widget__resizer' );
 			const shadowBoundingRect = resizerWrapper.getBoundingClientRect();
@@ -210,13 +210,37 @@ describe( 'ImageResizeHandles', () => {
 			expect( shadowBoundingRect.width ).to.equal( 100 );
 			expect( shadowBoundingRect.height ).to.equal( 50 );
 		} );
+
+		it( 'doesn\'t show resizers when undoing to multiple images', async () => {
+			// Based on https://github.com/ckeditor/ckeditor5/pull/8108#issuecomment-695949745.
+			await setModelAndWaitForImages( editor,
+				`[<image src="${ IMAGE_SRC_FIXTURE }"></image><image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+
+			const paragraph = editor.model.change( writer => {
+				return writer.createElement( 'paragraph' );
+			} );
+			editor.model.insertContent( paragraph );
+
+			// Undo to go back to two, selected images.
+			editor.commands.get( 'undo' ).execute();
+
+			for ( let i = 0; i < 2; i++ ) {
+				widget = viewDocument.getRoot().getChild( i );
+				const domImage = getWidgetDomParts( editor, widget, 'bottom-right' ).widget.querySelector( 'img' );
+				viewDocument.fire( 'imageLoaded', { target: domImage } );
+
+				const domResizeWrapper = getWidgetDomParts( editor, widget, 'bottom-left' ).resizeWrapper;
+
+				expect( domResizeWrapper.getBoundingClientRect().height ).to.equal( 0 );
+			}
+		} );
 	} );
 
 	describe( 'table integration', () => {
 		it( 'works when resizing in a table', async () => {
 			editor = await createEditor();
 
-			setData( editor.model,
+			await setModelAndWaitForImages( editor,
 				'<table>' +
 					`<tableRow><tableCell>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]</tableCell></tableRow>` +
 				'</table>'
@@ -238,6 +262,36 @@ describe( 'ImageResizeHandles', () => {
 		} );
 	} );
 
+	it( 'doesn\'t create multiple resizers for a single image widget', async () => {
+		// https://github.com/ckeditor/ckeditor5/pull/8108#issuecomment-708302992
+		editor = await createEditor();
+		await setModelAndWaitForImages( editor, `[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+		widget = viewDocument.getRoot().getChild( 0 );
+
+		const domParts = getWidgetDomParts( editor, widget, 'bottom-right' );
+		const alternativeImageFixture =
+			'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=';
+
+		// Change the image so that load event triggers for the same img element again.
+		domParts.widget.querySelector( 'img' ).src = alternativeImageFixture;
+		await waitForAllImagesLoaded( editor );
+
+		expect( domParts.widget.querySelectorAll( '.ck-widget__resizer' ).length ).to.equal( 1 );
+	} );
+
+	it( 'only creates a resizer after the image is loaded', async () => {
+		// https://github.com/ckeditor/ckeditor5/issues/8088
+		editor = await createEditor();
+		setData( editor.model, `[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+		widget = viewDocument.getRoot().getChild( 0 );
+		const domParts = getWidgetDomParts( editor, widget, 'bottom-right' );
+
+		expect( domParts.widget.querySelectorAll( '.ck-widget__resizer' ).length ).to.equal( 0 );
+
+		await waitForAllImagesLoaded( editor );
+		expect( domParts.widget.querySelectorAll( '.ck-widget__resizer' ).length ).to.equal( 1 );
+	} );
+
 	describe( 'srcset integration', () => {
 		// The image is 96x96 pixels.
 		const imageBaseUrl = '/assets/sample.png';
@@ -272,11 +326,13 @@ describe( 'ImageResizeHandles', () => {
 				</figure>`
 			);
 
+			await waitForAllImagesLoaded( editor );
+
 			widget = viewDocument.getRoot().getChild( 0 );
 			model = editor.model.document.getRoot().getChild( 0 );
 		} );
 
-		it( 'works with images containing srcset', () => {
+		it( 'works with images containing srcset', async () => {
 			const domParts = getWidgetDomParts( editor, widget, 'bottom-right' );
 			const initialPosition = getHandleCenterPoint( domParts.widget, 'bottom-right' );
 			const finalPointerPosition = initialPosition.clone().moveBy( -20, -20 );
@@ -289,7 +345,7 @@ describe( 'ImageResizeHandles', () => {
 			expect( model.getAttribute( 'width' ) ).to.equal( '76px' );
 		} );
 
-		it( 'retains width after removing srcset', () => {
+		it( 'retains width after removing srcset', async () => {
 			const domParts = getWidgetDomParts( editor, widget, 'bottom-right' );
 			const initialPosition = getHandleCenterPoint( domParts.widget, 'bottom-right' );
 			const finalPointerPosition = initialPosition.clone().moveBy( -16, -16 );
@@ -339,7 +395,7 @@ describe( 'ImageResizeHandles', () => {
 				}
 			} );
 
-			setData( editor.model, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
+			await setModelAndWaitForImages( editor, `<paragraph>foo</paragraph>[<image src="${ IMAGE_SRC_FIXTURE }"></image>]` );
 
 			widget = viewDocument.getRoot().getChild( 1 );
 
@@ -395,7 +451,7 @@ describe( 'ImageResizeHandles', () => {
 	}
 
 	function getSelectedImageResizer( editor ) {
-		return editor.plugins.get( 'WidgetResize' )._getResizerByViewElement(
+		return editor.plugins.get( 'WidgetResize' ).getResizerByViewElement(
 			editor.editing.view.document.selection.getSelectedElement()
 		);
 	}
@@ -415,4 +471,9 @@ describe( 'ImageResizeHandles', () => {
 			return newEditor;
 		} );
 	}
+
+	async function setModelAndWaitForImages( editor, data ) {
+		setData( editor.model, data );
+		return waitForAllImagesLoaded( editor );
+	}
 } );

+ 3 - 2
packages/ckeditor5-image/tests/manual/imageinsertviaurl.js

@@ -7,12 +7,13 @@
 
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
-import ImageInsert from '../../src/imageinsert';
 import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
+import ImageInsert from '../../src/imageinsert';
+import AutoImage from '../../src/autoimage';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ ArticlePluginSet, ImageInsert, CKFinder ],
+		plugins: [ ArticlePluginSet, ImageInsert, AutoImage, CKFinder ],
 		toolbar: [
 			'heading',
 			'|',

+ 5 - 0
packages/ckeditor5-image/tests/manual/imageinsertviaurl.md

@@ -16,3 +16,8 @@
 1. In the **Editor 2** click on the arrow button in `imageInsert` plugin to reveal the image insert panel.
 1. Click on the **CKFinder** button.
 1. Choose image and confirm.
+
+## Image insert via pasting URL into editor
+
+1. Copy image URL to clipboard (eg: `https://ckeditor.com/docs/ckeditor5/latest/assets/img/malta.jpg`).
+1. Paste the URL to the editor.

+ 1 - 1
packages/ckeditor5-indent/src/indentui.js

@@ -70,7 +70,7 @@ export default class IndentUI extends Plugin {
 
 			this.listenTo( view, 'execute', () => {
 				editor.execute( commandName );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

+ 2 - 2
packages/ckeditor5-link/src/linkui.js

@@ -371,7 +371,7 @@ export default class LinkUI extends Plugin {
 
 			// Because the form has an input which has focus, the focus must be brought back
 			// to the editor. Otherwise, it would be lost.
-			this.editor.editing.view.focus();
+			this.editor.focus();
 
 			this._hideFakeVisualSelection();
 		}
@@ -439,7 +439,7 @@ export default class LinkUI extends Plugin {
 
 		// Make sure the focus always gets back to the editable _before_ removing the focused form view.
 		// Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
-		editor.editing.view.focus();
+		editor.focus();
 
 		// Remove form first because it's on top of the stack.
 		this._removeFormView();

+ 81 - 24
packages/ckeditor5-list/src/liststyleediting.js

@@ -490,43 +490,100 @@ function fixListStyleAttributeOnListItemElements( editor ) {
 				} else {
 					writer.setAttribute( 'listStyle', DEFAULT_LIST_TYPE, item );
 				}
-
 				wasFixed = true;
+			} else {
+				// Adjust the `listStyle` attribute for inserted (pasted) items. See #8160.
+				//
+				// ■ List item 1. // [listStyle="square", listType="bulleted"]
+				//     ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
+				//     ○ [] (selection is here)
+				//
+				// Then, pasting a list with different attributes (listStyle, listType):
+				//
+				// 1. First. // [listStyle="decimal", listType="numbered"]
+				// 2. Second // [listStyle="decimal", listType="numbered"]
+				//
+				// The `listType` attribute will be corrected by the `ListEditing` converters.
+				// We need to adjust the `listStyle` attribute. Expected structure:
+				//
+				// ■ List item 1. // [listStyle="square", listType="bulleted"]
+				//     ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
+				//     ○ First. // [listStyle="circle", listType="bulleted"]
+				//     ○ Second // [listStyle="circle", listType="bulleted"]
+				const previousSibling = item.previousSibling;
+
+				if ( shouldInheritListTypeFromPreviousItem( previousSibling, item ) ) {
+					writer.setAttribute( 'listStyle', previousSibling.getAttribute( 'listStyle' ), item );
+
+					wasFixed = true;
+				}
 			}
 		}
 
 		return wasFixed;
 	};
+}
 
-	// Checks whether the `listStyle` attribute should be copied from the `baseItem` element.
-	//
-	// The attribute should be copied if the inserted element does not have defined it and
-	// the value for the element is other than default in the base element.
-	//
-	// @param {module:engine/model/element~Element|null} baseItem
-	// @param {module:engine/model/element~Element} itemToChange
-	// @returns {Boolean}
-	function shouldInheritListType( baseItem, itemToChange ) {
-		if ( !baseItem ) {
-			return false;
-		}
+// Checks whether the `listStyle` attribute should be copied from the `baseItem` element.
+//
+// The attribute should be copied if the inserted element does not have defined it and
+// the value for the element is other than default in the base element.
+//
+// @param {module:engine/model/element~Element|null} baseItem
+// @param {module:engine/model/element~Element} itemToChange
+// @returns {Boolean}
+function shouldInheritListType( baseItem, itemToChange ) {
+	if ( !baseItem ) {
+		return false;
+	}
 
-		const baseListStyle = baseItem.getAttribute( 'listStyle' );
+	const baseListStyle = baseItem.getAttribute( 'listStyle' );
 
-		if ( !baseListStyle ) {
-			return false;
-		}
+	if ( !baseListStyle ) {
+		return false;
+	}
 
-		if ( baseListStyle === DEFAULT_LIST_TYPE ) {
-			return false;
-		}
+	if ( baseListStyle === DEFAULT_LIST_TYPE ) {
+		return false;
+	}
 
-		if ( baseItem.getAttribute( 'listType' ) !== itemToChange.getAttribute( 'listType' ) ) {
-			return false;
-		}
+	if ( baseItem.getAttribute( 'listType' ) !== itemToChange.getAttribute( 'listType' ) ) {
+		return false;
+	}
 
-		return true;
+	return true;
+}
+
+// Checks whether the `listStyle` attribute should be copied from previous list item.
+//
+// The attribute should be copied if there's a mismatch of styles of the pasted list into a nested list.
+// Top-level lists are not normalized as we allow side-by-side list of different types.
+//
+// @param {module:engine/model/element~Element|null} previousItem
+// @param {module:engine/model/element~Element} itemToChange
+// @returns {Boolean}
+function shouldInheritListTypeFromPreviousItem( previousItem, itemToChange ) {
+	if ( !previousItem || !previousItem.is( 'element', 'listItem' ) ) {
+		return false;
+	}
+
+	if ( itemToChange.getAttribute( 'listType' ) !== previousItem.getAttribute( 'listType' ) ) {
+		return false;
+	}
+
+	const previousItemIndent = previousItem.getAttribute( 'listIndent' );
+
+	if ( previousItemIndent < 1 || previousItemIndent !== itemToChange.getAttribute( 'listIndent' ) ) {
+		return false;
 	}
+
+	const previousItemListStyle = previousItem.getAttribute( 'listStyle' );
+
+	if ( !previousItemListStyle || previousItemListStyle === itemToChange.getAttribute( 'listStyle' ) ) {
+		return false;
+	}
+
+	return true;
 }
 
 // Removes the `listStyle` attribute from "todo" list items.

+ 2 - 2
packages/ckeditor5-list/src/liststyleui.js

@@ -158,7 +158,7 @@ function getSplitButtonCreator( { editor, parentCommandName, buttonLabel, button
 
 		splitButtonView.on( 'execute', () => {
 			editor.execute( parentCommandName );
-			editor.editing.view.focus();
+			editor.focus();
 		} );
 
 		splitButtonView.set( {
@@ -222,7 +222,7 @@ function getStyleButtonCreator( { editor, listStyleCommand, parentCommandName }
 				} );
 			}
 
-			editor.editing.view.focus();
+			editor.focus();
 		} );
 
 		return button;

+ 1 - 1
packages/ckeditor5-list/src/utils.js

@@ -262,7 +262,7 @@ export function createUIComponent( editor, commandName, label, icon ) {
 		// Execute command.
 		buttonView.on( 'execute', () => {
 			editor.execute( commandName );
-			editor.editing.view.focus();
+			editor.focus();
 		} );
 
 		return buttonView;

+ 2 - 2
packages/ckeditor5-list/tests/liststylecommand.js

@@ -96,7 +96,7 @@ describe( 'ListStyleCommand', () => {
 		it( 'should return the value of `listStyle` attribute for the selection inside a nested list', () => {
 			setData( model,
 				'<listItem listIndent="0" listType="bulleted" listStyle="square">1.</listItem>' +
-				'<listItem listIndent="0" listType="bulleted" listStyle="disc">1.1.[]</listItem>' +
+				'<listItem listIndent="1" listType="bulleted" listStyle="disc">1.1.[]</listItem>' +
 				'<listItem listIndent="0" listType="bulleted" listStyle="square">2.</listItem>'
 			);
 
@@ -106,7 +106,7 @@ describe( 'ListStyleCommand', () => {
 		it( 'should return the value of `listStyle` attribute from a list where the selection starts (selection over nested list)', () => {
 			setData( model,
 				'<listItem listIndent="0" listType="bulleted" listStyle="square">1.</listItem>' +
-				'<listItem listIndent="0" listType="bulleted" listStyle="disc">1.1.[</listItem>' +
+				'<listItem listIndent="1" listType="bulleted" listStyle="disc">1.1.[</listItem>' +
 				'<listItem listIndent="0" listType="bulleted" listStyle="square">2.]</listItem>'
 			);
 

+ 163 - 0
packages/ckeditor5-list/tests/liststyleediting.js

@@ -3,10 +3,14 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
+/* global document */
+
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 
@@ -1459,5 +1463,164 @@ describe( 'ListStyleEditing', () => {
 				} );
 			}
 		} );
+
+		// #8160
+		describe( 'pasting a list into another list', () => {
+			let element;
+
+			beforeEach( () => {
+				element = document.createElement( 'div' );
+				document.body.append( element );
+
+				return ClassicTestEditor
+					.create( element, {
+						plugins: [ Paragraph, Clipboard, ListStyleEditing, UndoEditing ]
+					} )
+					.then( newEditor => {
+						editor = newEditor;
+						model = editor.model;
+					} );
+			} );
+
+			afterEach( () => {
+				return editor.destroy()
+					.then( () => {
+						element.remove();
+					} );
+			} );
+
+			it( 'should inherit attributes from the previous sibling element (collapsed selection)', () => {
+				setModelData( model,
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+
+				pasteHtml( editor,
+					'<ul style="list-style-type: square">' +
+						'<li>Foo 1</li>' +
+						'<li>Foo 2</li>' +
+					'</ul>'
+				);
+
+				expect( getModelData( model ) ).to.equal(
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 1</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 2[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+			} );
+
+			it( 'should inherit attributes from the previous sibling element (non-collapsed selection)', () => {
+				setModelData( model,
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">[Foo]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+
+				pasteHtml( editor,
+					'<ul style="list-style-type: square">' +
+						'<li>Foo 1</li>' +
+						'<li>Foo 2</li>' +
+					'</ul>'
+				);
+
+				expect( getModelData( model ) ).to.equal(
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 1</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 2[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+			} );
+
+			it( 'should inherit attributes from the previous sibling element (non-collapsed selection over a few elements)', () => {
+				setModelData( model,
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">[Foo 1.</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 2.</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 3.]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+
+				pasteHtml( editor,
+					'<ul style="list-style-type: square">' +
+						'<li>Foo 1</li>' +
+						'<li>Foo 2</li>' +
+					'</ul>'
+				);
+
+				expect( getModelData( model ) ).to.equal(
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 1</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo 2[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+			} );
+
+			it( 'should do nothing when pasting the similar list', () => {
+				setModelData( model,
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+
+				pasteHtml( editor,
+					'<ol style="list-style-type: decimal">' +
+						'<li>Foo</li>' +
+					'</ol>'
+				);
+
+				expect( getModelData( model ) ).to.equal(
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo Bar</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">Foo[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+			} );
+
+			it( 'should replace the entire list if selected', () => {
+				setModelData( model,
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="decimal" listType="numbered">[Foo Bar]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+
+				pasteHtml( editor,
+					'<ul style="list-style-type: square">' +
+						'<li>Foo</li>' +
+					'</ul>'
+				);
+
+				expect( getModelData( model ) ).to.equal(
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Foo</listItem>' +
+					'<listItem listIndent="1" listStyle="square" listType="bulleted">Foo[]</listItem>' +
+					'<listItem listIndent="0" listStyle="circle" listType="bulleted">Bar</listItem>'
+				);
+			} );
+
+			function pasteHtml( editor, html ) {
+				editor.editing.view.document.fire( 'paste', {
+					dataTransfer: createDataTransfer( { 'text/html': html } ),
+					stopPropagation() {},
+					preventDefault() {}
+				} );
+			}
+
+			function createDataTransfer( data ) {
+				return {
+					getData( type ) {
+						return data[ type ];
+					},
+					setData() {}
+				};
+			}
+		} );
 	} );
 } );

+ 35 - 1
packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.html

@@ -1,4 +1,38 @@
-<div id="snippet-markdown">This is [CKEditor 5](https://ckeditor.com).</div>
+<div id="snippet-markdown">
+
+<h2>The Markdown language</h2>
+<p>Markdown is a lightweight markup language that you can use to add formatting elements to plain text documents. Created by <a href="https://daringfireball.net/projects/markdown/" target="_blank">John Gruber</a> in 2004, Markdown is now one of the world&rsquo;s most popular markup languages.</p>
+
+<h2>Editing with Markdown output</h2>
+<p>The <a href="https://ckeditor.com/">CKEditor 5 WYSIWYG</a> editor lets you use this flexible yet simple markup language in the GitHub flavor. The editor-produced Markdown output supports the most important features, like <a href="https://ckeditor.com/">links</a>, <strong>different</strong> kinds of <em>emphasis</em>, <code>inline code formatting</code> or code blocks:</p>
+
+<pre>
+<code class="language-css">p {
+  text-align: center;
+  color: red;
+}</code>
+</pre>
+
+<h2>Formatting blocks with Markdown</h2>
+<p>Markdown can be used to create various block-level features, such as:</p>
+<ul>
+	<li>Block quotes</li>
+	<li>Headings
+		<ol>
+			<li>Heading 1</li>
+			<li>Heading 2</li>
+			<li>Heading 3</li>
+		</ol>
+	</li>
+	<li>Lists, including nested ones
+		<ul>
+			<li>Numbered lists</li>
+			<li>Bulleted lists</li>
+		</ul>
+	</li>
+</ul>
+
+</div>
 
 <p>Output:</p>
 

+ 20 - 2
packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js

@@ -9,32 +9,50 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
 
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
+import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock';
+import Code from '@ckeditor/ckeditor5-basic-styles/src/code';
+import TodoList from '@ckeditor/ckeditor5-list/src/todolist';
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
 import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown';
 
 ClassicEditor
 	.create( document.querySelector( '#snippet-markdown' ), {
-		plugins: [ ArticlePluginSet, EasyImage, Markdown ],
+		plugins: [ ArticlePluginSet, EasyImage, Markdown, Code, CodeBlock, TodoList ],
 		toolbar: [
 			'heading',
 			'|',
 			'bold',
 			'italic',
 			'link',
+			'|',
 			'bulletedList',
 			'numberedList',
+			'todoList',
+			'|',
+			'code',
+			'codeBlock',
 			'|',
 			'outdent',
 			'indent',
 			'|',
+			'imageUpload',
 			'blockQuote',
+			'|',
 			'undo',
 			'redo'
 		],
 		image: {
 			toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
 		},
+		codeBlock: {
+			languages: [
+				{ language: 'css', label: 'CSS' },
+				{ language: 'html', label: 'HTML' },
+				{ language: 'javascript', label: 'JavaScript' },
+				{ language: 'php', label: 'PHP' }
+			]
+		},
 		cloudServices: CS_CONFIG
 	} )
 	.then( editor => {
@@ -46,7 +64,7 @@ ClassicEditor
 			outputElement.innerText = editor.getData();
 		} );
 
-		// Set the initial data with delay so hightlight.js doesn't catch them.
+		// Set the initial data with delay so hightlight.js doesn't catch it.
 		setTimeout( () => {
 			outputElement.innerText = editor.getData();
 		}, 500 );

+ 19 - 9
packages/ckeditor5-markdown-gfm/docs/features/markdown.md

@@ -12,26 +12,40 @@ editor.setData( 'This is **bold**.' );
 ```
 
 <info-box info>
-	"GFM" stands for "GitHub Flavored Markdown" &mdash; a Markdown dialect used by [GitHub](https://github.com). Markdown lacks any formal specification and has many dialects, often incompatible with each other. When converting the output produced by this data processor make sure to use a compatible Markdown to HTML converter (e.g. the [marked](https://www.npmjs.com/package/marked) library).
+	"GFM" stands for "GitHub Flavored Markdown" &mdash; a Markdown dialect used by [GitHub](https://github.com). Markdown lacks any formal specification (although the [CommonMark](https://commonmark.org/) initiative aims to close this gap) and has many dialects, often incompatible with one another. When converting the output produced by this data processor, make sure to use a compatible Markdown-to-HTML converter (e.g. the [marked](https://www.npmjs.com/package/marked) library).
 </info-box>
 
 <info-box info>
-	**This feature is experimental!**
+	This feature is still experimental but a stable version is coming soon.
 
 	While the CKEditor 5 architecture supports changing the data format, in most scenarios we do recommend sticking to the default format which is HTML (supported by the {@link module:engine/dataprocessor/htmldataprocessor~HtmlDataProcessor}). HTML remains [the best standard for rich-text data](https://medium.com/content-uneditable/a-standard-for-rich-text-data-4b3a507af552).
 
-	And please do remember – using Markdown [does not automatically make your application/website secure](https://github.com/ckeditor/ckeditor5-markdown-gfm/issues/16#issuecomment-375752994).
+	And please do remember &mdash; using Markdown [does not automatically make your application or website secure](https://github.com/ckeditor/ckeditor5-markdown-gfm/issues/16#issuecomment-375752994).
 </info-box>
 
+## Demo
+
+The CKEditor 5 instance below is configured to output GitHub Flavored Markdown. Use the editor to create your content and see the Markdown output displayed as you type below the editor.
+
+{@snippet features/markdown}
+
+## Related features
+
+Some other ways to output the edited content include:
+
+* [Export to Word](https://ckeditor.com/docs/ckeditor5/latest/features/export-word.html) &ndash; Generate editable `.docx` files out of your editor-created content.
+* [Export to PDF](https://ckeditor.com/docs/ckeditor5/latest/features/export-pdf.html) &ndash; Generate portable PDF files out of your editor-created content.
+* {@link features/autoformat Autoformatting} &ndash; Use Markdown-like markers to automatically format your content as you type!
+
 ## Installation
 
-To enable this data processor in your editor install the [`@ckeditor/ckeditor5-markdown-gfm`](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm) package:
+To enable this data processor in your editor, install the [`@ckeditor/ckeditor5-markdown-gfm`](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm) package:
 
 ```
 npm install --save @ckeditor/ckeditor5-markdown-gfm
 ```
 
-Then, you can enable this data processor by using {@link module:markdown-gfm/markdown~Markdown} plugin which will change default {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor} with {@link module:markdown-gfm/gfmdataprocessor~GFMDataProcessor}:
+Then, you can enable this data processor by using the {@link module:markdown-gfm/markdown~Markdown} plugin which will change the default {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor} to the {@link module:markdown-gfm/gfmdataprocessor~GFMDataProcessor}:
 
 ```js
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
@@ -60,10 +74,6 @@ ClassicEditor
 
 ```
 
-## Demo
-
-{@snippet features/markdown}
-
 ## Contribute
 
 The source code of this feature is available on GitHub in https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-markdown-gfm.

+ 1 - 1
packages/ckeditor5-media-embed/src/mediaembedui.js

@@ -92,7 +92,7 @@ export default class MediaEmbedUI extends Plugin {
 		dropdown.on( 'cancel', () => closeUI() );
 
 		function closeUI() {
-			editor.editing.view.focus();
+			editor.focus();
 			dropdown.isOpen = false;
 		}
 	}

+ 9 - 11
packages/ckeditor5-media-embed/tests/automediaembed.js

@@ -11,8 +11,6 @@ import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictest
 import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Link from '@ckeditor/ckeditor5-link/src/link';
-import List from '@ckeditor/ckeditor5-list/src/list';
-import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import Image from '@ckeditor/ckeditor5-image/src/image';
@@ -30,7 +28,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 
 		return ClassicTestEditor
 			.create( editorElement, {
-				plugins: [ MediaEmbed, AutoMediaEmbed, Link, List, Bold, Typing, Image, ImageCaption ]
+				plugins: [ Typing, Paragraph, Link, Image, ImageCaption, MediaEmbed, AutoMediaEmbed ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -109,7 +107,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			);
 		} );
 
-		it( 'works for a full URL (https without "wwww" sub-domain)', () => {
+		it( 'works for a full URL (https without "www" sub-domain)', () => {
 			setData( editor.model, '<paragraph>[]</paragraph>' );
 			pasteHtml( editor, 'https://youtube.com/watch?v=H08tGjXNHO4' );
 
@@ -131,7 +129,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			);
 		} );
 
-		it( 'works for a full URL (http without "wwww" sub-domain)', () => {
+		it( 'works for a full URL (http without "www" sub-domain)', () => {
 			setData( editor.model, '<paragraph>[]</paragraph>' );
 			pasteHtml( editor, 'http://youtube.com/watch?v=H08tGjXNHO4' );
 
@@ -307,13 +305,13 @@ describe( 'AutoMediaEmbed - integration', () => {
 
 		it( 'does nothing if pasted a block of content that looks like a URL', () => {
 			setData( editor.model, '<paragraph>[]</paragraph>' );
-			pasteHtml( editor, '<ul><li>https://</li><li>youtube.com/watch?</li></ul><p>v=H08tGjXNHO4</p>' );
+			pasteHtml( editor, '<p>https://</p><p>youtube.com/watch?</p><p>v=H08tGjXNHO4</p>' );
 
 			clock.tick( 100 );
 
 			expect( getData( editor.model ) ).to.equal(
-				'<listItem listIndent="0" listType="bulleted">https://</listItem>' +
-				'<listItem listIndent="0" listType="bulleted">youtube.com/watch?</listItem>' +
+				'<paragraph>https://</paragraph>' +
+				'<paragraph>youtube.com/watch?</paragraph>' +
 				'<paragraph>v=H08tGjXNHO4[]</paragraph>'
 			);
 		} );
@@ -361,7 +359,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 				editor.model,
 				'<paragraph>Foo. <$text linkHref="https://cksource.com">Bar</$text></paragraph>' +
 				'[<media url="https://open.spotify.com/album/2IXlgvecaDqOeF3viUZnPI?si=ogVw7KlcQAGZKK4Jz9QzvA"></media>]' +
-				'<paragraph><$text bold="true">Bar</$text>.</paragraph>'
+				'<paragraph>Bar.</paragraph>'
 			);
 
 			pasteHtml( editor, 'https://www.youtube.com/watch?v=H08tGjXNHO4' );
@@ -371,7 +369,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			expect( getData( editor.model ) ).to.equal(
 				'<paragraph>Foo. <$text linkHref="https://cksource.com">Bar</$text></paragraph>' +
 				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]' +
-				'<paragraph><$text bold="true">Bar</$text>.</paragraph>'
+				'<paragraph>Bar.</paragraph>'
 			);
 		} );
 
@@ -587,7 +585,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 
 	it( 'should detach LiveRange', async () => {
 		const editor = await ClassicTestEditor.create( editorElement, {
-			plugins: [ MediaEmbed, AutoMediaEmbed, Link, List, Bold, Typing, Image, ImageCaption, Table ]
+			plugins: [ Typing, Paragraph, Link, Image, ImageCaption, Table, MediaEmbed, AutoMediaEmbed ]
 		} );
 
 		setData(

+ 1 - 1
packages/ckeditor5-mention/src/mentionui.js

@@ -253,7 +253,7 @@ export default class MentionUI extends Plugin {
 				range
 			} );
 
-			editor.editing.view.focus();
+			editor.focus();
 		} );
 
 		return mentionsView;

+ 1 - 1
packages/ckeditor5-page-break/src/pagebreakui.js

@@ -37,7 +37,7 @@ export default class PageBreakUI extends Plugin {
 			// Execute command.
 			this.listenTo( view, 'execute', () => {
 				editor.execute( 'pageBreak' );
-				editor.editing.view.focus();
+				editor.focus();
 			} );
 
 			return view;

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

@@ -56,13 +56,11 @@ export function transformListItemLikeElementsIntoLists( documentFragment, styles
 				const lastListItemChild = lastListItem.getChild( lastListItem.childCount - 1 );
 
 				currentList = insertNewEmptyList( listStyle, lastListItemChild, writer );
-
 				currentIndentation += 1;
 			} else if ( itemLikeElement.indent < currentIndentation ) {
 				const differentIndentation = currentIndentation - itemLikeElement.indent;
 
 				currentList = findParentListAtLevel( currentList, differentIndentation );
-
 				currentIndentation = parseInt( itemLikeElement.indent );
 			}
 
@@ -158,9 +156,9 @@ function findAllItemLikeElements( documentFragment, writer ) {
 // @param {String} stylesString CSS stylesheet.
 // @returns {Object} result
 // @returns {String} result.type List type, could be `ul` or `ol`.
-// @returns {String} result.style List style, for example: `decimal`, `lower-roman`, etc. It is extracted
-// directly from Word stylesheet without further processing and may be not compatible
-// with CSS `list-style-type` property accepted values.
+// @returns {String|null} result.style List style, for example: `decimal`, `lower-roman`, etc. It is extracted
+// directly from Word stylesheet and adjusted to represent proper values for the CSS `list-style-type` property.
+// If it cannot be adjusted, the `null` value is returned.
 function detectListStyle( listLikeItem, stylesString ) {
 	const listStyleRegexp = new RegExp( `@list l${ listLikeItem.id }:level${ listLikeItem.indent }\\s*({[^}]*)`, 'gi' );
 	const listStyleTypeRegex = /mso-level-number-format:([^;]*);/gi;
@@ -168,20 +166,105 @@ function detectListStyle( listLikeItem, stylesString ) {
 	const listStyleMatch = listStyleRegexp.exec( stylesString );
 
 	let listStyleType = 'decimal'; // Decimal is default one.
+	let type = 'ol'; // <ol> is default list.
+
 	if ( listStyleMatch && listStyleMatch[ 1 ] ) {
 		const listStyleTypeMatch = listStyleTypeRegex.exec( listStyleMatch[ 1 ] );
 
 		if ( listStyleTypeMatch && listStyleTypeMatch[ 1 ] ) {
 			listStyleType = listStyleTypeMatch[ 1 ].trim();
+			type = listStyleType !== 'bullet' && listStyleType !== 'image' ? 'ol' : 'ul';
+		}
+
+		// Styles for the numbered lists are always defined in Word CSS stylesheet.
+		// Unordered lists MAY contain a value for the Word CSS definition `mso-level-text` but sometimes
+		// the tag is missing. And because of that, we cannot depend on that. We need to predict the list style value based on
+		// the list style marker element.
+		if ( listStyleType === 'bullet' ) {
+			const bulletedStyle = findBulletedListStyle( listLikeItem.element );
+
+			if ( bulletedStyle ) {
+				listStyleType = bulletedStyle;
+			}
 		}
 	}
 
 	return {
-		type: listStyleType !== 'bullet' && listStyleType !== 'image' ? 'ol' : 'ul',
-		style: listStyleType
+		type,
+		style: mapListStyleDefinition( listStyleType )
 	};
 }
 
+// Tries extract the `list-style-type` value based on the marker element for bulleted list.
+//
+// @param {module:engine/view/element~Element} element
+// @returns {module:engine/view/element~Element|null}
+function findBulletedListStyle( element ) {
+	const listMarkerElement = findListMarkerNode( element );
+
+	if ( !listMarkerElement ) {
+		return null;
+	}
+
+	const listMarker = listMarkerElement._data;
+
+	if ( listMarker === 'o' ) {
+		return 'circle';
+	} else if ( listMarker === '·' ) {
+		return 'disc';
+	}
+	// Word returns '§' instead of '■' for the square list style.
+	else if ( listMarker === '§' ) {
+		return 'square';
+	}
+
+	return null;
+}
+
+// Tries to find a text node that represents the marker element (list-style-type).
+//
+// @param {module:engine/view/element~Element} element
+// @returns {module:engine/view/text~Text|null}
+function findListMarkerNode( element ) {
+	// If the first child is a text node, it is a value for the element.
+	if ( element.getChild( 0 ).is( '$text' ) ) {
+		return null;
+	}
+
+	const textNodeOrElement = element.getChild( 0 ).getChild( 0 );
+
+	if ( textNodeOrElement.is( '$text' ) ) {
+		return textNodeOrElement;
+	}
+
+	return textNodeOrElement.getChild( 0 );
+}
+
+// Parses the `list-style-type` value extracted directly from the Word CSS stylesheet and returns proper CSS definition.
+//
+// @param {String|null} value
+// @returns {String|null}
+function mapListStyleDefinition( value ) {
+	switch ( value ) {
+		case 'arabic-leading-zero':
+			return 'decimal-leading-zero';
+		case 'alpha-upper':
+			return 'upper-alpha';
+		case 'alpha-lower':
+			return 'lower-alpha';
+		case 'roman-upper':
+			return 'upper-roman';
+		case 'roman-lower':
+			return 'lower-roman';
+		case 'circle':
+		case 'disc':
+		case 'square':
+			return value;
+		default:
+			return null;
+	}
+}
+
 // Creates empty list of a given type and inserts it after a specified element.
 //
 // @param {Object} listStyle List style object which determines the type of newly created list.
@@ -197,6 +280,12 @@ function insertNewEmptyList( listStyle, element, writer ) {
 
 	writer.insertChild( position, list, parent );
 
+	// We do not support modifying the marker for particular list item.
+	// Set the value for the `list-style-type` property directly to the list container.
+	if ( listStyle.style ) {
+		writer.setStyle( 'list-style-type', listStyle.style, list );
+	}
+
 	return list;
 }
 

+ 2 - 2
packages/ckeditor5-paste-from-office/tests/_data/list/heading1/model.word2016.html

@@ -1,2 +1,2 @@
-<listItem listIndent="0" listType="numbered">H1 1</listItem>
-<listItem listIndent="0" listType="numbered">H1 2</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">H1 1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">H1 2</listItem>

+ 2 - 2
packages/ckeditor5-paste-from-office/tests/_data/list/heading3-styled/model.word2016.html

@@ -1,5 +1,5 @@
-<listItem listIndent="0" listType="bulleted">H<$text bold="true">2 1</$text></listItem>
-<listItem listIndent="0" listType="bulleted">
+<listItem listIndent="0" listStyle="disc" listType="bulleted">H<$text bold="true">2 1</$text></listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">
 	<$text italic="true" underline="true">H</$text>
 	<$text underline="true">2</$text> 2
 </listItem>

+ 1 - 1
packages/ckeditor5-paste-from-office/tests/_data/list/heading3-styled/normalized.safari.word2016.html

@@ -1,4 +1,4 @@
-<ul>
+<ul style="list-style-type:disc">
 	<li style="margin-left:36.0pt;text-indent:-18.0pt;mso-list:l0 level1 lfo1">
 		<!--[if !supportLists]--><span style="font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family:
 	Symbol;mso-ansi-language:PL"></span><!--[endif]-->

+ 1 - 1
packages/ckeditor5-paste-from-office/tests/_data/list/heading3-styled/normalized.word2016.html

@@ -1,4 +1,4 @@
-<ul>
+<ul style="list-style-type:disc">
 	<li style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:l0 level1 lfo1'>
 		<![if !supportLists]>
 		<span lang=PL style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family:Symbol;mso-ansi-language:PL'></span>

+ 1 - 1
packages/ckeditor5-paste-from-office/tests/_data/list/heading7/model.word2016.html

@@ -1 +1 @@
-<listItem listIndent="0" listType="numbered">H 7</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">H 7</listItem>

+ 12 - 12
packages/ckeditor5-paste-from-office/tests/_data/list/many-one-item/model.word2016.html

@@ -1,21 +1,21 @@
-<listItem listIndent="0" listType="numbered">A</listItem>
-<listItem listIndent="0" listType="numbered">B</listItem>
-<listItem listIndent="0" listType="numbered">C</listItem>
-<listItem listIndent="0" listType="numbered">D</listItem>
-<listItem listIndent="0" listType="numbered">E</listItem>
-<listItem listIndent="0" listType="numbered">F</listItem>
-<listItem listIndent="0" listType="numbered">G</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">A</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">B</listItem>
+<listItem listIndent="0" listStyle="upper-roman" listType="numbered">C</listItem>
+<listItem listIndent="0" listStyle="upper-alpha" listType="numbered">D</listItem>
+<listItem listIndent="0" listStyle="lower-alpha" listType="numbered">E</listItem>
+<listItem listIndent="0" listStyle="lower-alpha" listType="numbered">F</listItem>
+<listItem listIndent="0" listStyle="lower-roman" listType="numbered">G</listItem>
 
 <paragraph></paragraph>
 
-<listItem listIndent="0" listType="bulleted">H</listItem>
-<listItem listIndent="0" listType="bulleted">I</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">H</listItem>
+<listItem listIndent="0" listStyle="circle" listType="bulleted">I</listItem>
 
 <paragraph></paragraph>
 
-<listItem listIndent="0" listType="bulleted">J</listItem>
-<listItem listIndent="0" listType="bulleted">k</listItem>
+<listItem listIndent="0" listStyle="square" listType="bulleted">J</listItem>
+<listItem listIndent="0" listStyle="default" listType="bulleted">k</listItem>
 
 <paragraph></paragraph>
 
-<listItem listIndent="0" listType="numbered">h1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">h1</listItem>

+ 8 - 8
packages/ckeditor5-paste-from-office/tests/_data/list/many-one-item/normalized.safari.word2016.html

@@ -17,7 +17,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:upper-roman">
 	<li class="MsoListParagraphCxSpMiddle" style="text-indent:-36.0pt;mso-text-indent-alt:
 	-18.0pt;mso-list:l7 level1 lfo3">
 		<!--[if !supportLists]--><span style="mso-fareast-font-family:
@@ -27,7 +27,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:upper-alpha">
 	<li class="MsoListParagraphCxSpMiddle" style="text-indent:-18.0pt;mso-list:l10 level1 lfo4">
 		<!--[if !supportLists]--><span style="mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;
 	mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:
@@ -36,7 +36,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:lower-alpha">
 	<li class="MsoListParagraphCxSpMiddle" style="text-indent:-18.0pt;mso-list:l9 level1 lfo5">
 		<!--[if !supportLists]--><span style="mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;
 	mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:
@@ -45,7 +45,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:lower-alpha">
 	<li class="MsoListParagraphCxSpMiddle" style="text-indent:-18.0pt;mso-list:l4 level1 lfo6">
 		<!--[if !supportLists]--><span style="mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;
 	mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:
@@ -54,7 +54,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:lower-roman">
 	<li class="MsoListParagraphCxSpLast" style="text-indent:-36.0pt;mso-text-indent-alt:
 	-18.0pt;mso-list:l6 level1 lfo7">
 		<!--[if !supportLists]--><span style="mso-fareast-font-family:
@@ -66,7 +66,7 @@
 
 <p class="MsoNormal" style="margin: 0cm 0cm 8pt; line-height: 15.693333625793457px; font-size: 11pt; font-family: Calibri, sans-serif; caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;"><span></span></p>
 
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraphCxSpFirst" style="text-indent:-18.0pt;mso-list:l1 level1 lfo8">
 		<!--[if !supportLists]--><span style="font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family:
 	Symbol;mso-ansi-language:PL"></span><!--[endif]-->
@@ -74,7 +74,7 @@
 	</li>
 </ul>
 
-<ul>
+<ul style="list-style-type:circle">
 	<li class="MsoListParagraphCxSpLast" style="text-indent:-18.0pt;mso-list:l3 level1 lfo9">
 		<!--[if !supportLists]--><span style="font-family:&quot;Courier New&quot;;mso-fareast-font-family:&quot;Courier New&quot;;
 	mso-ansi-language:PL"></span><!--[endif]-->
@@ -84,7 +84,7 @@
 
 <p class="MsoNormal" style="margin: 0cm 0cm 8pt; line-height: 15.693333625793457px; font-size: 11pt; font-family: Calibri, sans-serif; caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;"><span></span></p>
 
-<ul>
+<ul style="list-style-type:square">
 	<li class="MsoListParagraphCxSpFirst" style="text-indent:-18.0pt;mso-list:l8 level1 lfo10">
 		<!--[if !supportLists]--><span style="font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:
 	Wingdings;mso-ansi-language:PL"></span><!--[endif]-->

+ 8 - 8
packages/ckeditor5-paste-from-office/tests/_data/list/many-one-item/normalized.word2016.html

@@ -16,7 +16,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:upper-roman">
 	<li class=MsoListParagraphCxSpMiddle style='text-indent:-36.0pt;mso-text-indent-alt:-18.0pt;mso-list:l7 level1 lfo3'>
 		<![if !supportLists]>
 		<span lang=PL style='mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:PL'></span>
@@ -25,7 +25,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:upper-alpha">
 	<li class=MsoListParagraphCxSpMiddle style='text-indent:-18.0pt;mso-list:l10 level1 lfo4'>
 		<![if !supportLists]>
 		<span lang=PL style='mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:PL'></span>
@@ -34,7 +34,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:lower-alpha">
 	<li class=MsoListParagraphCxSpMiddle style='text-indent:-18.0pt;mso-list:l9 level1 lfo5'>
 		<![if !supportLists]>
 		<span lang=PL style='mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:PL'></span>
@@ -43,7 +43,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:lower-alpha">
 	<li class=MsoListParagraphCxSpMiddle style='text-indent:-18.0pt;mso-list:l4 level1 lfo6'>
 		<![if !supportLists]>
 		<span lang=PL style='mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:PL'></span>
@@ -52,7 +52,7 @@
 	</li>
 </ol>
 
-<ol>
+<ol style="list-style-type:lower-roman">
 	<li class=MsoListParagraphCxSpLast style='text-indent:-36.0pt;mso-text-indent-alt:-18.0pt;mso-list:l6 level1 lfo7'>
 		<![if !supportLists]>
 		<span lang=PL style='mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin;mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-ansi-language:PL'></span>
@@ -63,7 +63,7 @@
 
 <p class=MsoNormal><span lang=PL style='mso-ansi-language:PL'></span></p>
 
-<ul>
+<ul style="list-style-type:disc">
 	<li class=MsoListParagraphCxSpFirst style='text-indent:-18.0pt;mso-list:l1 level1 lfo8'>
 		<![if !supportLists]>
 		<span lang=PL style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family:Symbol;mso-ansi-language:PL'></span>
@@ -72,7 +72,7 @@
 	</li>
 </ul>
 
-<ul>
+<ul style="list-style-type:circle">
 	<li class=MsoListParagraphCxSpLast style='text-indent:-18.0pt;mso-list:l3 level1 lfo9'>
 		<![if !supportLists]>
 		<span lang=PL style='font-family:"Courier New";mso-fareast-font-family:"Courier New";mso-ansi-language:PL'></span>
@@ -83,7 +83,7 @@
 
 <p class=MsoNormal><span lang=PL style='mso-ansi-language:PL'></span></p>
 
-<ul>
+<ul style="list-style-type:square">
 	<li class=MsoListParagraphCxSpFirst style='text-indent:-18.0pt;mso-list:l8 level1 lfo10'>
 		<![if !supportLists]>
 		<span lang=PL style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings;mso-ansi-language:PL'></span>

+ 4 - 4
packages/ckeditor5-paste-from-office/tests/_data/list/multiple-combined/model.word2016.html

@@ -1,4 +1,4 @@
-<listItem listIndent="0" listType="numbered">Item1</listItem>
-<listItem listIndent="0" listType="numbered">Item 2</listItem>
-<listItem listIndent="0" listType="numbered">Item 1</listItem>
-<listItem listIndent="0" listType="numbered">Item2</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">Item1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">Item 2</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">Item 1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">Item2</listItem>

+ 3 - 3
packages/ckeditor5-paste-from-office/tests/_data/list/multiple/model.word2016.html

@@ -1,6 +1,6 @@
-<listItem listIndent="0" listType="numbered">Item1</listItem>
-<listItem listIndent="0" listType="numbered">Item 2</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">Item1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">Item 2</listItem>
 
 <paragraph>Some text</paragraph>
 
-<listItem listIndent="0" listType="bulleted">Bullet 1</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Bullet 1</listItem>

+ 1 - 1
packages/ckeditor5-paste-from-office/tests/_data/list/multiple/normalized.safari.word2016.html

@@ -16,7 +16,7 @@
 
 <p class="MsoNormal" style="margin: 0cm 0cm 8pt; line-height: 15.693333625793457px; font-size: 11pt; font-family: Calibri, sans-serif; caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>Some text<o:p></o:p></span></p>
 
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraph" style="text-indent:-18.0pt;mso-list:l0 level1 lfo2">
 		<!--[if !supportLists]--><span style="font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family:
 	Symbol;mso-ansi-language:PL"></span><!--[endif]-->

+ 1 - 1
packages/ckeditor5-paste-from-office/tests/_data/list/multiple/normalized.word2016.html

@@ -16,7 +16,7 @@
 
 <p class=MsoNormal><span lang=PL style='mso-ansi-language:PL'>Some text<o:p></o:p></span></p>
 
-<ul>
+<ul style="list-style-type:disc">
 	<li class=MsoListParagraph style='text-indent:-18.0pt;mso-list:l0 level1 lfo2'>
 		<![if !supportLists]>
 		<span lang=PL style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family:Symbol;mso-ansi-language:PL'></span>

+ 6 - 6
packages/ckeditor5-paste-from-office/tests/_data/list/nested-mixed/model.word2016.html

@@ -1,6 +1,6 @@
-<listItem listIndent="0" listType="bulleted">A1</listItem>
-<listItem listIndent="1" listType="numbered">B2</listItem>
-<listItem listIndent="2" listType="numbered">C4</listItem>
-<listItem listIndent="2" listType="numbered">D3</listItem>
-<listItem listIndent="0" listType="bulleted">E1</listItem>
-<listItem listIndent="1" listType="numbered">F2</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">A1</listItem>
+<listItem listIndent="1" listStyle="default" listType="numbered">B2</listItem>
+<listItem listIndent="2" listStyle="disc" listType="numbered">C4</listItem>
+<listItem listIndent="2" listStyle="disc" listType="numbered">D3</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">E1</listItem>
+<listItem listIndent="1" listStyle="default" listType="numbered">F2</listItem>

+ 2 - 2
packages/ckeditor5-paste-from-office/tests/_data/list/nested-mixed/normalized.word2016.html

@@ -1,10 +1,10 @@
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraphCxSpFirst" style="mso-list:l0 level1 lfo1;text-indent:-18.0pt">
 		<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>A1<o:p></o:p>
 		<ol>
 			<li class="MsoListParagraphCxSpMiddle" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
 				<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>B2<o:p></o:p>
-				<ol>
+				<ol style="list-style-type:disc">
 					
 					<li class="MsoListParagraphCxSpMiddle" style="margin-left:144.0pt;mso-add-space:auto;mso-list:l0 level4 lfo1;text-indent:-18.0pt">
 						<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>C4<o:p></o:p>

+ 9 - 9
packages/ckeditor5-paste-from-office/tests/_data/list/nested-multiple/model.word2016.html

@@ -1,17 +1,17 @@
-<listItem listIndent="0" listType="bulleted">A1</listItem>
-<listItem listIndent="1" listType="numbered">B3</listItem>
-<listItem listIndent="1" listType="numbered">C2</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">A1</listItem>
+<listItem listIndent="1" listStyle="square" listType="numbered">B3</listItem>
+<listItem listIndent="1" listStyle="square" listType="numbered">C2</listItem>
 
 <paragraph></paragraph>
 <paragraph>Foo Bar...</paragraph>
 <paragraph></paragraph>
 
-<listItem listIndent="0" listType="numbered">A2</listItem>
-<listItem listIndent="0" listType="numbered">B1</listItem>
-<listItem listIndent="1" listType="numbered">C2</listItem>
+<listItem listIndent="0" listStyle="lower-alpha" listType="numbered">A2</listItem>
+<listItem listIndent="0" listStyle="lower-alpha" listType="numbered">B1</listItem>
+<listItem listIndent="1" listStyle="lower-alpha" listType="numbered">C2</listItem>
 
 <paragraph></paragraph>
 
-<listItem listIndent="0" listType="bulleted">A1</listItem>
-<listItem listIndent="1" listType="numbered">B2</listItem>
-<listItem listIndent="0" listType="bulleted">C1</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">A1</listItem>
+<listItem listIndent="1" listStyle="default" listType="numbered">B2</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">C1</listItem>

+ 6 - 6
packages/ckeditor5-paste-from-office/tests/_data/list/nested-multiple/normalized.safari.word2016.html

@@ -1,7 +1,7 @@
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraphCxSpFirst" style="mso-list:l1 level1 lfo2;text-indent:-18.0pt">
 		<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>A1<o:p></o:p>
-		<ol>
+		<ol style="list-style-type:square">
 			
           <li class="MsoListParagraphCxSpMiddle" style="margin-left:108.0pt;mso-add-space:auto;mso-list:l1 level3 lfo2;text-indent:-18.0pt">
             <span style="font-family:Wingdings;mso-bidi-font-family:Wingdings;mso-fareast-font-family:Wingdings"></span>B3<o:p></o:p>
@@ -18,14 +18,14 @@
 <p class="MsoNormal" style="-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px;caret-color:rgb(0, 0, 0);color:rgb(0, 0, 0);font-family:Calibri, sans-serif;font-size:medium;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;margin:0cm 0cm 0.0001pt;orphans:auto;text-align:start;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;widows:auto;word-spacing:0px">Foo Bar...<o:p></o:p></p>
 <p class="MsoNormal" style="-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px;caret-color:rgb(0, 0, 0);color:rgb(0, 0, 0);font-family:Calibri, sans-serif;font-size:medium;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;margin:0cm 0cm 0.0001pt;orphans:auto;text-align:start;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;widows:auto;word-spacing:0px"></p>
 
-<ol>
+<ol style="list-style-type:lower-alpha">
   <li class="MsoListParagraphCxSpFirst" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
     <span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>A2<o:p></o:p>
   </li>
 
     <li class="MsoListParagraphCxSpMiddle" style="mso-list:l0 level1 lfo1;text-indent:-18.0pt">
 		<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>B1<o:p></o:p>
-		<ol>
+		<ol style="list-style-type:lower-alpha">
 			<li class="MsoListParagraphCxSpLast" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
 				<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>C2<o:p></o:p>
 			</li>
@@ -35,7 +35,7 @@
 
 <p class="MsoNormal" style="-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px;caret-color:rgb(0, 0, 0);color:rgb(0, 0, 0);font-family:Calibri, sans-serif;font-size:medium;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;margin:0cm 0cm 0.0001pt;orphans:auto;text-align:start;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;widows:auto;word-spacing:0px"></p>
 
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraphCxSpFirst" style="mso-list:l1 level1 lfo2;text-indent:-18.0pt">
 		<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>A1<o:p></o:p>
 		<ol>
@@ -48,4 +48,4 @@
 	<li class="MsoListParagraphCxSpLast" style="mso-list:l1 level1 lfo2;text-indent:-18.0pt">
 		<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>C1<o:p></o:p>
 	</li>
-</ul>
+</ol>

+ 5 - 5
packages/ckeditor5-paste-from-office/tests/_data/list/nested-multiple/normalized.word2016.html

@@ -1,7 +1,7 @@
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraphCxSpFirst" style="mso-list:l1 level1 lfo2;text-indent:-18.0pt">
 		<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>A1<o:p></o:p>
-		<ol>
+		<ol style="list-style-type:square">
           <li class="MsoListParagraphCxSpMiddle" style="margin-left:108.0pt;mso-add-space:auto;mso-list:l1 level3 lfo2;text-indent:-18.0pt">
             <span style="font-family:Wingdings;mso-bidi-font-family:Wingdings;mso-fareast-font-family:Wingdings"></span>B3<o:p></o:p>
           </li>
@@ -17,14 +17,14 @@
 <p class="MsoNormal">Foo Bar...<o:p></o:p></p>
 <p class="MsoNormal"></p>
 
-<ol>
+<ol style="list-style-type:lower-alpha">
   <li class="MsoListParagraphCxSpFirst" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
     <span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>A2<o:p></o:p>
   </li>
 
     <li class="MsoListParagraphCxSpMiddle" style="mso-list:l0 level1 lfo1;text-indent:-18.0pt">
 		<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>B1<o:p></o:p>
-		<ol>
+		<ol style="list-style-type:lower-alpha">
 			<li class="MsoListParagraphCxSpLast" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
 				<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>C2<o:p></o:p>
 			</li>
@@ -34,7 +34,7 @@
 
 <p class="MsoNormal"></p>
 
-<ul>
+<ul style="list-style-type:disc">
 	<li class="MsoListParagraphCxSpFirst" style="mso-list:l1 level1 lfo2;text-indent:-18.0pt">
 		<span style="font-family:Symbol;mso-bidi-font-family:Symbol;mso-fareast-font-family:Symbol"></span>A1<o:p></o:p>
 		<ol>

+ 7 - 7
packages/ckeditor5-paste-from-office/tests/_data/list/nested/model.word2016.html

@@ -1,7 +1,7 @@
-<listItem listIndent="0" listType="numbered">A1</listItem>
-<listItem listIndent="0" listType="numbered">B1</listItem>
-<listItem listIndent="1" listType="numbered">C2</listItem>
-<listItem listIndent="2" listType="numbered">D4</listItem>
-<listItem listIndent="1" listType="numbered">E2</listItem>
-<listItem listIndent="2" listType="numbered">F3</listItem>
-<listItem listIndent="0" listType="numbered">G1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">A1</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">B1</listItem>
+<listItem listIndent="1" listStyle="lower-alpha" listType="numbered">C2</listItem>
+<listItem listIndent="2" listStyle="default" listType="numbered">D4</listItem>
+<listItem listIndent="1" listStyle="lower-alpha" listType="numbered">E2</listItem>
+<listItem listIndent="2" listStyle="lower-roman" listType="numbered">F3</listItem>
+<listItem listIndent="0" listStyle="default" listType="numbered">G1</listItem>

+ 2 - 2
packages/ckeditor5-paste-from-office/tests/_data/list/nested/normalized.word2016.html

@@ -5,7 +5,7 @@
 
 	<li class="MsoListParagraphCxSpMiddle" style="mso-list:l0 level1 lfo1;text-indent:-18.0pt">
 		<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>B1<o:p></o:p>
-		<ol>
+		<ol style="list-style-type:lower-alpha">
 			<li class="MsoListParagraphCxSpMiddle" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
 				<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>C2<o:p></o:p>
 				<ol>
@@ -17,7 +17,7 @@
 
 			<li class="MsoListParagraphCxSpMiddle" style="margin-left:72.0pt;mso-add-space:auto;mso-list:l0 level2 lfo1;text-indent:-18.0pt">
 				<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>E2<o:p></o:p>
-				<ol>
+				<ol style="list-style-type:lower-roman">
 					<li class="MsoListParagraphCxSpMiddle" style="margin-left:108.0pt;mso-add-space:auto;mso-list:l0 level3 lfo1;mso-text-indent-alt:-9.0pt;text-indent:-108.0pt">
 						<span style="mso-bidi-font-family:Calibri;mso-bidi-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-latin"></span>F3<o:p></o:p>
 					</li>

+ 12 - 12
packages/ckeditor5-paste-from-office/tests/_data/list/resume-template/model.safari.word2016.html

@@ -10,40 +10,40 @@
 
 <heading1>DEGREE | DATE EARNED | SCHOOL</heading1>
 
-<listItem listIndent="0" listType="bulleted">Major: Click here to enter text</listItem>
-<listItem listIndent="0" listType="bulleted">Minor: Click here to enter text</listItem>
-<listItem listIndent="0" listType="bulleted">Related coursework: Click here to enter text</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Major: Click here to enter text</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Minor: Click here to enter text</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Related coursework: Click here to enter text</listItem>
 
 <heading1>DEGREE | DATE EARNED | SCHOOL</heading1>
 
-<listItem listIndent="0" listType="bulleted">Major: Click here to enter text</listItem>
-<listItem listIndent="0" listType="bulleted">Minor: Click here to enter text</listItem>
-<listItem listIndent="0" listType="bulleted">Related coursework: Click here to enter text</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Major: Click here to enter text</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Minor: Click here to enter text</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Related coursework: Click here to enter text</listItem>
 
 <heading1>Skills & Abilities</heading1>
 
 <heading1>MANAGEMENT</heading1>
 
-<listItem listIndent="0" listType="bulleted">Think a document that looks this good has to be difficult to format? Think again! To easily apply any text formatting you see in this document with just a click, on the Home tab of the ribbon, check out Styles.</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Think a document that looks this good has to be difficult to format? Think again! To easily apply any text formatting you see in this document with just a click, on the Home tab of the ribbon, check out Styles.</listItem>
 
 <heading1>SALES</heading1>
 
-<listItem listIndent="0" listType="bulleted">Some of the sample text in this document indicates the name of the style applied, so that you can easily apply the same formatting again. For example, this is the List Bullet style.</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Some of the sample text in this document indicates the name of the style applied, so that you can easily apply the same formatting again. For example, this is the List Bullet style.</listItem>
 
 <heading1>COMMUNICATION</heading1>
 
-<listItem listIndent="0" listType="bulleted">You delivered that big presentation to rave reviews. Don’t be shy about it now! This is the place to show how well you work and play with others.</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">You delivered that big presentation to rave reviews. Don’t be shy about it now! This is the place to show how well you work and play with others.</listItem>
 
 <heading1>LEADERSHIP</heading1>
 
-<listItem listIndent="0" listType="bulleted">Are you president of your fraternity, head of the condo board, or a team lead for your favorite charity? You’re a natural leader—tell it like it is!</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">Are you president of your fraternity, head of the condo board, or a team lead for your favorite charity? You’re a natural leader—tell it like it is!</listItem>
 
 <heading1>Experience</heading1>
 
 <heading1>JOB TITLE | COMPANY | DATES FROM - TO</heading1>
 
-<listItem listIndent="0" listType="bulleted">This is the place for a brief summary of your key responsibilities and most stellar accomplishments.</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">This is the place for a brief summary of your key responsibilities and most stellar accomplishments.</listItem>
 
 <heading1>JOB TITLE | COMPANY | DATES FROM - TO</heading1>
 
-<listItem listIndent="0" listType="bulleted">This is the place for a brief summary of your key responsibilities and most stellar accomplishments.</listItem>
+<listItem listIndent="0" listStyle="disc" listType="bulleted">This is the place for a brief summary of your key responsibilities and most stellar accomplishments.</listItem>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio