瀏覽代碼

Added EmptyElement support to view.Writer.

Szymon Kupś 9 年之前
父節點
當前提交
5379b7fc85

+ 21 - 10
packages/ckeditor5-engine/src/view/writer.js

@@ -6,6 +6,7 @@
 import Position from './position.js';
 import ContainerElement from './containerelement.js';
 import AttributeElement from './attributeelement.js';
+import EmptyElement from './emptyelement.js';
 import Text from './text.js';
 import Range from './range.js';
 import CKEditorError from '../../utils/ckeditorerror.js';
@@ -137,8 +138,8 @@ export function breakContainer( position ) {
  * In following examples `<p>` is a container and `<b>` is an attribute element:
  *
  *		<p>foo[]bar</p> -> <p>foo{}bar</p>
- *		<p><b>foo</b>[]<b>bar</b> -> <p><b>foo{}bar</b></b>
- *		<p><b foo="bar">a</b>[]<b foo="baz">b</b> -> <p><b foo="bar">a</b>[]<b foo="baz">b</b>
+ *		<p><b>foo</b>[]<b>bar</b></p> -> <p><b>foo{}bar</b></p>
+ *		<p><b foo="bar">a</b>[]<b foo="baz">b</b></p> -> <p><b foo="bar">a</b>[]<b foo="baz">b</b></p>
  *
  * It will also take care about empty attributes when merging:
  *
@@ -188,6 +189,11 @@ export function mergeAttributes( position ) {
 		return position;
 	}
 
+	// When one or both nodes are EmptyElements - no attributes to merge.
+	if ( ( nodeBefore instanceof EmptyElement ) || ( nodeAfter instanceof EmptyElement ) ) {
+		return position;
+	}
+
 	// When position is between two text nodes.
 	if ( nodeBefore instanceof Text && nodeAfter instanceof Text ) {
 		return mergeTextNodes( nodeBefore, nodeAfter );
@@ -256,20 +262,20 @@ export function mergeContainers( position ) {
  *
  * Throws {@link utils.CKEditorError CKEditorError} `view-writer-insert-invalid-node` when nodes to insert
  * contains instances that are not {@link engine.view.Text Texts},
- * {@link engine.view.AttributeElement AttributeElements} or
- * {@link engine.view.ContainerElement ContainerElements}.
+ * {@link engine.view.AttributeElement AttributeElements},
+ * {@link engine.view.ContainerElement ContainerElements} or {@link engine.view.EmptyElement EmptyElements}.
  *
  * @function engine.view.writer.insert
  * @param {engine.view.Position} position Insertion position.
- * @param {engine.view.Text|engine.view.AttributeElement|engine.view.ContainerElement
- * |Iterable.<engine.view.Text|engine.view.AttributeElement|engine.view.ContainerElement>} nodes Node or
+ * @param {engine.view.Text|engine.view.AttributeElement|engine.view.ContainerElement|engine.view.EmptyElement
+ * |Iterable.<engine.view.Text|engine.view.AttributeElement|engine.view.ContainerElement|engine.view.EmptyElement>} nodes Node or
  * nodes to insert.
  * @returns {engine.view.Range} Range around inserted nodes.
  */
 export function insert( position, nodes ) {
 	nodes = isIterable( nodes ) ? [ ...nodes ] : [ nodes ];
 
-	// Check if nodes to insert are instances of AttributeElements, ContainerElements or Text.
+	// Check if nodes to insert are instances of AttributeElements, ContainerElements, EmptyElements or Text.
 	validateNodesToInsert( nodes );
 
 	const container = getParentContainer( position );
@@ -639,6 +645,11 @@ function _breakAttributes( position, forceSplitText = false ) {
 	const positionOffset = position.offset;
 	const positionParent = position.parent;
 
+	// If position is placed inside EmptyElement - return same position, cannot break EmptyElement.
+	if ( positionParent instanceof EmptyElement ) {
+		return Position.createFromPosition( position );
+	}
+
 	// There are no attributes to break and text nodes breaking is not forced.
 	if ( !forceSplitText && positionParent instanceof Text && isContainerOrFragment( positionParent.parent ) ) {
 		return Position.createFromPosition( position );
@@ -1025,8 +1036,8 @@ function rangeSpansOnAllChildren( range ) {
 }
 
 // Checks if provided nodes are valid to insert. Checks if each node is an instance of
-// {@link engine.view.Text Text} or {@link engine.view.AttributeElement AttributeElement} or
-// {@link engine.view.ContainerElement ContainerElement}.
+// {@link engine.view.Text Text} or {@link engine.view.AttributeElement AttributeElement},
+// {@link engine.view.ContainerElement ContainerElement} or {@link engine.view.EmptyElement EmptyElement}.
 //
 // Throws {@link utils.CKEditorError CKEditorError} `view-writer-insert-invalid-node` when nodes to insert
 // contains instances that are not {@link engine.view.Text Texts},
@@ -1036,7 +1047,7 @@ function rangeSpansOnAllChildren( range ) {
 // @param Iterable.<engine.view.Text|engine.view.AttributeElement|engine.view.ContainerElement> nodes
 function validateNodesToInsert( nodes ) {
 	for ( let node of nodes ) {
-		if ( !( node instanceof Text || node instanceof AttributeElement || node instanceof ContainerElement ) ) {
+		if ( !( node instanceof Text || node instanceof AttributeElement || node instanceof ContainerElement || node instanceof EmptyElement ) ) {
 			/**
 			 * Inserted nodes should be instance of {@link engine.view.AttributeElement AttributeElement},
 			 * {@link engine.view.ContainerElement ContainerElement} or {@link engine.view.Text Text}.

+ 14 - 0
packages/ckeditor5-engine/tests/view/writer/breakAttributes.js

@@ -9,7 +9,9 @@ import { breakAttributes } from 'ckeditor5/engine/view/writer.js';
 import { stringify, parse } from 'ckeditor5/engine/dev-utils/view.js';
 import ContainerElement from 'ckeditor5/engine/view/containerelement.js';
 import AttributeElement from 'ckeditor5/engine/view/attributeelement.js';
+import EmptyElement from 'ckeditor5/engine/view/emptyelement.js';
 import Range from 'ckeditor5/engine/view/range.js';
+import Position from 'ckeditor5/engine/view/position.js';
 import CKEditorError from 'ckeditor5/utils/ckeditorerror.js';
 
 describe( 'writer', () => {
@@ -227,6 +229,18 @@ describe( 'writer', () => {
 					'foo{}bar'
 				);
 			} );
+
+			it( 'should not break EmptyElements', () => {
+				const img = new EmptyElement( 'img' );
+				const container = new ContainerElement( 'p', null, [ img ] );
+				const position = new Position( img, 0 );
+
+				const newPosition = breakAttributes( position );
+				expect( stringify( container, newPosition, {
+					showType: true,
+					showPriority: true
+				} ) ).to.equal( '<container:p><empty:img>[]</empty:img></container:p>' );
+			} );
 		} );
 	} );
 } );

+ 8 - 0
packages/ckeditor5-engine/tests/view/writer/insert.js

@@ -175,5 +175,13 @@ describe( 'writer', () => {
 				insert( position, attributeElement );
 			} ).to.throw( CKEditorError, 'view-writer-invalid-position-container' );
 		} );
+
+		it( 'should allow to insert EmptyElement into container', () => {
+			test(
+				'<container:p>[]</container:p>',
+				[ '<empty:img></empty:img>' ],
+				'<container:p>[<empty:img></empty:img>]</container:p>'
+			);
+		} );
 	} );
 } );

+ 7 - 0
packages/ckeditor5-engine/tests/view/writer/mergeAttributes.js

@@ -129,5 +129,12 @@ describe( 'writer', () => {
 				'<container:p>[]</container:p>'
 			);
 		} );
+
+		it( 'should not merge when placed between EmptyElements', () => {
+			test(
+				'<container:p><empty:img></empty:img>[]<empty:img></empty:img></container:p>',
+				'<container:p><empty:img></empty:img>[]<empty:img></empty:img></container:p>'
+			);
+		} );
 	} );
 } );