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

Support for nested indentation on pasted content #94

Merged
merged 9 commits into from
Mar 5, 2020
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
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