Skip to content

Commit 0f59e67

Browse files
Fix PowerPoint paste bug with list markers in table cells (#3398)
* Fix PowerPoint paste bug with list markers in table cells Skip list-item processing for table elements and clear the list marker font size so it does not overflow the table cell. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add unit tests and scope list marker font size removal to tables Only remove the list marker font size when the list is inside a table cell, and add unit tests covering table detection and marker font size. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3abd612 commit 0f59e67

2 files changed

Lines changed: 103 additions & 2 deletions

File tree

packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const BulletSelector = '* > span > span[style*=mso-special-format]';
1818
const MsOfficeSpecialFormat = 'mso-special-format';
1919
const CssStyleKey = 'style';
2020
const MsoSpecialFormatRegex = /mso-special-format:\s*([^;]*)/;
21+
const TableElementTags = new Set(['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TD', 'TH']);
2122

2223
const clearListItemStyles = (format: ContentModelListItemLevelFormat): void => {
2324
delete format.textAlign;
@@ -52,7 +53,9 @@ export function processPastedContentFromPowerPoint(
5253
if (style.includes(MsOfficeSpecialFormat) && context.listFormat.levels.length > 0) {
5354
return;
5455
}
55-
const bulletElement = element.querySelector(BulletSelector) as HTMLElement;
56+
const bulletElement = !TableElementTags.has(element.tagName)
57+
? (element.querySelector(BulletSelector) as HTMLElement)
58+
: null;
5659
if (bulletElement) {
5760
const {
5861
depth,
@@ -95,6 +98,13 @@ export function processPastedContentFromPowerPoint(
9598
}
9699
clearListItemStyles(listItem.levels[listItem.levels.length - 1].format);
97100
clearListItemStyles(listItem.format);
101+
102+
// When the list is inside a table cell, remove the font size from the
103+
// list marker so it does not inherit the list's font size. Otherwise a
104+
// large marker may be rendered outside of the table cell.
105+
if (element.closest('td,th')) {
106+
delete listItem.formatHolder.format.fontSize;
107+
}
98108
});
99109
} else {
100110
context.defaultElementProcessors.element?.(group, element, context);

packages/roosterjs-content-model-plugins/test/paste/powerPoint/processPastedContentFromPowerPointTest.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as moveChildNodes from 'roosterjs-content-model-dom/lib/domUtils/moveChildNodes';
22
import { createDefaultDomToModelContext } from '../../TestHelper';
3+
import { createDomToModelContext, domToContentModel } from 'roosterjs-content-model-dom';
34
import { processPastedContentFromPowerPoint } from '../../../lib/paste/PowerPoint/processPastedContentFromPowerPoint';
4-
import type { BeforePasteEvent, ClipboardData, DOMCreator } from 'roosterjs-content-model-types';
5+
import type {
6+
BeforePasteEvent,
7+
ClipboardData,
8+
ContentModelListItem,
9+
DOMCreator,
10+
} from 'roosterjs-content-model-types';
511

612
const getPasteEvent = (): BeforePasteEvent => {
713
return {
@@ -120,3 +126,88 @@ describe('processPastedContentFromPowerPointTest |', () => {
120126
expect(moveChildNodes.moveChildNodes).not.toHaveBeenCalled();
121127
});
122128
});
129+
130+
describe('processPastedContentFromPowerPoint list handling |', () => {
131+
const domCreator: DOMCreator = {
132+
htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
133+
};
134+
135+
const bulletHtml =
136+
"<span style='font-size:7.0pt'>" +
137+
"<span style='mso-special-format:bullet;font-family:Arial'>\u2022</span>" +
138+
'</span>';
139+
140+
function convert(html: string) {
141+
const ev = getPasteEvent();
142+
ev.clipboardData.html = html;
143+
ev.clipboardData.text = 'text';
144+
145+
processPastedContentFromPowerPoint(ev, domCreator);
146+
147+
const container = document.createElement('div');
148+
container.innerHTML = html;
149+
150+
const context = createDomToModelContext(undefined, ev.domToModelOption);
151+
152+
return domToContentModel(container, context);
153+
}
154+
155+
it('Converts a PowerPoint bullet div into a list item', () => {
156+
const model = convert(
157+
"<div style='margin-top:10.0pt;direction:ltr;text-align:left'>" +
158+
bulletHtml +
159+
"<span style='font-size:7.0pt;color:black'>Item</span>" +
160+
'</div>'
161+
);
162+
163+
expect(model.blocks.length).toBe(1);
164+
expect(model.blocks[0].blockType).toBe('BlockGroup');
165+
expect((model.blocks[0] as ContentModelListItem).blockGroupType).toBe('ListItem');
166+
});
167+
168+
it('Keeps the list marker font size when the list is not inside a table', () => {
169+
const model = convert(
170+
"<div style='margin-top:10.0pt;direction:ltr;text-align:left;font-size:7.0pt'>" +
171+
bulletHtml +
172+
"<span style='font-size:7.0pt;color:black'>Item</span>" +
173+
'</div>'
174+
);
175+
176+
const listItem = model.blocks[0] as ContentModelListItem;
177+
178+
expect(listItem.blockGroupType).toBe('ListItem');
179+
expect(listItem.formatHolder.format.fontSize).toBe('7pt');
180+
});
181+
182+
it('Removes the list marker font size when the list is inside a table', () => {
183+
const model = convert(
184+
'<table><tbody><tr><td>' +
185+
"<div style='margin-top:10.0pt;direction:ltr;text-align:left;font-size:7.0pt'>" +
186+
bulletHtml +
187+
"<span style='font-size:7.0pt;color:black'>Item</span>" +
188+
'</div>' +
189+
'</td></tr></tbody></table>'
190+
);
191+
192+
const table = model.blocks[0] as any;
193+
const listItem = table.rows[0].cells[0].blocks[0] as ContentModelListItem;
194+
195+
expect(model.blocks[0].blockType).toBe('Table');
196+
expect(listItem.blockGroupType).toBe('ListItem');
197+
expect(listItem.formatHolder.format.fontSize).toBeUndefined();
198+
});
199+
200+
it('Does not convert a table element into a list when it contains a bullet', () => {
201+
const model = convert(
202+
'<table><tbody><tr><td>' +
203+
"<div style='margin-top:10.0pt;direction:ltr;text-align:left'>" +
204+
bulletHtml +
205+
"<span style='font-size:7.0pt;color:black'>Item</span>" +
206+
'</div>' +
207+
'</td></tr></tbody></table>'
208+
);
209+
210+
expect(model.blocks.length).toBe(1);
211+
expect(model.blocks[0].blockType).toBe('Table');
212+
});
213+
});

0 commit comments

Comments
 (0)