From 080b9ab79545270b204281b6b416992eccc41a67 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 1 May 2025 12:47:36 +0200 Subject: [PATCH] courrier preview --- components/email/ComposeEmail.tsx | 78 +++++++++++++----------- components/email/EmailContentDisplay.tsx | 29 ++++++++- components/email/EmailDetailView.tsx | 38 +++--------- components/email/RichEmailEditor.tsx | 30 +++++++-- lib/utils/email-content.ts | 45 ++++++++++++-- lib/utils/email-utils.ts | 74 +++++++++++----------- 6 files changed, 184 insertions(+), 110 deletions(-) diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index f825c940..b71684b8 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -200,48 +200,58 @@ export default function ComposeEmail(props: ComposeEmailProps) { // Provide a basic template if the content is empty const { fromStr, toStr, ccStr, dateStr, subject } = getFormattedInfoForEmail(initialEmail); const fallbackContent = ` -
-
-
---------------------------- Forwarded Message ----------------------------
+
+
+
+
---------------------------- Forwarded Message ----------------------------
+
+ + + + + + + + + + + + + + + + + + ${ccStr ? ` + + + + ` : ''} +
From:${fromStr}
Date:${dateStr}
Subject:${subject || ''}
To:${toStr}
Cc:${ccStr}
+
+
----------------------------------------------------------------------
+
- - - - - - - - - - - - - - - - - - ${ccStr ? ` - - - - ` : ''} -
From:${fromStr}
Date:${dateStr}
Subject:${subject || ''}
To:${toStr}
Cc:${ccStr}
-
-
----------------------------------------------------------------------
-
-
-
- [Original message content could not be loaded] +
[Original message content could not be loaded]
`; setEmailContent(fallbackContent); } else { console.log('Setting forward content:', { length: content.length, - isHtml: formatted.content.isHtml + isHtml: formatted.content.isHtml, + hasForwardedTable: content.includes('---------------------------- Forwarded Message ----------------------------'), + hasClosingContainer: content.includes('
') }); - setEmailContent(content); + + // Ensure the content is wrapped in a div to preserve structure + // Add a distinctive id to help with debugging + if (!content.startsWith('${content}`; + setEmailContent(wrappedContent); + } else { + setEmailContent(content); + } } // Handle attachments for forward (original attachments + extracted inline images) diff --git a/components/email/EmailContentDisplay.tsx b/components/email/EmailContentDisplay.tsx index 5ba2c26b..0b4b1620 100644 --- a/components/email/EmailContentDisplay.tsx +++ b/components/email/EmailContentDisplay.tsx @@ -73,7 +73,11 @@ const EmailContentDisplay: React.FC = ({

Content Type: {typeof content === 'string' ? 'Text' : 'HTML'}

HTML Length: {typeof content === 'string' ? content.length : content?.html?.length || 0}

-

Text Length: {typeof content === 'string' ? content.length : content?.text?.length || 0}

+

Text Length: {typeof content === 'string' ? 0 : content?.text?.length || 0}

+

