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

Commit

Permalink
Merge pull request #94 from gjhenrique:nested-list-reborn
Browse files Browse the repository at this point in the history
Feature: Added support for basic list indentation when pasting from Microsoft Word. Closes ckeditor/ckeditor5#2518.

Thanks to [gjhenrique](https://github.com/gjhenrique) for the contribution!
  • Loading branch information
mlewand authored Mar 5, 2020
2 parents e7949c9 + af4cbcc commit 58ae829
Show file tree
Hide file tree
Showing 19 changed files with 3,941 additions and 11 deletions.
82 changes: 77 additions & 5 deletions src/filters/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,43 @@ export function transformListItemLikeElementsIntoLists( documentFragment, styles
}

let currentList = null;
let currentIndentation = 1;

itemLikeElements.forEach( ( itemLikeElement, i ) => {
if ( !currentList || isNewListNeeded( itemLikeElements[ i - 1 ], itemLikeElement ) ) {
const isDifferentList = isNewListNeeded( itemLikeElements[ i - 1 ], itemLikeElement );
const previousItemLikeElement = isDifferentList ? null : itemLikeElements[ i - 1 ];
const indentationDifference = getIndentationDifference( previousItemLikeElement, itemLikeElement );

if ( isDifferentList ) {
currentList = null;
currentIndentation = 1;
}

if ( !currentList || indentationDifference !== 0 ) {
const listStyle = detectListStyle( itemLikeElement, stylesString );

currentList = insertNewEmptyList( listStyle, itemLikeElement.element, writer );
if ( !currentList ) {
currentList = insertNewEmptyList( listStyle, itemLikeElement.element, writer );
} else if ( itemLikeElement.indent > currentIndentation ) {
const lastListItem = currentList.getChild( currentList.childCount - 1 );
const lastListItemChild = lastListItem.getChild( lastListItem.childCount - 1 );

currentList = insertNewEmptyList( listStyle, lastListItemChild, writer );

currentIndentation += 1;
} else if ( itemLikeElement.indent < currentIndentation ) {
const differentIndentation = currentIndentation - itemLikeElement.indent;

currentList = findParentListAtLevel( currentList, differentIndentation );

currentIndentation = parseInt( itemLikeElement.indent );
}

if ( itemLikeElement.indent <= currentIndentation ) {
if ( !currentList.is( listStyle.type ) ) {
currentList = writer.rename( listStyle.type, currentList );
}
}
}

const listItem = transformElementIntoListItem( itemLikeElement.element, writer );
Expand Down Expand Up @@ -155,14 +186,16 @@ function detectListStyle( listLikeItem, stylesString ) {
//
// @param {Object} listStyle List style object which determines the type of newly created list.
// Usually a result of `detectListStyle()` function.
// @param {module:engine/view/element~Element} element Element before which list is inserted.
// @param {module:engine/view/element~Element} element Element after which list is inserted.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
// @returns {module:engine/view/element~Element} Newly created list element.

function insertNewEmptyList( listStyle, element, writer ) {
const parent = element.parent;
const list = writer.createElement( listStyle.type );
const position = element.parent.getChildIndex( element );
const position = parent.getChildIndex( element ) + 1;

writer.insertChild( position, list, element.parent );
writer.insertChild( position, list, parent );

return list;
}
Expand Down Expand Up @@ -243,6 +276,10 @@ function removeBulletElement( element, writer ) {
// @param {Object} currentItem
// @returns {Boolean}
function isNewListNeeded( previousItem, currentItem ) {
if ( !previousItem ) {
return true;
}

if ( previousItem.id !== currentItem.id ) {
return true;
}
Expand All @@ -260,3 +297,38 @@ function isNewListNeeded( previousItem, currentItem ) {
function isList( element ) {
return element.is( 'ol' ) || element.is( 'ul' );
}

// Calculates the indentation difference between two given list items (based on indent attribute
// extracted from `mso-list` style, see #getListItemData).
//
// @param {Object} previousItem
// @param {Object} currentItem
// @returns {Number}
function getIndentationDifference( previousItem, currentItem ) {
return previousItem ? currentItem.indent - previousItem.indent : currentItem.indent - 1;
}

// Finds parent list element (ul/ol) of a given list element with indentation level lower by a given value.
//
// @param {module:engine/view/element~Element} listElement List element from which to start looking for a parent list.
// @param {Number} indentationDifference Indentation difference between lists.
// @returns {module:engine/view/element~Element} Found list element with indentation level lower by a given value.
function findParentListAtLevel( listElement, indentationDifference ) {
const ancestors = listElement.getAncestors( { parentFirst: true } );

let parentList = null;
let levelChange = 0;

for ( const ancestor of ancestors ) {
if ( ancestor.name === 'ul' || ancestor.name === 'ol' ) {
levelChange++;
}

if ( levelChange === indentationDifference ) {
parentList = ancestor;
break;
}
}

return parentList;
}
43 changes: 37 additions & 6 deletions tests/_data/list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import heading1 from './heading1/input.word2016.html';
import heading3Styled from './heading3-styled/input.word2016.html';
import heading7 from './heading7/input.word2016.html';
import resumeTemplate from './resume-template/input.word2016.html';
import nested from './nested/input.word2016.html';
import nestedMixed from './nested-mixed/input.word2016.html';
import nestedMultiple from './nested-multiple/input.word2016.html';

import simpleNormalized from './simple/normalized.word2016.html';
import styledNormalized from './styled/normalized.word2016.html';
Expand All @@ -23,6 +26,9 @@ import heading1Normalized from './heading1/normalized.word2016.html';
import heading3StyledNormalized from './heading3-styled/normalized.word2016.html';
import heading7Normalized from './heading7/normalized.word2016.html';
import resumeTemplateNormalized from './resume-template/normalized.word2016.html';
import nestedNormalized from './nested/normalized.word2016.html';
import nestedMixedNormalized from './nested-mixed/normalized.word2016.html';
import nestedMultipleNormalized from './nested-multiple/normalized.word2016.html';

import simpleModel from './simple/model.word2016.html';
import styledModel from './styled/model.word2016.html';
Expand All @@ -33,6 +39,9 @@ import heading1Model from './heading1/model.word2016.html';
import heading3StyledModel from './heading3-styled/model.word2016.html';
import heading7Model from './heading7/model.word2016.html';
import resumeTemplateModel from './resume-template/model.word2016.html';
import nestedModel from './nested/model.word2016.html';
import nestedMixedModel from './nested-mixed/model.word2016.html';
import nestedMultipleModel from './nested-multiple/model.word2016.html';

export const fixtures = {
input: {
Expand All @@ -44,7 +53,10 @@ export const fixtures = {
heading1,
heading3Styled,
heading7,
resumeTemplate
resumeTemplate,
nested,
nestedMixed,
nestedMultiple
},
normalized: {
simple: simpleNormalized,
Expand All @@ -55,7 +67,10 @@ export const fixtures = {
heading1: heading1Normalized,
heading3Styled: heading3StyledNormalized,
heading7: heading7Normalized,
resumeTemplate: resumeTemplateNormalized
resumeTemplate: resumeTemplateNormalized,
nested: nestedNormalized,
nestedMixed: nestedMixedNormalized,
nestedMultiple: nestedMultipleNormalized
},
model: {
simple: simpleModel,
Expand All @@ -66,7 +81,10 @@ export const fixtures = {
heading1: heading1Model,
heading3Styled: heading3StyledModel,
heading7: heading7Model,
resumeTemplate: resumeTemplateModel
resumeTemplate: resumeTemplateModel,
nested: nestedModel,
nestedMixed: nestedMixedModel,
nestedMultiple: nestedMultipleModel
}
};

Expand All @@ -80,6 +98,9 @@ import heading1Safari from './heading1/input.safari.word2016.html';
import heading3StyledSafari from './heading3-styled/input.safari.word2016.html';
import heading7Safari from './heading7/input.safari.word2016.html';
import resumeTemplateSafari from './resume-template/input.safari.word2016.html';
import nestedSafari from './nested/input.safari.word2016.html';
import nestedMixedSafari from './nested-mixed/input.safari.word2016.html';
import nestedMultipleSafari from './nested-multiple/input.safari.word2016.html';

import simpleNormalizedSafari from './simple/normalized.safari.word2016.html';
import styledNormalizedSafari from './styled/normalized.safari.word2016.html';
Expand All @@ -90,6 +111,7 @@ import heading1NormalizedSafari from './heading1/normalized.safari.word2016.html
import heading3StyledNormalizedSafari from './heading3-styled/normalized.safari.word2016.html';
import heading7NormalizedSafari from './heading7/normalized.safari.word2016.html';
import resumeTemplateNormalizedSafari from './resume-template/normalized.safari.word2016.html';
import nestedMultipleNormalizedSafari from './nested-multiple/normalized.safari.word2016.html';

import styledSafariModel from './styled/model.safari.word2016.html';
import resumeTemplateSafariModel from './resume-template/model.safari.word2016.html';
Expand All @@ -105,7 +127,10 @@ export const browserFixtures = {
heading1: heading1Safari,
heading3Styled: heading3StyledSafari,
heading7: heading7Safari,
resumeTemplate: resumeTemplateSafari
resumeTemplate: resumeTemplateSafari,
nested: nestedSafari,
nestedMixed: nestedMixedSafari,
nestedMultiple: nestedMultipleSafari
},
normalized: {
simple: simpleNormalizedSafari,
Expand All @@ -116,7 +141,10 @@ export const browserFixtures = {
heading1: heading1NormalizedSafari,
heading3Styled: heading3StyledNormalizedSafari,
heading7: heading7NormalizedSafari,
resumeTemplate: resumeTemplateNormalizedSafari
resumeTemplate: resumeTemplateNormalizedSafari,
nested: nestedNormalized,
nestedMixed: nestedMixedNormalized,
nestedMultiple: nestedMultipleNormalizedSafari
},
model: {
simple: simpleModel,
Expand All @@ -127,7 +155,10 @@ export const browserFixtures = {
heading1: heading1Model,
heading3Styled: heading3StyledModel,
heading7: heading7Model,
resumeTemplate: resumeTemplateSafariModel
resumeTemplate: resumeTemplateSafariModel,
nested: nestedModel,
nestedMixed: nestedMixedModel,
nestedMultiple: nestedMultipleModel
}
}
};
Loading

0 comments on commit 58ae829

Please sign in to comment.