Bladeren bron

Added single element wrapping improvements to treeView.Writer.wrap.

Szymon Kupś 9 jaren geleden
bovenliggende
commit
b396225b5a

+ 75 - 0
packages/ckeditor5-engine/src/treeview/writer.js

@@ -344,6 +344,27 @@ import DocumentFragment from './documentfragment.js';
 			return range;
 		}
 
+		// Range around one element.
+		if ( range.end.isEqual( range.start.getShiftedBy( 1 ) ) ) {
+			const node = range.start.nodeAfter;
+
+			if ( node instanceof AttributeElement && wrapAttributes( attribute, node ) ) {
+				return range;
+			}
+		}
+
+		// Range is inside single attribute and spans on all children.
+		if ( range.start.parent == range.end.parent && range.end.parent instanceof AttributeElement ) {
+			if ( range.start.offset === 0 && range.end.offset === range.start.parent.getChildCount() ) {
+				if ( wrapAttributes( attribute, range.start.parent ) ) {
+					const parent = range.start.parent.parent;
+					const index = range.start.parent.getIndex();
+
+					return Range.createFromParentsAndOffsets( parent, index, parent, index + 1 ) ;
+				}
+			}
+		}
+
 		// Break attributes at range start and end.
 		const { start: breakStart, end: breakEnd } = this.breakRange( range );
 		const parentContainer = breakStart.parent;
@@ -679,4 +700,58 @@ function mergeTextNodes( t1, t2 ) {
 	t2.remove();
 
 	return new Position( t1, nodeBeforeLength );
+}
+
+function wrapAttributes( wrapper, toWrap ) {
+	// Can't merge if name or priority differs.
+	if ( wrapper.name !== toWrap.name || wrapper.priority !== toWrap.priority ) {
+		return false;
+	}
+
+	// Check if attributes can be merged.
+	for ( let key of wrapper.getAttributeKeys() ) {
+		// Classes and styles should be checked separately.
+		if ( key === 'class' || key === 'style' ) {
+			continue;
+		}
+
+		// If some attributes are different we cannot wrap.
+		if ( toWrap.hasAttribute( key ) && toWrap.getAttribute( key ) !== wrapper.getAttribute( key ) ) {
+			return false;
+		}
+	}
+
+	// Check if styles can be merged.
+	for ( let key of wrapper.getStyleNames() ) {
+		if ( toWrap.hasStyle( key ) && toWrap.getStyle( key ) !== wrapper.getStyle( key ) ) {
+			return false;
+		}
+	}
+
+	// Move all attributes/classes/styles from wrapper to wrapped attribute.
+	for ( let key of wrapper.getAttributeKeys() ) {
+		// Classes and styles should be checked separately.
+		if ( key === 'class' || key === 'style' ) {
+			continue;
+		}
+
+		// Move only these attributes that are not present - other are similar.
+		if ( !toWrap.hasAttribute( key ) ) {
+			toWrap.setAttribute( key, wrapper.getAttribute( key ) );
+		}
+	}
+
+	for ( let key of wrapper.getStyleNames() ) {
+		if ( !toWrap.hasStyle( key ) ) {
+			toWrap.setStyle( key, wrapper.getStyle( key ) );
+		}
+	}
+
+	for ( let key of wrapper.getClassNames() ) {
+		if ( !toWrap.hasClass( key ) ) {
+			toWrap.addClass( key );
+		}
+	}
+
+	return true;
 }

+ 125 - 0
packages/ckeditor5-engine/tests/treeview/writer/wrap.js

@@ -551,5 +551,130 @@ describe( 'Writer', () => {
 				]
 			} );
 		} );
