Skip to content

Commit

Permalink
Keep start property and marker (if supported) when copying list from …
Browse files Browse the repository at this point in the history
…WordOnline (#1200)

* Keep start property and marker from WordOnline

* fix tests

* refactor

* remove unneeded change
  • Loading branch information
BryanValverdeU authored Aug 25, 2022
1 parent ff92953 commit fe1f775
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ const WORD_ONLINE_IDENTIFYING_SELECTOR =
const LIST_CONTAINER_ELEMENT_CLASS_NAME = 'ListContainerWrapper';
const IMAGE_CONTAINER_ELEMENT_CLASS_NAME = 'WACImageContainer';

//When the list style is a symbol and the value is not in the clipboard, WordOnline
const VALID_LIST_STYLE_CHAR_CODES = [
'111', //'o'
'9643', //'▫'
'9830', //'♦'
];

/**
* @internal
*/
Expand Down Expand Up @@ -102,7 +109,7 @@ export default function convertPastedContentFromWordOnline(fragment: DocumentFra
let listType: 'OL' | 'UL' = getContainerListType(listItemContainer); // list type that is contained by iterator.
// Initialize processed element with proper listType if this is the first element
if (!convertedListElement) {
convertedListElement = doc.createElement(listType);
convertedListElement = createNewList(listItemContainer, doc, listType);
}

// Get all list items(<li>) in the current iterator element.
Expand All @@ -117,7 +124,7 @@ export default function convertPastedContentFromWordOnline(fragment: DocumentFra
// and keep the processing going.
if (getTagOfNode(convertedListElement) != listType && itemLevel == 1) {
insertConvertedListToDoc(convertedListElement, fragment, itemBlock);
convertedListElement = doc.createElement(listType);
convertedListElement = createNewList(listItemContainer, doc, listType);
}
insertListItem(convertedListElement, item, listType, doc);
});
Expand Down Expand Up @@ -157,6 +164,15 @@ export default function convertPastedContentFromWordOnline(fragment: DocumentFra
});
}

function createNewList(listItemContainer: Element, doc: Document, tag: 'OL' | 'UL') {
const newList = doc.createElement(tag);
const startAttribute = listItemContainer.firstElementChild?.getAttribute('start');
if (startAttribute) {
newList.setAttribute('start', startAttribute);
}
return newList;
}

