Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RichText: List: fix indentation #13563

Merged
merged 4 commits into from
Jan 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 );
} );
} );