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

Integrated the DowncastWriter with the RawElement.

Aleksander Nowodzinski 5 лет назад
Родитель
Сommit
3ddcc77228

+ 45 - 4
packages/ckeditor5-engine/src/view/downcastwriter.js

@@ -14,6 +14,7 @@ import ContainerElement from './containerelement';
 import AttributeElement from './attributeelement';
 import EmptyElement from './emptyelement';
 import UIElement from './uielement';
+import RawElement from './rawelement';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import DocumentFragment from './documentfragment';
 import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
@@ -272,6 +273,36 @@ export default class DowncastWriter {
 		return uiElement;
 	}
 
+	/**
+	 * Creates a new {@link module:engine/view/rawelement~RawElement}.
+	 *
+	 *		writer.createRawElement( 'span' );
+	 *		writer.createRawElement( 'span', { id: 'foo-1234' } );
+	 *
+	 * Custom render function can be provided as third parameter:
+	 *
+	 *		writer.createRawElement( 'span', null, function( domDocument ) {
+	 *			const domElement = this.toDomElement( domDocument );
+	 *			domElement.innerHTML = '<b>this is ui element</b>';
+	 *
+	 *			return domElement;
+	 *		} );
+	 *
+	 * @param {String} name Name of the element.
+	 * @param {Object} [attributes] Elements attributes.
+	 * @param {Function} [renderFunction] Custom render function.
+	 * @returns {module:engine/view/rawelement~RawElement} Created element.
+	 */
+	createRawElement( name, attributes, renderFunction ) {
+		const rawElement = new RawElement( this.document, name, attributes );
+
+		if ( renderFunction ) {
+			rawElement.render = renderFunction;
+		}
+
+		return rawElement;
+	}
+
 	/**
 	 * Adds or overwrite element's attribute with a specified key and value.
 	 *
@@ -831,7 +862,7 @@ export default class DowncastWriter {
 	 * @param {module:engine/view/range~Range} range Range to wrap.
 	 * @param {module:engine/view/attributeelement~AttributeElement} attribute Attribute element to use as wrapper.
 	 * @returns {module:engine/view/range~Range} range Range after wrapping, spanning over wrapping attribute element.
-	*/
+	 */
 	wrap( range, attribute ) {
 		if ( !( attribute instanceof AttributeElement ) ) {
 			throw new CKEditorError( 'view-writer-wrap-invalid-attribute', this.document );
@@ -1108,6 +1139,7 @@ export default class DowncastWriter {
 			const isAttribute = child.is( 'attributeElement' );
 			const isEmpty = child.is( 'emptyElement' );
 			const isUI = child.is( 'uiElement' );
+			const isRaw = child.is( 'rawElement' );
 
 			//
 			// (In all examples, assume that `wrapElement` is `<span class="foo">` element.)
@@ -1126,8 +1158,7 @@ export default class DowncastWriter {
 			//
 			// <p>abc</p>                   -->  <p><span class="foo">abc</span></p>
 			// <p><strong>abc</strong></p>  -->  <p><span class="foo"><strong>abc</strong></span></p>
-			//
-			else if ( isText || isEmpty || isUI || ( isAttribute && shouldABeOutsideB( wrapElement, child ) ) ) {
+			else if ( isText || isEmpty || isUI || isRaw || ( isAttribute && shouldABeOutsideB( wrapElement, child ) ) ) {
 				// Clone attribute.
 				const newAttribute = wrapElement._clone();
 
@@ -1572,6 +1603,16 @@ export default class DowncastWriter {
 			throw new CKEditorError( 'view-writer-cannot-break-ui-element', this.document );
 		}
 
+		// If position is placed inside RawElement - throw an exception as we cannot break inside.
+		if ( position.parent.is( 'rawElement' ) ) {
+			/**
+			 * Cannot break inside RawElement instance.
+			 *
+			 * @error view-writer-cannot-break-raw-element
+			 */
+			throw new CKEditorError( 'view-writer-cannot-break-raw-element', this.document );
+		}
+
 		// There are no attributes to break and text nodes breaking is not forced.
 		if ( !forceSplitText && positionParent.is( 'text' ) && isContainerOrFragment( positionParent.parent ) ) {
 			return position.clone();
@@ -1872,7 +1913,7 @@ function validateNodesToInsert( nodes, errorContext ) {
 	}
 }
 
-const validNodesToInsert = [ Text, AttributeElement, ContainerElement, EmptyElement, UIElement ];
+const validNodesToInsert = [ Text, AttributeElement, ContainerElement, EmptyElement, RawElement, UIElement ];
 
 // Checks if node is ContainerElement or DocumentFragment, because in most cases they should be treated the same way.
 //

+ 22 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/breakattributes.js

@@ -10,6 +10,7 @@ import ContainerElement from '../../../src/view/containerelement';
 import AttributeElement from '../../../src/view/attributeelement';
 import EmptyElement from '../../../src/view/emptyelement';
 import UIElement from '../../../src/view/uielement';
+import RawElement from '../../../src/view/rawelement';
 import Range from '../../../src/view/range';
 import Position from '../../../src/view/position';
 
@@ -282,6 +283,27 @@ describe( 'DowncastWriter', () => {
 					writer.breakAttributes( range );
 				}, 'view-writer-cannot-break-ui-element', writer );
 			} );
+
+			it( 'should throw if breaking inside a RawElement #1', () => {
+				const span = new RawElement( document, 'span' );
+				new ContainerElement( document, 'p', null, span ); // eslint-disable-line no-new
+				const position = new Position( span, 0 );
+
+				expectToThrowCKEditorError( () => {
+					writer.breakAttributes( position );
+				}, 'view-writer-cannot-break-raw-element', writer );
+			} );
+
+			it( 'should throw if breaking inside a RawElement #2', () => {
+				const span = new RawElement( document, 'span' );
+				const b = new AttributeElement( document, 'b' );
+				new ContainerElement( document, 'p', null, [ span, b ] ); // eslint-disable-line no-new
+				const range = Range._createFromParentsAndOffsets( span, 0, b, 0 );
+
+				expectToThrowCKEditorError( () => {
+					writer.breakAttributes( range );
+				}, 'view-writer-cannot-break-raw-element', writer );
+			} );
 		} );
 	} );
 } );

