From 122295b5f469e5acee2d45a74491c57136cc5a4b Mon Sep 17 00:00:00 2001 From: iamsivin Date: Wed, 29 Apr 2026 17:43:32 +0530 Subject: [PATCH] chore: Review fix --- .../dashboard/helper/emailQuoteExtractor.js | 27 +++++++++------ .../helper/specs/emailQuoteExtractor.spec.js | 34 ++++++++++++++++++- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/app/javascript/dashboard/helper/emailQuoteExtractor.js b/app/javascript/dashboard/helper/emailQuoteExtractor.js index 965a91f66..0bd68243c 100644 --- a/app/javascript/dashboard/helper/emailQuoteExtractor.js +++ b/app/javascript/dashboard/helper/emailQuoteExtractor.js @@ -126,11 +126,12 @@ export class EmailQuoteExtractor { return this.findBlocksContainingText(root, HEADER_PATTERNS); } - // ---------- 5. Plain-text RFC `>` tail ---------- - // For text/plain emails (after sanitizeTextForRender converts \n →
), - // find the earliest top-level text node whose visible lines all begin with - // `>` and strip from there to the end (collapsing leading
/whitespace - // separators back into the tail). + // ---------- 5. Top-level quote tail ---------- + // For replies that arrive as text +
with no block wrapper (text/plain + // bodies after sanitizeTextForRender). Find the earliest top-level text + // node that begins a quote tail — either every visible line starts with `>` + // (RFC quote prefix) or the text contains a header marker — and strip from + // there, collapsing leading
/whitespace separators into the tail. static removePlainTextTail(root) { const start = this.findPlainTextTailStart(root); @@ -142,7 +143,7 @@ export class EmailQuoteExtractor { static findPlainTextTailStart(root) { const children = Array.from(root.childNodes); const tailIdx = children.findIndex(node => - this.isQuotePrefixedTextNode(node) + this.isQuoteTailStartTextNode(node) ); if (tailIdx === -1) return -1; let start = tailIdx; @@ -152,14 +153,18 @@ export class EmailQuoteExtractor { return start; } - static isQuotePrefixedTextNode(node) { + static isQuoteTailStartTextNode(node) { if (node.nodeType !== Node.TEXT_NODE) return false; const text = node.textContent; if (!text.trim()) return false; - return text - .split('\n') - .filter(line => line.trim() !== '') - .every(line => line.trim().startsWith('>')); + const lines = text.split('\n').filter(line => line.trim() !== ''); + if (lines.length > 0 && lines.every(l => l.trim().startsWith('>'))) { + return true; + } + return ( + HEADER_PATTERNS.some(p => p.test(text)) || + HARD_HEADER_PATTERNS.some(p => p.test(text)) + ); } static isNeutralNode(node) { diff --git a/app/javascript/dashboard/helper/specs/emailQuoteExtractor.spec.js b/app/javascript/dashboard/helper/specs/emailQuoteExtractor.spec.js index f3d338da9..963c83263 100644 --- a/app/javascript/dashboard/helper/specs/emailQuoteExtractor.spec.js +++ b/app/javascript/dashboard/helper/specs/emailQuoteExtractor.spec.js @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { EmailQuoteExtractor } from '../emailQuoteExtractor.js'; const SAMPLE_EMAIL_HTML = ` @@ -237,6 +237,38 @@ describe('EmailQuoteExtractor', () => { expect(cleaned).toContain('Reply text'); expect(cleaned).not.toContain('From: Sam'); }); + + // Header markers at the TOP LEVEL — text +
shape with no block + // wrapper. The marker's nearest block ancestor is root itself. + it('detects top-level "On … wrote:" header (no wrapper)', () => { + const html = + 'Reply text

On Tue, Pat wrote:
Original line 1
Original line 2'; + expect(EmailQuoteExtractor.hasQuotes(html)).toBe(true); + const c = document.createElement('div'); + c.innerHTML = EmailQuoteExtractor.extractQuotes(html); + expect(c.textContent).toContain('Reply text'); + expect(c.textContent).not.toContain('On Tue, Pat wrote'); + expect(c.textContent).not.toContain('Original line 1'); + }); + + it('detects top-level "From:/Sent:" header (no wrapper)', () => { + const html = + 'Reply text
From: Sam
Sent: Wednesday
Original body'; + expect(EmailQuoteExtractor.hasQuotes(html)).toBe(true); + const c = document.createElement('div'); + c.innerHTML = EmailQuoteExtractor.extractQuotes(html); + expect(c.textContent).toContain('Reply text'); + expect(c.textContent).not.toContain('From: Sam'); + }); + + it('detects top-level "-----Original Message-----" (no wrapper)', () => { + const html = 'Reply
-----Original Message-----
From: Sam
Body'; + expect(EmailQuoteExtractor.hasQuotes(html)).toBe(true); + const c = document.createElement('div'); + c.innerHTML = EmailQuoteExtractor.extractQuotes(html); + expect(c.textContent).toContain('Reply'); + expect(c.textContent).not.toContain('Original Message'); + }); }); it('strips RFC-style `>` quoted lines from a plain-text only body (iPhone Mail)', () => {