Skip to content

Commit

Permalink
RichText: List: fix indentation (#13563)
Browse files Browse the repository at this point in the history
* fix list indentation

* Add more tests

* Guard against negative lineIndex

* Fix outdent error
  • Loading branch information
ellatrix authored Jan 29, 2019
1 parent 4165bbd commit d9cb84e
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 37 deletions.
79 changes: 43 additions & 36 deletions packages/rich-text/src/indent-list-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@ import { LINE_SEPARATOR } from './special-characters';
import { normaliseFormats } from './normalise-formats';
import { getLineIndex } from './get-line-index';

/**
* Gets the line index of the first previous list item with higher indentation.
*
* @param {Object} value Value to search.
* @param {number} lineIndex Line index of the list item to compare with.
*
* @return {boolean} The line index.
*/
function getTargetLevelLineIndex( { text, formats }, lineIndex ) {
const startFormats = formats[ lineIndex ] || [];

let index = lineIndex;

while ( index-- >= 0 ) {
if ( text[ index ] !== LINE_SEPARATOR ) {
continue;
}

const formatsAtIndex = formats[ index ] || [];

// Return the first line index that is one level higher. If the level is
// lower or equal, there is no result.
if ( formatsAtIndex.length === startFormats.length + 1 ) {
return index;
} else if ( formatsAtIndex.length <= startFormats.length ) {
return;
}
}
}

/**
* Indents any selected list items if possible.
*
Expand All @@ -23,62 +53,39 @@ export function indentListItems( value, rootFormat ) {
}

const { text, formats, start, end } = value;
const previousLineIndex = getLineIndex( value, lineIndex );
const formatsAtLineIndex = formats[ lineIndex ] || [];
const targetFormats = formats[ getLineIndex( value, lineIndex ) ] || [];
const formatsAtPreviousLineIndex = formats[ previousLineIndex ] || [];

// The the indentation of the current line is greater than previous line,
// then the line cannot be furter indented.
if ( formatsAtLineIndex.length > targetFormats.length ) {
if ( formatsAtLineIndex.length > formatsAtPreviousLineIndex.length ) {
return value;
}

const newFormats = formats.slice();
const targetLevelLineIndex = getTargetLevelLineIndex( value, lineIndex );

for ( let index = lineIndex; index < end; index++ ) {
if ( text[ index ] !== LINE_SEPARATOR ) {
continue;
}

// If the indentation of the previous line is the same as the current
// line, then duplicate the type and append all current types. E.g.
//
// 1. one
// 2. two <= Selected
// * three <= Selected
//
// should become:
//
// 1. one
// 1. two <= Selected
// * three <= Selected
//
// ^ Inserted list
//
// Otherwise take the target formats and append traling lists. E.g.
//
// 1. one
// * target
// 2. two <= Selected
// * three <= Selected
//
// should become:
//
// 1. one
// * target
// * two <= Selected
// * three <= Selected
//
if ( targetFormats.length === formatsAtLineIndex.length ) {
// Get the previous list, and if there's a child list, take over the
// formats. If not, duplicate the last level and create a new level.
if ( targetLevelLineIndex ) {
const targetFormats = formats[ targetLevelLineIndex ] || [];
newFormats[ index ] = targetFormats.concat(
( newFormats[ index ] || [] ).slice( targetFormats.length - 1 )
);
} else {
const targetFormats = formats[ previousLineIndex ] || [];
const lastformat = targetFormats[ targetFormats.length - 1 ] || rootFormat;

newFormats[ index ] = targetFormats.concat(
[ lastformat ],
( newFormats[ index ] || [] ).slice( targetFormats.length )
);
} else {
newFormats[ index ] = targetFormats.concat(
( newFormats[ index ] || [] ).slice( targetFormats.length - 1 )
);
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/rich-text/src/outdent-list-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ export function outdentListItems( value ) {
continue;
}

// In the case of level 0, the formats at the index are undefined.
const currentFormats = newFormats[ index ] || [];

// Omit the indentation level where the selection starts.
newFormats[ index ] = parentFormats.concat(
newFormats[ index ].slice( parentFormats.length + 1 )
currentFormats.slice( parentFormats.length + 1 )
);

if ( newFormats[ index ].length === 0 ) {
Expand Down
44 changes: 44 additions & 0 deletions packages/rich-text/src/test/indent-list-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,48 @@ describe( 'indentListItems', () => {
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 2 );
} );

it( 'should indent one level at a time', () => {
// As we're testing list formats, the text should remain the same.
const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`;
const record = {
formats: [ , [ ul ], , [ ul, ul ], , , , ],
text,
start: 6,
end: 6,
};

const result1 = indentListItems( deepFreeze( record ), ul );

expect( result1 ).not.toBe( record );
expect( getSparseArrayLength( result1.formats ) ).toBe( 3 );
expect( result1 ).toEqual( {
formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ],
text,
start: 6,
end: 6,
} );

const result2 = indentListItems( deepFreeze( result1 ), ul );

expect( result2 ).not.toBe( result1 );
expect( getSparseArrayLength( result2.formats ) ).toBe( 3 );
expect( result2 ).toEqual( {
formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ],
text,
start: 6,
end: 6,
} );

const result3 = indentListItems( deepFreeze( result2 ), ul );

expect( result3 ).not.toBe( result2 );
expect( getSparseArrayLength( result3.formats ) ).toBe( 3 );
expect( result3 ).toEqual( {
formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul, ul ], , ],
text,
start: 6,
end: 6,
} );
} );
} );
22 changes: 22 additions & 0 deletions packages/rich-text/src/test/outdent-list-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,26 @@ describe( 'outdentListItems', () => {
expect( result ).not.toBe( record );
expect( getSparseArrayLength( result.formats ) ).toBe( 2 );
} );

it( 'should outdent when a selected item is at level 0', () => {
// As we're testing list formats, the text should remain the same.
const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`;
const record = {
formats: [ , [ ul ], , , , ],
text,
start: 2,
end: 5,
};
const expected = {
formats: [ , , , , , ],
text,
start: 2,
end: 5,
};
const result = outdentListItems( deepFreeze( record ) );

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

0 comments on commit d9cb84e

Please sign in to comment.