+ 11 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/clear.js

@@ -10,6 +10,7 @@ import ContainerElement from '../../../src/view/containerelement';
 import AttributeElement from '../../../src/view/attributeelement';
 import EmptyElement from '../../../src/view/emptyelement';
 import UIElement from '../../../src/view/uielement';
+import RawElement from '../../../src/view/rawelement';
 
 import Document from '../../../src/view/document';
 import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
@@ -166,6 +167,16 @@ describe( 'DowncastWriter', () => {
 			);
 		} );
 
+		it( 'should remove a RawElement', () => {
+			const elementToRemove = new RawElement( document, 'span' );
+
+			testDowncast(
+				elementToRemove,
+				'<container:p>f{oo<raw:span></raw:span>ba}r</container:p>',
+				'<container:p>foobar</container:p>'
+			);
+		} );
+
 		it( 'should remove ContainerElement', () => {
 			const elementToRemove = new ContainerElement( document, 'p' );
 

+ 12 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/insert.js

@@ -8,6 +8,7 @@ import ContainerElement from '../../../src/view/containerelement';
 import Element from '../../../src/view/element';
 import EmptyElement from '../../../src/view/emptyelement';
 import UIElement from '../../../src/view/uielement';
+import RawElement from '../../../src/view/rawelement';
 import Position from '../../../src/view/position';
 
 import { stringify, parse } from '../../../src/dev-utils/view';
@@ -215,5 +216,16 @@ describe( 'DowncastWriter', () => {
 				writer.insert( position, attributeElement );
 			}, 'view-writer-cannot-break-ui-element', document );
 		} );
+
+		it( 'should throw if trying to insert inside a RawElement', () => {
+			const rawElement = new RawElement( document, 'span' );
+			new ContainerElement( document, 'p', null, rawElement ); // eslint-disable-line no-new
+			const position = new Position( rawElement, 0 );
+			const attributeElement = new AttributeElement( document, 'i' );
+
+			expectToThrowCKEditorError( () => {
+				writer.insert( position, attributeElement );
+			}, 'view-writer-cannot-break-raw-element', document );
+		} );
 	} );
 } );

+ 7 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/mergeattributes.js

@@ -150,5 +150,12 @@ describe( 'DowncastWriter', () => {
 				'<container:p><ui:span></ui:span>[]<ui:span></ui:span></container:p>'
 			);
 		} );
