Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge c5f3e8c into 0fd6f49
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator committed Sep 10, 2019
2 parents 0fd6f49 + c5f3e8c commit 9ee3d3b
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 15 deletions.
65 changes: 50 additions & 15 deletions src/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,22 +365,19 @@ export function modelViewMergeAfter( evt, data, conversionApi ) {
export function viewModelConverter( evt, data, conversionApi ) {
if ( conversionApi.consumable.consume( data.viewItem, { name: true } ) ) {
const writer = conversionApi.writer;
const conversionStore = this.conversionApi.store;

// 1. Create `listItem` model element.
const listItem = writer.createElement( 'listItem' );

// 2. Handle `listItem` model element attributes.
conversionStore.indent = conversionStore.indent || 0;
writer.setAttribute( 'listIndent', conversionStore.indent, listItem );
const indent = getIndent( data.viewItem );

writer.setAttribute( 'listIndent', indent, listItem );

// Set 'bulleted' as default. If this item is pasted into a context,
const type = data.viewItem.parent && data.viewItem.parent.name == 'ol' ? 'numbered' : 'bulleted';
writer.setAttribute( 'listType', type, listItem );

// `listItem`s created recursively should have bigger indent.
conversionStore.indent++;

// Try to find allowed parent for list item.
const splitResult = conversionApi.splitToAllowedParent( listItem, data.modelCursor );

Expand All @@ -394,8 +391,6 @@ export function viewModelConverter( evt, data, conversionApi ) {

const nextPosition = viewToModelListItemChildrenConverter( listItem, data.viewItem.getChildren(), conversionApi );

conversionStore.indent--;

// Result range starts before the first item and ends after the last.
data.modelRange = writer.createRange( data.modelCursor, nextPosition );

Expand Down Expand Up @@ -426,7 +421,9 @@ export function cleanList( evt, data, conversionApi ) {
const children = Array.from( data.viewItem.getChildren() );

for ( const child of children ) {
if ( !child.is( 'li' ) ) {
const isWrongElement = !( child.is( 'li' ) || isList( child ) );

if ( isWrongElement ) {
child._remove();
}
}
Expand All @@ -453,7 +450,7 @@ export function cleanListItem( evt, data, conversionApi ) {
let firstNode = true;

for ( const child of children ) {
if ( foundList && !child.is( 'ul' ) && !child.is( 'ol' ) ) {
if ( foundList && !isList( child ) ) {
child._remove();
}

Expand All @@ -464,10 +461,10 @@ export function cleanListItem( evt, data, conversionApi ) {
}

// If this is the last text node before <ul> or <ol>, right-trim it.
if ( !child.nextSibling || ( child.nextSibling.is( 'ul' ) || child.nextSibling.is( 'ol' ) ) ) {
if ( !child.nextSibling || isList( child.nextSibling ) ) {
child._data = child.data.replace( /\s+$/, '' );
}
} else if ( child.is( 'ul' ) || child.is( 'ol' ) ) {
} else if ( isList( child ) ) {
// If this is a <ul> or <ol>, do not process it, just mark that we already visited list element.
foundList = true;
}
Expand Down Expand Up @@ -496,7 +493,7 @@ export function modelToViewPosition( view ) {

if ( modelItem && modelItem.is( 'listItem' ) ) {
const viewItem = data.mapper.toViewElement( modelItem );
const topmostViewList = viewItem.getAncestors().find( element => element.is( 'ul' ) || element.is( 'ol' ) );
const topmostViewList = viewItem.getAncestors().find( isList );
const walker = view.createPositionAt( viewItem, 0 ).getWalker();

for ( const value of walker ) {
Expand Down Expand Up @@ -564,7 +561,7 @@ export function viewToModelPosition( model ) {
let modelLength = 1; // Starts from 1 because the original <li> has to be counted in too.
let viewList = viewPos.nodeBefore;

while ( viewList && ( viewList.is( 'ul' ) || viewList.is( 'ol' ) ) ) {
while ( viewList && isList( viewList ) ) {
modelLength += mapper.getModelLength( viewList );

viewList = viewList.previousSibling;
Expand Down Expand Up @@ -984,11 +981,49 @@ function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStart
// Handle multiple lists. This happens if list item has nested numbered and bulleted lists. Following lists
// are inserted after the first list (no need to recalculate insertion position for them).
for ( const child of [ ...viewRemovedItem.getChildren() ] ) {
if ( child.is( 'ul' ) || child.is( 'ol' ) ) {
if ( isList( child ) ) {
insertPosition = viewWriter.move( viewWriter.createRangeOn( child ), insertPosition ).end;

mergeViewLists( viewWriter, child, child.nextSibling );
mergeViewLists( viewWriter, child.previousSibling, child );
}
}
}

// Checks if view element is a list type (ul or ol).
//
// @param {module:engine/view/element~Element} viewElement
// @returns {Boolean}
function isList( viewElement ) {
return viewElement.is( 'ol' ) || viewElement.is( 'ul' );
}

// Calculates the indent value for a list item. Handles HTML compliant and non-compliant lists.
//
// @param {module:engine/view/element~Element} listItem
// @param {Object} conversionStore
// @returns {Number}
function getIndent( listItem ) {
let indent = 0;

let parent = listItem.parent;

while ( parent && ( parent.is( 'li' ) || isList( parent ) ) ) {
// Each LI in the tree will result in an increased indent for HTML compliant lists.
if ( parent.is( 'li' ) ) {
indent++;
} else {
// If however the list is nested in other list we should check previous sibling of any of the list elements...
const previousSibling = parent.previousSibling;

// ..,because the we might need increase its indent.
if ( previousSibling && previousSibling.is( 'li' ) ) {
indent++;
}
}

parent = parent.parent;
}

return indent;
}
237 changes: 237 additions & 0 deletions tests/listediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,243 @@ describe( 'ListEditing', () => {
} );
}

describe( 'non HTML compliant list fixing', () => {
test( 'ul in ul',
'<ul>' +
'<ul>' +
'<li>1.1</li>' +
'</ul>' +
'</ul>',
'<ul>' +
'<li>1.1</li>' +
'</ul>'
);

test( 'ul in ol',
'<ol>' +
'<ul>' +
'<li>1.1</li>' +
'</ul>' +
'</ol>',
'<ul>' +
'<li>1.1</li>' +
'</ul>'
);

test( 'ul in ul (previous sibling is li)',
'<ul>' +
'<li>1</li>' +
'<ul>' +
'<li>2.1</li>' +
'</ul>' +
'</ul>',
'<ul>' +
'<li>1' +
'<ul>' +
'<li>2.1</li>' +
'</ul>' +
'</li>' +
'</ul>'
);

test( 'ul in deeply nested ul - base index > 0 #1',
'<ul>' +
'<li>1.1</li>' +
'<li>1.2' +
'<ul>' +
'<ul>' +
'<ul>' +
'<ul>' +
'<li>2.1</li>' +
'</ul>' +
'</ul>' +
'</ul>' +
'</ul>' +
'</li>' +
'</ul>',
'<ul>' +
'<li>1.1</li>' +
'<li>1.2' +
'<ul>' +
'<li>2.1</li>' +
'</ul>' +
'</li>' +
'</ul>'
);

test( 'ul in deeply nested ul - base index > 0 #2',
'<ul>' +
'<li>1.1</li>' +
'<li>1.2' +
'<ul>' +
'<li>2.1</li>' +
'<ul>' +
'<ul>' +
'<ul>' +
'<li>3.1</li>' +
'</ul>' +
'</ul>' +
'</ul>' +
'<li>2.2</li>' +
'</ul>' +
'</li>' +
'</ul>',
'<ul>' +
'<li>1.1</li>' +
'<li>1.2' +
'<ul>' +
'<li>2.1' +
'<ul>' +
'<li>3.1</li>' +
'</ul>' +
'</li>' +
'<li>2.2</li>' +
'</ul>' +
'</li>' +
'</ul>'
);

test( 'ul in deeply nested ul inside li',
'<ul>' +
'<li>A' +
'<ul>' +
'<ul>' +
'<ul>' +
'<ul>' +
'<li>B</li>' +
'</ul>' +
'</ul>' +
'</ul>' +
'<li>C</li>' +
'</ul>' +
'</li>' +
'</ul>',
'<ul>' +
'<li>A' +
'<ul>' +
'<li>B</li>' +
'<li>C</li>' +
'</ul>' +
'</li>' +
'</ul>'
);

test( 'ul in deeply nested ul/ol',
'<ul>' +
'<li>A' +
'<ol>' +
'<ul>' +
'<ol>' +
'<ul>' +
'<li>B</li>' +
'</ul>' +
'</ol>' +
'</ul>' +
'<li>C</li>' +
'</ol>' +
'</li>' +
'</ul>',
'<ul>' +
'<li>A' +
'<ul>' +
'<li>B</li>' +
'<li>C</li>' +
'</ul>' +
'</li>' +
'</ul>'
);

test( 'ul in ul (complex case)',
'<ol>' +
'<li>1</li>' +
'<ul>' +
'<li>A</li>' +
'<ol>' +
'<li>1</li>' +
'</ol>' +
'</ul>' +
'<li>2</li>' +
'<li>3</li>' +
'<ul>' +
'<li>A</li>' +
'<li>B</li>' +
'</ul>' +
'</ol>' +
'<ul>' +
'<li>A</li>' +
'<ol>' +
'<li>1</li>' +
'<li>2</li>' +
'</ol>' +
'</ul>',
'<ol>' +
'<li>1' +
'<ul>' +
'<li>A' +
'<ol>' +
'<li>1</li>' +
'</ol>' +
'</li>' +
'</ul>' +
'</li>' +
'<li>2</li>' +
'<li>3' +
'<ul>' +
'<li>A</li>' +
'<li>B</li>' +
'</ul>' +
'</li>' +
'</ol>' +
'<ul>' +
'<li>A' +
'<ol>' +
'<li>1</li>' +
'<li>2</li>' +
'</ol>' +
'</li>' +
'</ul>'
);

test( 'ol in ol (deep structure)',
'<ol>' +
'<li>A1</li>' +
'<ol>' +
'<ol>' +
'<ol>' +
'<ol>' +
'<ol>' +
'<ol>' +
'<ol>' +
'<li>B8</li>' +
'</ol>' +
'</ol>' +
'</ol>' +
'</ol>' +
'</ol>' +
'<li>C3</li>' +
'<ol>' +
'<li>D4</li>' +
'</ol>' +
'</ol>' +
'<li>E2</li>' +
'</ol>' +
'</ol>',
'<ol>' +
'<li>A1' +
'<ol>' +
'<li>B8</li>' +
'<li>C3' +
'<ol>' +
'<li>D4</li>' +
'</ol>' +
'</li>' +
'<li>E2</li>' +
'</ol>' +
'</li>' +
'</ol>'
);
} );

test( 'bullet list simple structure',
'<p>foo</p>' +
'<ul>' +
Expand Down

0 comments on commit 9ee3d3b

Please sign in to comment.