8
0
Просмотр исходного кода

Merge pull request #8354 from ckeditor/i/8160

Fix (list): List styles will be inherited correctly when pasting a list into another list. Closes #8160.
Maciej 5 лет назад
Родитель
Сommit
5dd14a38a4

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

@@ -490,43 +490,100 @@ function fixListStyleAttributeOnListItemElements( editor ) {
 				} else {
 				} else {
 					writer.setAttribute( 'listStyle', DEFAULT_LIST_TYPE, item );
 					writer.setAttribute( 'listStyle', DEFAULT_LIST_TYPE, item );
 				}
 				}
-
 				wasFixed = true;
 				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;
 		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.
 // Removes the `listStyle` attribute from "todo" list items.

+ 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', () => {
 		it( 'should return the value of `listStyle` attribute for the selection inside a nested list', () => {
 			setData( model,
 			setData( model,
 				'<listItem listIndent="0" listType="bulleted" listStyle="square">1.</listItem>' +
 				'<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>'
 				'<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)', () => {
 		it( 'should return the value of `listStyle` attribute from a list where the selection starts (selection over nested list)', () => {
 			setData( model,
 			setData( model,
 				'<listItem listIndent="0" listType="bulleted" listStyle="square">1.</listItem>' +
 				'<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>'
 				'<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
  * 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 VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
 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 getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 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() {}
+				};
+			}
+		} );
 	} );
 	} );
 } );
 } );