+
+		it( 'should not merge when placed between RawElements', () => {
+			testMerge(
+				'<container:p><raw:span></raw:span>[]<raw:span></raw:span></container:p>',
+				'<container:p><raw:span></raw:span>[]<raw:span></raw:span></container:p>'
+			);
+		} );
 	} );
 } );

+ 24 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/move.js

@@ -10,6 +10,7 @@ import AttributeElement from '../../../src/view/attributeelement';
 import RootEditableElement from '../../../src/view/rooteditableelement';
 import EmptyElement from '../../../src/view/emptyelement';
 import UIElement from '../../../src/view/uielement';
+import RawElement from '../../../src/view/rawelement';
 import Range from '../../../src/view/range';
 import Position from '../../../src/view/position';
 
@@ -186,6 +187,29 @@ describe( 'DowncastWriter', () => {
 			}, 'view-writer-cannot-break-ui-element', writer );
 		} );
 
+		it( 'should move a RawElement', () => {
+			testMove(
+				'<container:p>foo[<raw:span></raw:span>]bar</container:p>',
+				'<container:div>baz{}quix</container:div>',
+				'<container:p>foobar</container:p>',
+				'<container:div>baz[<raw:span></raw:span>]quix</container:div>'
+			);
+		} );
+
+		it( 'should throw if trying to move to a RawElement', () => {
+			const srcAttribute = new AttributeElement( document, 'b' );
+			const srcContainer = new ContainerElement( document, 'p', null, srcAttribute );
+			const srcRange = Range._createFromParentsAndOffsets( srcContainer, 0, srcContainer, 1 );
+
+			const dstRawElement = new RawElement( document, 'span' );
+			new ContainerElement( document, 'p', null, dstRawElement ); // eslint-disable-line no-new
+			const dstPosition = new Position( dstRawElement, 0 );
+
+			expectToThrowCKEditorError( () => {
+				writer.move( srcRange, dstPosition );
+			}, 'view-writer-cannot-break-raw-element', writer );
+		} );
+
 		it( 'should not break marker mappings if marker element was split and the original element was removed', () => {
 			const mapper = new Mapper();
 

+ 29 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/remove.js

@@ -11,6 +11,7 @@ import { stringify, parse } from '../../../src/dev-utils/view';
 import AttributeElement from '../../../src/view/attributeelement';
 import EmptyElement from '../../../src/view/emptyelement';
 import UIElement from '../../../src/view/uielement';
+import RawElement from '../../../src/view/rawelement';
 
 import Document from '../../../src/view/document';
 import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
@@ -160,6 +161,25 @@ describe( 'DowncastWriter', () => {
 			}, 'view-writer-cannot-break-ui-element', document );
 		} );
 
+		it( 'should remove a RawElement', () => {
+			testRemove(
+				'<container:p>foo[<raw:span></raw:span>]bar</container:p>',
+				'<container:p>foo{}bar</container:p>',
+				'<raw:span></raw:span>'
+			);
+		} );
+
+		it( 'should throw if a range is placed inside a RawElement', () => {
+			const rawElement = new RawElement( document, 'span' );
+			const attributeElement = new AttributeElement( document, 'b' );
+			new ContainerElement( document, 'p', null, [ rawElement, attributeElement ] ); // eslint-disable-line no-new
+			const range = Range._createFromParentsAndOffsets( rawElement, 0, attributeElement, 0 );
+
+			expectToThrowCKEditorError( () => {
+				writer.remove( range );
+			}, 'view-writer-cannot-break-raw-element', document );
+		} );
+
 		it( 'should remove single text node (as item)', () => {
 			testRemove( '<container:p>[foobar]</container:p>', '<container:p></container:p>', 'foobar', true );
 		} );
@@ -201,6 +221,15 @@ describe( 'DowncastWriter', () => {
 			);
 		} );
 
+		it( 'should remove a RawElement (as an item)', () => {
+			testRemove(
+				'<container:p>foo[<raw:span></raw:span>]bar</container:p>',
+				'<container:p>foobar</container:p>',
+				'<raw:span></raw:span>',
+				true
+			);
+		} );
+
 		it( 'should throw when item has no parent container', () => {
 			const el = new AttributeElement( document, 'b' );
 

+ 20 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/unwrap.js

@@ -9,6 +9,7 @@ import ContainerElement from '../../../src/view/containerelement';
 import AttributeElement from '../../../src/view/attributeelement';
 import EmptyElement from '../../../src/view/emptyelement';
 import UIElement from '../../../src/view/uielement';
+import RawElement from '../../../src/view/rawelement';
 import Position from '../../../src/view/position';
 import Range from '../../../src/view/range';
 import Text from '../../../src/view/text';
@@ -426,6 +427,25 @@ describe( 'DowncastWriter', () => {
 			}, 'view-writer-cannot-break-ui-element', document );
 		} );
 
