Skip to content

Commit

Permalink
RichText: fix tree output after applying format (#14555)
Browse files Browse the repository at this point in the history
* RichText: fix HTML output after applying format

* Add unit tests

* Fix position calc
  • Loading branch information
ellatrix authored and draganescu committed Apr 23, 2019
1 parent d635ca9 commit c1a36fc
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 18 deletions.
49 changes: 34 additions & 15 deletions packages/rich-text/src/apply-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { find } from 'lodash';

import { normaliseFormats } from './normalise-formats';

function replace( array, index, value ) {
array = array.slice();
array[ index ] = value;
return array;
}

/**
* Apply a format object to a Rich Text value from the given `startIndex` to the
* given `endIndex`. Indices are retrieved from the selection if none are
Expand Down Expand Up @@ -38,15 +44,19 @@ export function applyFormat(
// If the caret is at a format of the same type, expand start and end to
// the edges of the format. This is useful to apply new attributes.
if ( startFormat ) {
while ( find( newFormats[ startIndex ], startFormat ) ) {
applyFormats( newFormats, startIndex, format );
const index = newFormats[ startIndex ].indexOf( startFormat );

while ( newFormats[ startIndex ] && newFormats[ startIndex ][ index ] === startFormat ) {
newFormats[ startIndex ] =
replace( newFormats[ startIndex ], index, format );
startIndex--;
}

endIndex++;

while ( find( newFormats[ endIndex ], startFormat ) ) {
applyFormats( newFormats, endIndex, format );
while ( newFormats[ endIndex ] && newFormats[ endIndex ][ index ] === startFormat ) {
newFormats[ endIndex ] =
replace( newFormats[ endIndex ], index, format );
endIndex++;
}
// Otherwise, insert a placeholder with the format so new input appears
Expand All @@ -58,20 +68,29 @@ export function applyFormat(
};
}
} else {
// Determine the highest position the new format can be inserted at.
let position = +Infinity;

for ( let index = startIndex; index < endIndex; index++ ) {
if ( newFormats[ index ] ) {
newFormats[ index ] = newFormats[ index ]
.filter( ( { type } ) => type !== format.type );

const length = newFormats[ index ].length;

if ( length < position ) {
position = length;
}
} else {
newFormats[ index ] = [];
position = 0;
}
}

for ( let index = startIndex; index < endIndex; index++ ) {
applyFormats( newFormats, index, format );
newFormats[ index ].splice( position, 0, format );
}
}

return normaliseFormats( { ...value, formats: newFormats } );
}

function applyFormats( formats, index, format ) {
if ( formats[ index ] ) {
const newFormatsAtIndex = formats[ index ].filter( ( { type } ) => type !== format.type );
newFormatsAtIndex.push( format );
formats[ index ] = newFormatsAtIndex;
} else {
formats[ index ] = [ format ];
}
}
116 changes: 114 additions & 2 deletions packages/rich-text/src/test/apply-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,124 @@ describe( 'applyFormat', () => {
const a2 = { type: 'a', attributes: { href: '#test' } };

it( 'should apply format', () => {
const record = {
formats: [ , , , , ],
text: 'test',
};
const expected = {
...record,
formats: [ [ em ], [ em ], [ em ], [ em ] ],
};
const result = applyFormat( deepFreeze( record ), em, 0, 4 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 4 );
} );

it( 'should apply format on top of existing format', () => {
const record = {
formats: [ [ strong ], [ strong ], [ strong ], [ strong ] ],
text: 'test',
};
const expected = {
...record,
formats: [ [ strong, em ], [ strong, em ], [ strong, em ], [ strong, em ] ],
};
const result = applyFormat( deepFreeze( record ), em, 0, 4 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 4 );
} );

it( 'should apply format and remove same format type', () => {
const record = {
formats: [ [ strong ], [ em, strong ], [ em, strong ], [ strong ] ],
text: 'test',
};
const expected = {
...record,
formats: [ [ strong, em ], [ strong, em ], [ strong, em ], [ strong, em ] ],
};
const result = applyFormat( deepFreeze( record ), em, 0, 4 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 4 );
} );

it( 'should apply format around existing format', () => {
const record = {
formats: [ , [ em ], [ em ], , ],
text: 'test',
};
const expected = {
...record,
formats: [ [ strong ], [ strong, em ], [ strong, em ], [ strong ] ],
};
const result = applyFormat( deepFreeze( record ), strong, 0, 4 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 4 );
} );

it( 'should apply format around existing format with edge right', () => {
const record = {
formats: [ , [ em ], [ em ], , ],
text: 'test',
};
const expected = {
...record,
formats: [ [ strong ], [ strong, em ], [ strong, em ], , ],
};
const result = applyFormat( deepFreeze( record ), strong, 0, 3 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 3 );
} );

it( 'should apply format around existing format with edge left', () => {
const record = {
formats: [ , [ em ], [ em ], , ],
text: 'test',
};
const expected = {
...record,
formats: [ , [ strong, em ], [ strong, em ], [ strong ] ],
};
const result = applyFormat( deepFreeze( record ), strong, 1, 4 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 3 );
} );

it( 'should apply format around existing format with break', () => {
const record = {
formats: [ , [ em ], , [ em ] ],
text: 'test',
};
const expected = {
...record,
formats: [ , [ strong, em ], [ strong ], [ strong, em ] ],
};
const result = applyFormat( deepFreeze( record ), strong, 1, 4 );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 3 );
} );

it( 'should apply format crossing existing format', () => {
const record = {
formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ],
text: 'one two three',
};
const expected = {
formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ],
formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ],
text: 'one two three',
};
const result = applyFormat( deepFreeze( record ), strong, 3, 6 );
Expand All @@ -40,7 +152,7 @@ describe( 'applyFormat', () => {
end: 6,
};
const expected = {
formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ],
formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ],
text: 'one two three',
start: 3,
end: 6,
Expand Down
2 changes: 1 addition & 1 deletion packages/rich-text/src/test/toggle-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe( 'toggleFormat', () => {
end: 6,
};
const expected = {
formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ],
formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ],
text: 'one two three',
start: 3,
end: 6,
Expand Down

0 comments on commit c1a36fc

Please sign in to comment.