chore: Review fix

This commit is contained in:
iamsivin
2026-04-29 23:01:41 +05:30
parent c8abbadafc
commit 5245cc4245
2 changed files with 81 additions and 7 deletions
@@ -91,6 +91,17 @@ const cutBlockAtMarker = (block, marker) => {
if (!block.childNodes.length) block.remove();
};
// Walk up to the nearest enclosing `<blockquote>` (including `block` itself).
// Returns null when there is none below `root`.
const findEnclosingBlockquote = (block, root) => {
let cur = block;
while (cur && cur !== root) {
if (cur.tagName === 'BLOCKQUOTE') return cur;
cur = cur.parentElement;
}
return null;
};
// Walk up while `block` is the first substantive child of its parent.
// Promotes the cut to the wrapper, so a divider `<div>` plus the body
// siblings AFTER it strip together.
@@ -151,13 +162,28 @@ const apply = root => {
// 3. Trailing <blockquote> as the last top-level child.
if (root.lastElementChild?.matches?.('blockquote'))
root.lastElementChild.remove();
// 4. Soft headers — same hard-cut, but walk up to the wrapper first.
findBlocks(root, isSoftHeader).forEach(b =>
cutBlockAtMarker(
expandToWrapper(b, root),
t => HEADER_LINE.test(t) || ATTRIBUTION.test(t)
)
);
// 4. Soft headers. Three sub-cases:
// (a) match sits inside a <blockquote> — remove the whole blockquote
// (it wraps the entire quote: attribution + body). Apple Mail.
// (b) match has >= 2 header lines (real Outlook attribution shape) —
// hard-cut at the wrapper level so the body siblings go too.
// (c) just an "On … wrote:" attribution → develop-style remove the
// block, so a user reply sitting at root level isn't eaten.
findBlocks(root, isSoftHeader).forEach(block => {
const enclosingBq = findEnclosingBlockquote(block, root);
if (enclosingBq) {
enclosingBq.remove();
return;
}
if (countHeaderLines(blockText(block)) >= 2) {
cutBlockAtMarker(
expandToWrapper(block, root),
t => HEADER_LINE.test(t) || ATTRIBUTION.test(t)
);
return;
}
block.remove();
});
// 5. Top-level RFC `>` / header tail.
const start = findTopLevelTailStart(root);
if (start !== -1) [...root.childNodes].slice(start).forEach(n => n.remove());
@@ -310,6 +310,54 @@ describe('EmailQuoteExtractor', () => {
expect(cleaned).not.toContain('From: Sam');
});
it('strips Apple-Mail blockquote (attribution + body inside one <blockquote type="cite">)', () => {
const html =
'<div>Sounds good, see you Friday.</div>' +
'<div><br><blockquote type="cite">' +
'<div>On Apr 6, 2026, at 11:26 AM, Sam &lt;sam@example.test&gt; wrote:</div>' +
'<br><div><div>Hi Pat,</div><div>Locking the Friday slot.</div><div>Sam</div></div>' +
'</blockquote></div>';
const c = document.createElement('div');
c.innerHTML = EmailQuoteExtractor.extractQuotes(html);
expect(c.textContent).toContain('Sounds good');
expect(c.textContent).not.toContain('On Apr 6, 2026');
expect(c.textContent).not.toContain('Hi Pat');
expect(c.textContent).not.toContain('Locking the Friday slot');
});
it('strips flat Outlook attribution + body (multi-line From/Sent/To/Subject)', () => {
const html =
'<p>Confirming I received this — will review tomorrow.</p>' +
'<p>Thanks,<br>Pat</p>' +
'<p>From: Sam &lt;sam@example.test&gt;<br>Sent: Wednesday, December 4, 2024 5:15 PM<br>To: Pat &lt;pat@example.test&gt;<br>Subject: Quotation</p>' +
'<p>Hi Pat,<br>Quotation attached. Let me know if you need changes.<br>Sam</p>';
const c = document.createElement('div');
c.innerHTML = EmailQuoteExtractor.extractQuotes(html);
expect(c.textContent).toContain('Confirming I received this');
expect(c.textContent).toContain('Thanks,');
expect(c.textContent).not.toContain('From: Sam');
expect(c.textContent).not.toContain('Quotation attached');
});
// Inline reply where a soft-header `<blockquote>` is followed by the
// user's actual reply at the SAME level. The hard-cut for soft headers
// would otherwise eat the reply.
it('preserves user reply that follows a soft-header <blockquote>', () => {
const html =
'<blockquote>On Mon, Sep 22, Sam wrote:<br>Original quoted line.</blockquote><p>My actual reply.</p>';
const c = document.createElement('div');
c.innerHTML = EmailQuoteExtractor.extractQuotes(html);
expect(c.textContent).toContain('My actual reply');
});
it('preserves user reply that follows a wrapper div containing the soft-header block', () => {
const html =
'<div><blockquote>On Mon, Sam wrote:</blockquote></div><p>My reply outside the wrapper.</p>';
const c = document.createElement('div');
c.innerHTML = EmailQuoteExtractor.extractQuotes(html);
expect(c.textContent).toContain('My reply outside the wrapper');
});
it('preserves reply when the From-header sits inside a deep wrapper (Outlook WordSection1)', () => {
const html = `
<div class="WordSection1">