+		it( 'should unwrap a RawElement', () => {
+			testUnwrap(
+				'<container:p>[<attribute:b><raw:span></raw:span></attribute:b>]</container:p>',
+				'<attribute:b></attribute:b>',
+				'<container:p>[<raw:span></raw:span>]</container:p>'
+			);
+		} );
+
+		it( 'should throw if a range is placed inside a RawElement', () => {
+			const rawElement = new RawElement( document, 'span' );
+			const attribute = new AttributeElement( document, 'b' );
+			const container = new ContainerElement( document, 'p', null, [ rawElement, attribute ] );
+			const range = Range._createFromParentsAndOffsets( rawElement, 0, container, 2 );
+
+			expectToThrowCKEditorError( () => {
+				writer.unwrap( range, attribute );
+			}, 'view-writer-cannot-break-raw-element', document );
+		} );
+
 		it( 'should unwrap if both elements have same id', () => {
 			const unwrapper = writer.createAttributeElement( 'span', null, { id: 'foo' } );
 			const attribute = writer.createAttributeElement( 'span', null, { id: 'foo' } );

+ 19 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/wrap.js

@@ -10,6 +10,7 @@ import Element from '../../../src/view/element';
 import ContainerElement from '../../../src/view/containerelement';
 import AttributeElement from '../../../src/view/attributeelement';
 import EmptyElement from '../../../src/view/emptyelement';
+import RawElement from '../../../src/view/rawelement';
 import UIElement from '../../../src/view/uielement';
 import Position from '../../../src/view/position';
 import Range from '../../../src/view/range';
@@ -422,6 +423,24 @@ describe( 'DowncastWriter', () => {
 				}, 'view-writer-cannot-break-ui-element', document );
 			} );
 
+			it( 'should wrap a RawElement', () => {
+				testWrap(
+					'<container:p>[<raw:span></raw:span>]</container:p>',
+					'<attribute:b></attribute:b>',
+					'<container:p>[<attribute:b view-priority="10"><raw:span></raw:span></attribute:b>]</container:p>'
+				);
+			} );
+
+			it( 'should throw if a range is inside a RawElement', () => {
+				const rawElement = new RawElement( document, 'span' );
+				const container = new ContainerElement( document, 'p', null, rawElement );
+				const range = Range._createFromParentsAndOffsets( rawElement, 0, container, 1 );
+
+				expectToThrowCKEditorError( () => {
+					writer.wrap( range, new AttributeElement( document, 'b' ) );
+				}, 'view-writer-cannot-break-raw-element', document );
+			} );
+
 			it( 'should keep stable hierarchy when wrapping with attribute with same priority', () => {
 				testWrap(
 					'<container:p>[<attribute:span>foo</attribute:span>]</container:p>',

+ 20 - 0
packages/ckeditor5-engine/tests/view/downcastwriter/writer.js

@@ -136,6 +136,26 @@ describe( 'DowncastWriter', () => {
 		} );
 	} );
 
+	describe( 'createRawElement()', () => {
+		it( 'should create a RawElement', () => {
+			const element = writer.createRawElement( 'foo', attributes );
+
+			expect( element.is( 'rawElement' ) ).to.be.true;
+			expect( element.name ).to.equal( 'foo' );
+			assertElementAttributes( element, attributes );
+		} );
+
+		it( 'should allow to pass custom rendering method', () => {
+			const renderFn = function() {};
+			const element = writer.createRawElement( 'foo', attributes, renderFn );
+
+			expect( element.is( 'rawElement' ) ).to.be.true;
+			expect( element.name ).to.equal( 'foo' );
+			expect( element.render ).to.equal( renderFn );
+			assertElementAttributes( element, attributes );
+		} );
+	} );
+
 	describe( 'setAttribute()', () => {
 		it( 'should set attribute on given element', () => {
 			const element = writer.createAttributeElement( 'span' );