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 outdent with children #13559

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
12 changes: 12 additions & 0 deletions packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ exports[`List should indent and outdent level 2 3`] = `
<!-- /wp:list -->"
`;

exports[`List should outdent with children 1`] = `
"<!-- wp:list -->
<ul><li>a<ul><li>b<ul><li>c</li></ul></li></ul></li></ul>
<!-- /wp:list -->"
`;

exports[`List should outdent with children 2`] = `
"<!-- wp:list -->
<ul><li>a</li><li>b<ul><li>c</li></ul></li></ul>
<!-- /wp:list -->"
`;

exports[`List should split indented list item 1`] = `
"<!-- wp:list -->
<ul><li>one<ul><li>two</li><li>three</li></ul></li></ul>
Expand Down
18 changes: 18 additions & 0 deletions packages/e2e-tests/specs/blocks/list.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,22 @@ describe( 'List', () => {

expect( await getEditedPostContent() ).toMatchSnapshot();
} );

it( 'should outdent with children', async () => {
await insertBlock( 'List' );
await page.keyboard.type( 'a' );
await page.keyboard.press( 'Enter' );
await pressKeyWithModifier( 'primary', 'm' );
await page.keyboard.type( 'b' );
await page.keyboard.press( 'Enter' );
await pressKeyWithModifier( 'primary', 'm' );
await page.keyboard.type( 'c' );

expect( await getEditedPostContent() ).toMatchSnapshot();

await page.keyboard.press( 'ArrowUp' );
await pressKeyWithModifier( 'primaryShift', 'm' );

expect( await getEditedPostContent() ).toMatchSnapshot();
} );
} );
40 changes: 40 additions & 0 deletions packages/rich-text/src/get-last-child-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Internal dependencies
*/

import { LINE_SEPARATOR } from './special-characters';

/**
* Gets the line index of the last child in the list.
*
* @param {Object} value Value to search.
* @param {number} lineIndex Line index of a list item in the list.
*
* @return {Array} The index of the last child.
*/
export function getLastChildIndex( { text, formats }, lineIndex ) {
const lineFormats = formats[ lineIndex ] || [];
// Use the given line index in case there are no next children.
let childIndex = lineIndex;

// `lineIndex` could be `undefined` if it's the first line.
for ( let index = lineIndex || 0; index < text.length; index++ ) {
// We're only interested in line indices.
if ( text[ index ] !== LINE_SEPARATOR ) {
continue;
}

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

// If the amout of formats is equal or more, store it, then return the
// last one if the amount of formats is less.
if ( formatsAtIndex.length >= lineFormats.length ) {
childIndex = index;
} else {
return childIndex;
}
}

// If the end of the text is reached, return the last child index.
return childIndex;
}
21 changes: 14 additions & 7 deletions packages/rich-text/src/outdent-list-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LINE_SEPARATOR } from './special-characters';
import { normaliseFormats } from './normalise-formats';
import { getLineIndex } from './get-line-index';
import { getParentLineIndex } from './get-parent-line-index';
import { getLastChildIndex } from './get-last-child-index';

/**
* Outdents any selected list items if possible.
Expand All @@ -16,17 +17,23 @@ import { getParentLineIndex } from './get-parent-line-index';
*/
export function outdentListItems( value ) {
const { text, formats, start, end } = value;
const lineIndex = getLineIndex( value );
const lineFormats = formats[ lineIndex ];
const startingLineIndex = getLineIndex( value, start );

if ( lineFormats === undefined ) {
// Return early if the starting line index cannot be further outdented.
if ( formats[ startingLineIndex ] === undefined ) {
return value;
}

const newFormats = formats.slice( 0 );
const parentFormats = formats[ getParentLineIndex( value, lineIndex ) ] || [];

for ( let index = lineIndex; index < end; index++ ) {
const parentFormats = formats[ getParentLineIndex( value, startingLineIndex ) ] || [];
const endingLineIndex = getLineIndex( value, end );
const lastChildIndex = getLastChildIndex( value, endingLineIndex );

// Outdent all list items from the starting line index until the last child
// index of the ending list. All children of the ending list need to be
// outdented, otherwise they'll be orphaned.
for ( let index = startingLineIndex; index <= lastChildIndex; index++ ) {
// Skip indices that are not line separators.
if ( text[ index ] !== LINE_SEPARATOR ) {
continue;
}
Expand All @@ -37,7 +44,7 @@ export function outdentListItems( value ) {
);

if ( newFormats[ index ].length === 0 ) {
delete newFormats[ lineIndex ];
delete newFormats[ index ];
}
}

Expand Down
50 changes: 50 additions & 0 deletions packages/rich-text/src/test/get-last-child-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* External dependencies
*/
import deepFreeze from 'deep-freeze';

/**
* Internal dependencies
*/

import { getLastChildIndex } from '../get-last-child-index';
import { LINE_SEPARATOR } from '../special-characters';

describe( 'outdentListItems', () => {
const ul = { type: 'ul' };

it( 'should return undefined if there is only one line', () => {
expect( getLastChildIndex( deepFreeze( {
formats: [ , ],
text: '1',
} ), undefined ) ).toBe( undefined );
} );

it( 'should return the last line if no line is indented', () => {
expect( getLastChildIndex( deepFreeze( {
formats: [ , ],
text: `1${ LINE_SEPARATOR }`,
} ), undefined ) ).toBe( 1 );
} );

it( 'should return the last child index', () => {
expect( getLastChildIndex( deepFreeze( {
formats: [ , [ ul ], , [ ul ], , ],
text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`,
} ), undefined ) ).toBe( 3 );
} );

it( 'should return the last child index by sibling', () => {
expect( getLastChildIndex( deepFreeze( {
formats: [ , [ ul ], , [ ul ], , ],
text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`,
} ), 1 ) ).toBe( 3 );
} );

it( 'should return the last child index (with further lower indented items)', () => {
expect( getLastChildIndex( deepFreeze( {
formats: [ , [ ul ], , , , ],
text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`,
} ), 1 ) ).toBe( 1 );
} );
} );
30 changes: 26 additions & 4 deletions packages/rich-text/src/test/outdent-list-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe( 'outdentListItems', () => {
start: 1,
end: 1,
};
const result = outdentListItems( deepFreeze( record ), ul );
const result = outdentListItems( deepFreeze( record ) );

expect( result ).toEqual( record );
expect( result ).toBe( record );
Expand All @@ -43,7 +43,7 @@ describe( 'outdentListItems', () => {
start: 2,
end: 2,
};
const result = outdentListItems( deepFreeze( record ), ul );
const result = outdentListItems( deepFreeze( record ) );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
Expand All @@ -65,7 +65,7 @@ describe( 'outdentListItems', () => {
start: 5,
end: 5,
};
const result = outdentListItems( deepFreeze( record ), ul );
const result = outdentListItems( deepFreeze( record ) );

expect( result ).toEqual( expected );
expect( result ).not.toBe( record );
Expand All @@ -87,10 +87,32 @@ describe( 'outdentListItems', () => {
start: 2,
end: 5,
};
const result = outdentListItems( deepFreeze( record ), ul );
const result = outdentListItems( deepFreeze( record ) );

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

it( 'should outdent list item with children', () => {
// 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 ], , [ ul, ul ], , ],
text,
start: 2,
end: 2,
};
const expected = {
formats: [ , , , [ ul ], , [ ul ], , ],
text,
start: 2,
end: 2,
};
const result = outdentListItems( deepFreeze( record ) );

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