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