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

Commit

Permalink
Merge 69befbc into 0fd6f49
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator committed Sep 10, 2019
2 parents 0fd6f49 + 69befbc commit df97ee7
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 8 deletions.
124 changes: 116 additions & 8 deletions src/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,14 @@ export function viewModelConverter( evt, data, conversionApi ) {

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

// The offset is used if list is not nested according to rules from HTML specification, e.g. directly in other list.
// We calculate offset here as the indent increment is done when converting children of other list items
// and it might be set incorrectly.
const offset = getIncompatibleListOffset( data.viewItem, conversionStore );
const resultingIndent = conversionStore.indent + offset;

writer.setAttribute( 'listIndent', resultingIndent, 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';
Expand Down Expand Up @@ -426,7 +433,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 +462,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 +473,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 +505,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 +573,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 +993,110 @@ 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' );
}

// Return list item indent offset - used to fix lists that are not properly nested according to HTML rules.
//
// Returning offset will change the indent from normal flow of a conversion process.
//
// 1. List item in a wrongly nested list (previous sibling is a list item)
//
// before: fixed list:
// OL OL
// |-> LI |-> LI (indent: 0)
// |-> OL |-> OL
// |-> LI (offset: +1) |-> LI (indent: 1)
//
// 2. List nested directly in other list as a first child.
//
// The offset will be 0 if one of the ancestors is nested in li so the indent is already properly calculated.
//
// before: fixed list:
// OL OL
// |-> LI |-> LI (indent: 0)
// |-> OL |-> OL
// |-> OL |-> LI (indent: 1)
// | |-> OL |-> LI (indent: 1)
// | |-> OL
// | |-> LI (offset: 0)
// |-> LI (offset: 0)
//
// The offset will be 0 if list is not nested in any other list item:
//
// before: fixed list:
// OL OL
// |-> OL |-> LI (indent: 0)
// |-> OL
// |-> OL
// |-> LI (offset: 0)
//
// @param {module:engine/view/element~Element} listItem
// @param {Object} conversionStore
// @returns {Number}
function getIncompatibleListOffset( listItem, conversionStore ) {
// Ensure proper conversion store value.
// Indent modifiers stores already calculated indentation modifiers.
conversionStore.indentOffsets = conversionStore.indentOffsets || new WeakMap();

const list = listItem.parent;

// List item might be pasted without parent list or be directly in pasted content - no need to calculate or store anything.
if ( !list || !list.parent ) {
return 0;
}

let offset = 0;

if ( isList( list.parent ) ) {
// Only consider list nested directly in other list so when not wrapped by LI element.
const previousSibling = list.previousSibling;

if ( previousSibling && previousSibling.is( 'li' ) ) {
// List is nested in other list but after other list item.
offset = ( conversionStore.indentOffsets.get( previousSibling.parent ) || 0 ) + 1;
} else {
// List is nested directly in other list as a first item.
offset = getNestedListOffset( list, conversionStore );
}
}

// Store the indent modifier of parent list.
conversionStore.indentOffsets.set( list, offset );

return offset;
}

// Return list item indent offset - used to fix lists that are not properly nested according to HTML rules.
//
// @param {module:engine/view/element~Element} listItem
// @param {Object} conversionStore
// @returns {Number}
function getNestedListOffset( list, conversionStore ) {
let listParent = list.parent;

while ( isList( listParent ) && !conversionStore.indentOffsets.has( listParent ) ) {
listParent = listParent.parent;
}

// If there was a list ancestor with offset we must return greater offset.
if ( conversionStore.indentOffsets.has( listParent ) ) {
return conversionStore.indentOffsets.get( listParent ) + 1;
}

return 0;
}
Loading

0 comments on commit df97ee7

Please sign in to comment.