diff --git a/packages/roosterjs-editor-plugins/lib/plugins/Paste/officeOnlineConverter/convertPastedContentFromWordOnline.ts b/packages/roosterjs-editor-plugins/lib/plugins/Paste/officeOnlineConverter/convertPastedContentFromWordOnline.ts index 0d6e90057c6..97cc17122aa 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/Paste/officeOnlineConverter/convertPastedContentFromWordOnline.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/Paste/officeOnlineConverter/convertPastedContentFromWordOnline.ts @@ -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 */ @@ -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(
  • ) in the current iterator element. @@ -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); }); @@ -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 @@ -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) { diff --git a/packages/roosterjs-editor-plugins/test/paste/word/wordOnlineHandlerTest.ts b/packages/roosterjs-editor-plugins/test/paste/word/wordOnlineHandlerTest.ts index ac9b2797375..533faadd61f 100644 --- a/packages/roosterjs-editor-plugins/test/paste/word/wordOnlineHandlerTest.ts +++ b/packages/roosterjs-editor-plugins/test/paste/word/wordOnlineHandlerTest.ts @@ -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); } @@ -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( + '

    Test 

    1. Test 

    Test 

    1. Test 

    ' + ); + + 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( + `

    Test 

    1. Test 

    Test 

    1. Test 

    ` + ); + + 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; +}