mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
🐛(frontend) fix outlook web handler in unquote logic (#754)
About the unquote-message logic, we encount a bug with a thread implying Outlook Desktop quotes. Actually, for Outlook web we were looking for a hr tag as quote separtor element. But sometimes this one can be wrap into a div and we missed it.
This commit is contained in:
@@ -143,15 +143,31 @@ export const HANDLERS: CustomHandler[] = [
|
||||
const normalizedStyle = style.replaceAll(/(cm|pt|mm)/g, "in");
|
||||
|
||||
if (normalizedStyle.endsWith(" 1.0in;padding:3.0in 0in 0in 0in")) {
|
||||
// Check if parent has only one element child
|
||||
if (msoRoot.parentElement) {
|
||||
const elementChildren = Array.from(
|
||||
msoRoot.parentElement.childNodes
|
||||
).filter((node) => node.nodeType === Node.ELEMENT_NODE);
|
||||
|
||||
if (elementChildren.length === 1) {
|
||||
msoRoot = msoRoot.parentElement as HTMLElement;
|
||||
// The quoted content may live at an upper level: climb out of wrapper
|
||||
// divs while the header has no following sibling, as long as nothing
|
||||
// meaningful precedes it inside the wrapper (to avoid swallowing the
|
||||
// new message content)
|
||||
while (
|
||||
msoRoot.parentElement &&
|
||||
msoRoot.parentElement.tagName.toLowerCase() !== "body" &&
|
||||
!msoRoot.nextElementSibling
|
||||
) {
|
||||
// Use childNodes to also catch plain-text nodes, but only those
|
||||
// preceding the header: text nodes following it belong to the quote
|
||||
// and must not halt the climb
|
||||
const siblings = Array.from(msoRoot.parentElement.childNodes);
|
||||
const previousSiblings = siblings.slice(0, siblings.indexOf(msoRoot));
|
||||
if (
|
||||
previousSiblings.some(
|
||||
(node) =>
|
||||
(node.nodeType === Node.ELEMENT_NODE ||
|
||||
node.nodeType === Node.TEXT_NODE) &&
|
||||
node.textContent?.trim()
|
||||
)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
msoRoot = msoRoot.parentElement;
|
||||
}
|
||||
|
||||
// Collect all next siblings
|
||||
@@ -175,7 +191,14 @@ export const HANDLERS: CustomHandler[] = [
|
||||
const detectedElements: HTMLElement[] = [];
|
||||
let prev = element.previousElementSibling;
|
||||
while (prev) {
|
||||
if (prev.tagName.toLowerCase() === "hr") {
|
||||
// Outlook renders the reply separator either as a bare <hr>
|
||||
// or as an <hr> alone inside a centering div
|
||||
const isSeparator =
|
||||
prev.tagName.toLowerCase() === "hr" ||
|
||||
(prev.children.length === 1 &&
|
||||
prev.children[0].tagName.toLowerCase() === "hr" &&
|
||||
!prev.textContent?.trim());
|
||||
if (isSeparator) {
|
||||
// It's a reply from Outlook!
|
||||
detectedElements.push(element, prev as HTMLElement);
|
||||
let sibling = element.nextElementSibling;
|
||||
|
||||
@@ -375,6 +375,85 @@ describe("UnquoteMessage", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should remove Microsoft Outlook Desktop quotes when the hr separator is wrapped in a div", () => {
|
||||
const html = `
|
||||
<div>My reply to the message</div>
|
||||
<div class="MsoNormal" align="center" style="text-align:center"><hr size="3" width="98%" align="center"></div>
|
||||
<div id="divRplyFwdMsg">
|
||||
<b>De :</b> sender@example.com<br>
|
||||
<b>Envoyé :</b> lundi 6 juillet 2026 15:45<br>
|
||||
<b>À :</b> recipient@example.com<br>
|
||||
</div>
|
||||
<div>Original message body</div>
|
||||
`;
|
||||
|
||||
const result = new UnquoteMessage(html).getHtml();
|
||||
|
||||
expect(result.hadQuotes).toBe(true);
|
||||
expect(result.detectionMethod).toBe("handlers");
|
||||
expect(result.content).toMatchInlineSnapshot(
|
||||
`"<div>My reply to the message</div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it("should remove Microsoft Outlook Web quotes when the header is nested in a wrapper and the body is a sibling of the wrapper", () => {
|
||||
const html = `
|
||||
<div>My reply</div>
|
||||
<div>
|
||||
<div id="content_out_sender_example.com"></div>
|
||||
<div style="border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0cm 0cm 0cm">
|
||||
<p class="MsoNormal"><b>De :</b> sender@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>Original message body</div>
|
||||
`;
|
||||
|
||||
const result = new UnquoteMessage(html).getHtml();
|
||||
|
||||
expect(result.hadQuotes).toBe(true);
|
||||
expect(result.detectionMethod).toBe("handlers");
|
||||
expect(result.content).toMatchInlineSnapshot(
|
||||
`"<div>My reply</div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it("should not swallow a plain-text reply preceding the Microsoft Outlook Web header inside a wrapper", () => {
|
||||
const html = `
|
||||
<div>
|
||||
My plain-text reply
|
||||
<div style="border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0cm 0cm 0cm">
|
||||
<p class="MsoNormal"><b>De :</b> sender@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const result = new UnquoteMessage(html).getHtml();
|
||||
|
||||
expect(result.hadQuotes).toBe(true);
|
||||
expect(result.content).toContain("My plain-text reply");
|
||||
expect(result.content).not.toContain("sender@example.com");
|
||||
});
|
||||
|
||||
it("should keep climbing when only quoted text nodes follow the Microsoft Outlook Web header", () => {
|
||||
const html = `
|
||||
<div>My reply</div>
|
||||
<div>
|
||||
<div style="border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0cm 0cm 0cm">
|
||||
<p class="MsoNormal"><b>De :</b> sender@example.com</p>
|
||||
</div>
|
||||
Quoted text node
|
||||
</div>
|
||||
<div>Original message body</div>
|
||||
`;
|
||||
|
||||
const result = new UnquoteMessage(html).getHtml();
|
||||
|
||||
expect(result.hadQuotes).toBe(true);
|
||||
expect(result.content).toMatchInlineSnapshot(
|
||||
`"<div>My reply</div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it("should remove ZMail quotes with zmail_extra", () => {
|
||||
const html = `
|
||||
<div>New message</div>
|
||||
|
||||
Reference in New Issue
Block a user