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

Handle block elements inside list item during view-model conversion.

Krzysztof Krztoń 7 лет назад
Родитель
Сommit
7a7e7cc18f
1 измененных файлов с 80 добавлено и 16 удалено
  1. 80 16
      packages/ckeditor5-list/src/converters.js

+ 80 - 16
packages/ckeditor5-list/src/converters.js

@@ -371,22 +371,7 @@ export function viewModelConverter( evt, data, conversionApi ) {
 
 
 		writer.insert( listItem, splitResult.position );
 		writer.insert( listItem, splitResult.position );
 
 
-		// Remember position after list item, next list items will be inserted at this position.
-		let nextPosition = ModelPosition.createAfter( listItem );
-
-		// Check all children of the converted `<li>`.
-		// At this point we assume there are no "whitespace" view text nodes in view list, between view list items.
-		// This should be handled by `<ul>` and `<ol>` converters.
-		for ( const child of data.viewItem.getChildren() ) {
-			// If this is a view list element, we will convert it after last `listItem` model element.
-			if ( child.name == 'ul' || child.name == 'ol' ) {
-				nextPosition = conversionApi.convertItem( child, nextPosition ).modelCursor;
-			}
-			// If it was not a list it was a "regular" list item content. Just convert it to `listItem`.
-			else {
-				conversionApi.convertItem( child, ModelPosition.createAt( listItem, 'end' ) );
-			}
-		}
+		const nextPosition = viewToModelListItemChildrenConverter( listItem, data.viewItem.getChildren(), conversionApi, writer );
 
 
 		conversionStore.indent--;
 		conversionStore.indent--;
 
 
@@ -814,6 +799,85 @@ function generateLiInUl( modelItem, conversionApi ) {
 	return viewItem;
 	return viewItem;
 }
 }
 
 
+// Helper function that converts children of a given `<li>` view element into corresponding model elements.
+// The function maintains proper order of elements if model `listItem` is split during the conversion
+// due to block children conversion.
+//
+// @param {module:engine/model/element~Element} listItemModel List item model element to which converted children will be inserted.
+// @param {Iterable.<module:engine/view/node~Node>} viewChildren View elements which will be converted.
+// @param {Object} conversionApi Conversion interface to be used by the callback.
+// @param {module:engine/model/writer~Writer} writer Writer used to manipulate model elements.
+// @returns {module:engine/model/position~Position} Position on which next elements should be inserted after children conversion.
+function viewToModelListItemChildrenConverter( listItemModel, viewChildren, conversionApi, writer ) {
+	let lastListItem = listItemModel;
+	let nextPosition = ModelPosition.createAfter( lastListItem );
+
+	// Check all children of the converted `<li>`. At this point we assume there are no "whitespace" view text nodes
+	// in view list, between view list items. This should be handled by `<ul>` and `<ol>` converters.
+	for ( const child of viewChildren ) {
+		// If this is a view list element, we will convert it after last `listItem` model element.
+		if ( child.name == 'ul' || child.name == 'ol' ) {
+			nextPosition = conversionApi.convertItem( child, nextPosition ).modelCursor;
+		}
+		// If it was not a list view element it was a "regular" list item content. Convert it to a `listItem`.
+		else {
+			const result = conversionApi.convertItem( child, ModelPosition.createAt( lastListItem, 'end' ) );
+			const convertedChild = Array.from( result.modelRange )[ 0 ];
+
+			// If there is a block element child being converted it will split the current list item, for example:
+			//
+			//		<li><p>Foo</p></li>
+			//
+			// will be converted to:
+			//
+			//		<listItem></listItem><paragraph>Foo</paragraph><listItem></listItem>
+			//
+			// so we need to update reference to `lastListItem` and `nextPosition`.
+			if ( convertedChild.type === 'elementStart' && convertedChild.item.is( 'element' ) ) {
+				nextPosition = result.modelCursor;
+				lastListItem = nextPosition.getAncestors().pop();
+
+				// Depending on the used converter for block elements, usually the position (marked as #)
+				// points to the second list item after conversion:
+				//
+				//		`<li><p>Foo</p></li>` -> `<listItem></listItem><paragraph>Foo</paragraph><listItem>#</listItem>`
+				//
+				// However, in some cases like autoparagraphing the position is placed on the end of the block element:
+				//
+				//		`<li><h2>Foo</h2></li>` -> `<listItem></listItem><paragraph>Foo#</paragraph><listItem></listItem>`
+				//
+				// We need to check fo such cases and use proper list item and position based on it.
+				if ( !lastListItem.is( 'listItem' ) && lastListItem.nextSibling && lastListItem.nextSibling.is( 'listItem' ) ) {
+					lastListItem = lastListItem.nextSibling;
+					nextPosition = ModelPosition.createAt( lastListItem, 'end' );
+				}
+			// If list item was split by the block element, the text from splitted list item needs to be moved.
+			//
+			//		`<li>Foo<p>123</p>Bar</li>`
+			//
+			// Is converted to:
+			//
+			//		<listItem>FooBar</listItem><paragraph>123</paragraph><listItem></listItem>
+			//
+			// So the text which was after splitting element (`<p>` in this case) should be move to second list item:
+			//
+			//		<listItem>Foo</listItem><paragraph>123</paragraph><listItem>Bar</listItem>
+			} else if ( convertedChild.type === 'text' && listItemModel !== lastListItem ) {
+				// 1. Remove text part from the list item positioned before splitting element.
+				writer.remove( ModelRange.createOn( convertedChild.item ) );
+
+				// 2. Insert text part into list item positioned after splitting element.
+				writer.insert( convertedChild.item, nextPosition );
+
+				// 3. Update 'nextPosition' so it points after last list item.
+				nextPosition = ModelPosition.createAfter( lastListItem );
+			}
+		}
+	}
+
+	return nextPosition;
+}
+
 // Helper function that seeks for a list item sibling of given model item (or position) which meets given criteria.
 // Helper function that seeks for a list item sibling of given model item (or position) which meets given criteria.
 // `options` object may contain one or more of given values (by default they are `false`):
 // `options` object may contain one or more of given values (by default they are `false`):
 // `options.sameIndent` - whether sought sibling should have same indent (default = no),
 // `options.sameIndent` - whether sought sibling should have same indent (default = no),