diff --git a/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts b/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts index 1cc4446e58ad..b44e9faf9d55 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts @@ -18,6 +18,7 @@ const BulletSelector = '* > span > span[style*=mso-special-format]'; const MsOfficeSpecialFormat = 'mso-special-format'; const CssStyleKey = 'style'; const MsoSpecialFormatRegex = /mso-special-format:\s*([^;]*)/; +const TableElementTags = new Set(['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TD', 'TH']); const clearListItemStyles = (format: ContentModelListItemLevelFormat): void => { delete format.textAlign; @@ -52,7 +53,9 @@ export function processPastedContentFromPowerPoint( if (style.includes(MsOfficeSpecialFormat) && context.listFormat.levels.length > 0) { return; } - const bulletElement = element.querySelector(BulletSelector) as HTMLElement; + const bulletElement = !TableElementTags.has(element.tagName) + ? (element.querySelector(BulletSelector) as HTMLElement) + : null; if (bulletElement) { const { depth, @@ -95,6 +98,13 @@ export function processPastedContentFromPowerPoint( } clearListItemStyles(listItem.levels[listItem.levels.length - 1].format); clearListItemStyles(listItem.format); + + // When the list is inside a table cell, remove the font size from the + // list marker so it does not inherit the list's font size. Otherwise a + // large marker may be rendered outside of the table cell. + if (element.closest('td,th')) { + delete listItem.formatHolder.format.fontSize; + } }); } else { context.defaultElementProcessors.element?.(group, element, context); diff --git a/packages/roosterjs-content-model-plugins/test/paste/powerPoint/processPastedContentFromPowerPointTest.ts b/packages/roosterjs-content-model-plugins/test/paste/powerPoint/processPastedContentFromPowerPointTest.ts index 422c783f3c64..791252750bf5 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/powerPoint/processPastedContentFromPowerPointTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/powerPoint/processPastedContentFromPowerPointTest.ts @@ -1,7 +1,13 @@ import * as moveChildNodes from 'roosterjs-content-model-dom/lib/domUtils/moveChildNodes'; import { createDefaultDomToModelContext } from '../../TestHelper'; +import { createDomToModelContext, domToContentModel } from 'roosterjs-content-model-dom'; import { processPastedContentFromPowerPoint } from '../../../lib/paste/PowerPoint/processPastedContentFromPowerPoint'; -import type { BeforePasteEvent, ClipboardData, DOMCreator } from 'roosterjs-content-model-types'; +import type { + BeforePasteEvent, + ClipboardData, + ContentModelListItem, + DOMCreator, +} from 'roosterjs-content-model-types'; const getPasteEvent = (): BeforePasteEvent => { return { @@ -120,3 +126,88 @@ describe('processPastedContentFromPowerPointTest |', () => { expect(moveChildNodes.moveChildNodes).not.toHaveBeenCalled(); }); }); + +describe('processPastedContentFromPowerPoint list handling |', () => { + const domCreator: DOMCreator = { + htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'), + }; + + const bulletHtml = + "" + + "\u2022" + + ''; + + function convert(html: string) { + const ev = getPasteEvent(); + ev.clipboardData.html = html; + ev.clipboardData.text = 'text'; + + processPastedContentFromPowerPoint(ev, domCreator); + + const container = document.createElement('div'); + container.innerHTML = html; + + const context = createDomToModelContext(undefined, ev.domToModelOption); + + return domToContentModel(container, context); + } + + it('Converts a PowerPoint bullet div into a list item', () => { + const model = convert( + "
" + + bulletHtml + + "Item" + + '
' + ); + + expect(model.blocks.length).toBe(1); + expect(model.blocks[0].blockType).toBe('BlockGroup'); + expect((model.blocks[0] as ContentModelListItem).blockGroupType).toBe('ListItem'); + }); + + it('Keeps the list marker font size when the list is not inside a table', () => { + const model = convert( + "
" + + bulletHtml + + "Item" + + '
' + ); + + const listItem = model.blocks[0] as ContentModelListItem; + + expect(listItem.blockGroupType).toBe('ListItem'); + expect(listItem.formatHolder.format.fontSize).toBe('7pt'); + }); + + it('Removes the list marker font size when the list is inside a table', () => { + const model = convert( + '
' + + "
" + + bulletHtml + + "Item" + + '
' + + '
' + ); + + const table = model.blocks[0] as any; + const listItem = table.rows[0].cells[0].blocks[0] as ContentModelListItem; + + expect(model.blocks[0].blockType).toBe('Table'); + expect(listItem.blockGroupType).toBe('ListItem'); + expect(listItem.formatHolder.format.fontSize).toBeUndefined(); + }); + + it('Does not convert a table element into a list when it contains a bullet', () => { + const model = convert( + '
' + + "
" + + bulletHtml + + "Item" + + '
' + + '
' + ); + + expect(model.blocks.length).toBe(1); + expect(model.blocks[0].blockType).toBe('Table'); + }); +});