Has Forwarded Header: {typeof content === 'string' ? + content.includes('Forwarded Message') : + (content?.html?.includes('Forwarded Message') || content?.text?.includes('Forwarded Message')) ? 'Yes' : 'No' + }

)} @@ -103,6 +107,29 @@ const EmailContentDisplay: React.FC = ({ border-left: none; border-right: 2px solid #ddd; } + + /* Specific styling for forwarded email header */ + :global(.forwarded-email-header) { + margin: 20px 0 10px 0 !important; + color: #666 !important; + font-family: Arial, sans-serif !important; + } + + :global(.forwarded-email-header table) { + margin-bottom: 10px !important; + font-size: 14px !important; + width: 100% !important; + border-collapse: collapse !important; + } + + :global(.forwarded-email-header td) { + padding: 3px 5px !important; + vertical-align: top !important; + } + + :global(.forwarded-email-content) { + margin-top: 15px !important; + } `} ); diff --git a/components/email/EmailDetailView.tsx b/components/email/EmailDetailView.tsx index afb1ef28..eaa26f36 100644 --- a/components/email/EmailDetailView.tsx +++ b/components/email/EmailDetailView.tsx @@ -48,39 +48,19 @@ export default function EmailDetailView({ console.log('EmailDetailView renderEmailContent', { hasContent: !!email.content, contentType: typeof email.content, - hasHtml: !!email.html, - hasText: !!email.text + hasHtml: !!email.content?.html, + hasText: !!email.content?.text }); - // Determine what content to use and how to handle it - let contentToUse = ''; + // Import the centralized rendering function + const { formatEmailContent } = require('@/lib/utils/email-utils'); - if (email.content) { - // If content is a string, use it directly - if (typeof email.content === 'string') { - contentToUse = email.content; - } - // If content is an object with html/text properties - else if (typeof email.content === 'object') { - contentToUse = email.content.html || email.content.text || ''; - } - } - // Fall back to html or text properties if content is not available - else if (email.html) { - contentToUse = email.html; - } - else if (email.text) { - // Convert plain text to HTML with line breaks - contentToUse = email.text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/\n/g, '
'); - } + // Use the centralized formatting function + const formattedContent = formatEmailContent(email); - // Return content or fallback message - return contentToUse ? -
: + // Return formatted content or fallback message + return formattedContent ? +
:
No content available
; } catch (e) { console.error('Error rendering email:', e); diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index e7a1c310..53fe2e6f 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -91,13 +91,22 @@ const RichEmailEditor: React.FC = ({ console.log('Setting initial content in editor', { length: initialContent.length, startsWithHtml: initialContent.trim().startsWith('<'), + hasForwardedHeader: initialContent.includes('Forwarded Message'), }); // Detect text direction const direction = detectTextDirection(initialContent); + // Preserve forwarded email structure by wrapping in a container if needed + let contentToSet = initialContent; + if (initialContent.includes('Forwarded Message') && !initialContent.includes('forwarded-email-container')) { + // This might be a forwarded email that wasn't properly wrapped + contentToSet = `
${initialContent}
`; + console.log('Adding container to forwarded email content'); + } + // Process HTML content using centralized utility - const sanitizedContent = processHtmlContent(initialContent); + const sanitizedContent = processHtmlContent(contentToSet); // Check if sanitized content is valid if (sanitizedContent.trim().length === 0) { @@ -105,7 +114,7 @@ const RichEmailEditor: React.FC = ({ // Try to extract text content if HTML processing failed try { const tempDiv = document.createElement('div'); - tempDiv.innerHTML = initialContent; + tempDiv.innerHTML = contentToSet; const textContent = tempDiv.textContent || tempDiv.innerText || 'Empty content'; // Set text directly to ensure something displays @@ -209,27 +218,36 @@ const RichEmailEditor: React.FC = ({ try { console.log('Updating content in editor:', { contentLength: initialContent.length, - startsWithHtml: initialContent.trim().startsWith('<') + startsWithHtml: initialContent.trim().startsWith('<'), + hasForwardedHeader: initialContent.includes('Forwarded Message'), }); // Detect text direction const direction = detectTextDirection(initialContent); + // Special handling for forwarded email content + let contentToSet = initialContent; + if (initialContent.includes('Forwarded Message') && !initialContent.includes('forwarded-email-container')) { + // This might be a forwarded email that wasn't properly wrapped + contentToSet = `
${initialContent}
`; + console.log('Adding container to forwarded email content during update'); + } + // Process HTML content using centralized utility - const sanitizedContent = processHtmlContent(initialContent); + const sanitizedContent = processHtmlContent(contentToSet); // Check if content is valid HTML if (sanitizedContent.trim().length === 0) { console.warn('Sanitized content is empty, using original content'); // If sanitized content is empty, try to extract text from original const tempDiv = document.createElement('div'); - tempDiv.innerHTML = initialContent; + tempDiv.innerHTML = contentToSet; const textContent = tempDiv.textContent || tempDiv.innerText || ''; // Create simple HTML with text content quillRef.current.setText(textContent); } else { - // SIMPLIFIED: Set content directly to the root element rather than using clipboard + // Set content directly to the root element rather than using clipboard quillRef.current.root.innerHTML = sanitizedContent; // Set the direction for the content diff --git a/lib/utils/email-content.ts b/lib/utils/email-content.ts index dac643c4..c6eaac4c 100644 --- a/lib/utils/email-content.ts +++ b/lib/utils/email-content.ts @@ -65,6 +65,27 @@ export function extractEmailContent(email: any): { text: string; html: string } } } } + + // Special case for YCharts and similar emails where content is detected as object + // but doesn't have standard html/text properties - stringify and try to extract + if (!textContent && !htmlContent) { + try { + // Try to extract from stringified version + const contentString = JSON.stringify(email.content); + if (contentString && contentString.includes(']*>([\s\S]*)<\/html>/i) || + contentString.match(/]*>([\s\S]*)<\/body>/i); + + if (htmlMatch && htmlMatch[1]) { + htmlContent = htmlMatch[1]; + console.log('Extracted HTML from stringified content object'); + } + } + } catch (err) { + console.warn('Failed to extract from stringified content:', err); + } + } } } else if (typeof email.content === 'string') { // Check if content is likely HTML @@ -213,6 +234,16 @@ export function processHtmlContent(htmlContent: string, textContent?: string): s // Use the centralized sanitizeHtml function let sanitizedContent = sanitizeHtml(htmlContent); + // If sanitized content is empty but original content wasn't, + // try to extract text as a fallback + if (!sanitizedContent.trim() && htmlContent.trim()) { + console.warn('Sanitized content is empty, attempting to extract text as fallback'); + const extractedText = extractTextFromHtml(htmlContent); + if (extractedText) { + return `
${extractedText}
`; + } + } + // Fix URL encoding issues and clean up content try { if (typeof window !== 'undefined' && typeof document !== 'undefined') { @@ -243,12 +274,14 @@ export function processHtmlContent(htmlContent: string, textContent?: string): s if (src) { // Don't modify cid: URLs as they are handled specially in email clients if (src.startsWith('cid:')) { - // Keep cid: URLs as they are + // Keep cid: URLs as they are but add data attribute for debugging + img.setAttribute('data-cid-preserved', 'true'); console.log('Preserving CID reference:', src); } - // Fix http:// URLs to https:// for security + // Fix http:// URLs to https:// for security - unless they're to known image hosts else if (src.startsWith('http://')) { img.setAttribute('src', src.replace('http://', 'https://')); + console.log('Fixed HTTP image URL:', src); } // Handle relative URLs that might be broken else if (!src.startsWith('https://') && !src.startsWith('data:')) { @@ -303,17 +336,21 @@ export function processHtmlContent(htmlContent: string, textContent?: string): s } // Fix common email client quirks without breaking cid: URLs - return sanitizedContent + sanitizedContent = sanitizedContent // Fix for Outlook WebVML content .replace(/