+
+		it( 'should wrap single element by joining attributes', () => {
+			// <p>[<b foo="bar" one="two"></b>]</p>
+			// wrap with <b baz="qux" one="two"></b>
+			// <p>[<b foo="bar" one="two" baz="qux"></b>]</p>
+			const b = new AttributeElement( 'b', {
+				foo: 'bar',
+				one: 'two'
+			} );
+			const p = new ContainerElement( 'p', null, b );
+			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
+			const wrapper = new AttributeElement( 'b', {
+				baz: 'qux',
+				one: 'two'
+			} );
+
+			const newRange = writer.wrap( range, wrapper );
+			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
+			expect( b.getAttribute( 'baz' ) ).to.equal( 'qux' );
+			expect( b.getAttribute( 'one' ) ).to.equal( 'two' );
+
+			test( writer, newRange, p, {
+				instanceOf: ContainerElement,
+				name: 'p',
+				rangeStart: 0,
+				rangeEnd: 1,
+				children: [
+					{ instanceOf: AttributeElement, name: 'b', children: [] }
+				]
+			} );
+		} );
+
+		it( 'should wrap single element by joining classes', () => {
+			// <p>[<b class="foo bar baz" ></b>]</p>
+			// wrap with <b class="foo bar qux jax"></b>
+			// <p>[<b class="foo bar baz qux jax"></b>]</p>
+			const b = new AttributeElement( 'b', {
+				class: 'foo bar baz'
+			} );
+			const p = new ContainerElement( 'p', null, b );
+			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
+			const wrapper = new AttributeElement( 'b', {
+				class: 'foo bar qux jax'
+			} );
+
+			const newRange = writer.wrap( range, wrapper );
+			expect( b.hasClass( 'foo', 'bar', 'baz', 'qux', 'jax' ) ).to.be.true;
+			test( writer, newRange, p, {
+				instanceOf: ContainerElement,
+				name: 'p',
+				rangeStart: 0,
+				rangeEnd: 1,
+				children: [
+					{ instanceOf: AttributeElement, name: 'b', children: [] }
+				]
+			} );
+		} );
+
+		it( 'should wrap single element by joining styles', () => {
+			// <p>[<b style="color:red; position: absolute;"></b>]</p>
+			// wrap with <b style="color:red; top: 20px;"></b>
+			// <p>[<b class="color:red; position: absolute; top:20px;"></b>]</p>
+			const b = new AttributeElement( 'b', {
+				style: 'color: red; position: absolute;'
+			} );
+			const p = new ContainerElement( 'p', null, b );
+			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
+			const wrapper = new AttributeElement( 'b', {
+				style: 'color:red; top: 20px;'
+			} );
+
+			const newRange = writer.wrap( range, wrapper );
+			expect( b.getStyle( 'color' ) ).to.equal( 'red' );
+			expect( b.getStyle( 'position' ) ).to.equal( 'absolute' );
+			expect( b.getStyle( 'top' ) ).to.equal( '20px' );
+
+			test( writer, newRange, p, {
+				instanceOf: ContainerElement,
+				name: 'p',
+				rangeStart: 0,
+				rangeEnd: 1,
+				children: [
+					{ instanceOf: AttributeElement, name: 'b', children: [] }
+				]
+			} );
+		} );
+
+		it( 'should be merged with outside element when wrapping all children', () => {
+			// <p><b foo="bar">[{foobar}<i>{baz}</i>]</b></p>
+			// wrap with <b baz="qux"></b>
+			// <p>[<b foo="bar" baz="qux">{foobar}</b>]</p>
+			const text1 = new Text( 'foobar' );
+			const text2 = new Text( 'baz' );
+			const i = new AttributeElement( 'i', null, text2 );
+			const b = new AttributeElement( 'b', { foo: 'bar' }, [ text1, i ] );
+			const p = new ContainerElement( 'p', null, [ b ] );
+			const wrapper = new AttributeElement( 'b', { baz: 'qux' } );
+			const range = Range.createFromParentsAndOffsets( b, 0, b, 2 );
+
+			const newRange = writer.wrap( range, wrapper );
+			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
+			expect( b.getAttribute( 'baz' ) ).to.equal( 'qux' );
+			test( writer, newRange, p, {
+				instanceOf: ContainerElement,
+				name: 'p',
+				rangeStart: 0,
+				rangeEnd: 1,
+				children: [
+					{
+						instanceof: AttributeElement,
+						name: 'b',
+						children: [
+							{ instanceOf: Text, data: 'foobar' },
+							{
+								instanceOf: AttributeElement,
+								name: 'i',
+								children: [
+									{ instanceOf: Text, data: 'baz' }
+								]
+							}
+						]
+					}
+				]
+			} );
+		} );
 	} );
 } );