/**
* The node processing is based on the premise of only ol/ul is in ListContainerWrapper class
* However the html might be malformed, this function is to split all the other elements out of ListContainerWrapper
Expand Down Expand Up @@ -254,14 +270,25 @@ function getContainerListType(listItemContainer: Element): 'OL' | 'UL' | null {
function insertListItem(
listRootElement: Element,
itemToInsert: HTMLElement,
listType: string,
listType: 'UL' | 'OL',
doc: HTMLDocument
): void {
if (!listType) {
return;
}
// Get item level from 'data-aria-level' attribute
let itemLevel = parseInt(itemToInsert.getAttribute('data-aria-level'));
let itemLevel = parseInt(itemToInsert.getAttribute('data-aria-level') ?? '');

// Try to reuse the List Marker
let style = itemToInsert.getAttribute('data-leveltext');
if (
listType == 'UL' &&
style &&
VALID_LIST_STYLE_CHAR_CODES.indexOf(style.charCodeAt(0).toString()) > -1
) {
itemToInsert.style.listStyleType = `"${style} "`;
}

let curListLevel = listRootElement; // Level iterator to find the correct place for the current element.
// if the itemLevel is 1 it means the level iterator is at the correct place.
while (itemLevel > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,7 @@ import convertPastedContentFromWordOnline from '../../../lib/plugins/Paste/offic

describe('wordOnlineHandler', () => {
function runTest(html: string, expectedInnerHtml: string) {
const doc = new DOMParser().parseFromString(html, 'text/html');
const fragment = doc.createDocumentFragment();
while (doc.body.firstChild) {
fragment.appendChild(doc.body.firstChild);
}

convertPastedContentFromWordOnline(fragment);

while (fragment.firstChild) {
doc.body.appendChild(fragment.firstChild);
}
const doc = sanitizeContent(html);

expect(doc.body.innerHTML).toBe(expectedInnerHtml);
}
Expand Down Expand Up @@ -384,4 +374,56 @@ describe('wordOnlineHandler', () => {
);
});
});

it('Keep the start property on lists and try to reuse the Word provided marker style', () => {
const doc = sanitizeContent(
'<html><body><div><p><span><span>Test</span></span><span>&nbsp;</span></p></div><div class="ListContainerWrapper"><ol start="1"><li data-aria-level="1" data-aria-posinset="1" aria-setsize="-1" data-listid="1" data-leveltext="%1."><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ol></div><div class="ListContainerWrapper"><ul><li data-aria-level="2" data-aria-posinset="1" aria-setsize="-1" data-listid="1" data-leveltext="▫"><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ul></div><div><p><span><span>Test</span></span><span>&nbsp;</span></p></div><div class="ListContainerWrapper"><ol start="5"><li data-aria-level="1" data-aria-posinset="2" aria-setsize="-1" data-listid="1" data-leveltext="%1."><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ol></div><div class="ListContainerWrapper"><ul><li data-aria-level="2" data-aria-posinset="2" aria-setsize="-1" data-listid="1" data-leveltext="▫"><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ul></div></body></html>'
);

doc.querySelectorAll('ul li').forEach(el => {
const dataLevelText = el.getAttribute('data-leveltext');
if (dataLevelText) {
expect((el as HTMLElement).style.listStyleType).toContain(dataLevelText);
}
});

const orderedLists = doc.querySelectorAll('ol');
expect(orderedLists.length).toBe(2);
expect(orderedLists[0].start).toBe(1);
expect(orderedLists[1].start).toBe(5);
});

it('Keep the start property on lists and remove marker style that is not reusable', () => {
const notUsableMarker = String.fromCharCode(10);
const doc = sanitizeContent(
`<html><body><div><p><span><span>Test</span></span><span>&nbsp;</span></p></div><div class="ListContainerWrapper"><ol start="1"><li data-aria-level="1" data-aria-posinset="1" aria-setsize="-1" data-listid="1" data-leveltext="%1."><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ol></div><div class="ListContainerWrapper"><ul><li data-aria-level="2" data-aria-posinset="1" aria-setsize="-1" data-listid="1" data-leveltext="${notUsableMarker}"><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ul></div><div><p><span><span>Test</span></span><span>&nbsp;</span></p></div><div class="ListContainerWrapper"><ol start="2"><li data-aria-level="1" data-aria-posinset="2" aria-setsize="-1" data-listid="1" data-leveltext="%1."><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ol></div><div class="ListContainerWrapper"><ul><li data-aria-level="2" data-aria-posinset="2" aria-setsize="-1" data-listid="1" data-leveltext="${notUsableMarker}"><p><span><span>Test</span></span><span>&nbsp;</span></p></li></ul></div></body></html>`
);

doc.querySelectorAll('ul li').forEach(el => {
const dataLevelText = el.getAttribute('data-leveltext');
if (dataLevelText) {
expect((el as HTMLElement).style.listStyleType).not.toContain(dataLevelText);
}
});

const orderedLists = doc.querySelectorAll('ol');
expect(orderedLists.length).toBe(2);
expect(orderedLists[0].start).toBe(1);
expect(orderedLists[1].start).toBe(2);
});
});

function sanitizeContent(html: string) {
const doc = new DOMParser().parseFromString(html, 'text/html');
const fragment = doc.createDocumentFragment();
while (doc.body.firstChild) {
fragment.appendChild(doc.body.firstChild);
}

convertPastedContentFromWordOnline(fragment);

while (fragment.firstChild) {
doc.body.appendChild(fragment.firstChild);
}
return doc;
}

0 comments on commit fe1f775

Please sign in to comment.