courrier preview

This commit is contained in:
alma 2025-05-01 11:44:58 +02:00
parent 1ced248940
commit 0251b08113
2 changed files with 263 additions and 76 deletions

View File

@ -172,6 +172,16 @@ export default function ComposeEmail(props: ComposeEmailProps) {
});
setEmailContent(content);
}
// Handle any attachments from reply (e.g., inline images extracted as attachments)
if (formatted.attachments && formatted.attachments.length > 0) {
const formattedAttachments = formatted.attachments.map(att => ({
name: att.filename || 'attachment',
type: att.contentType || 'application/octet-stream',
content: att.content || ''
}));
setAttachments(formattedAttachments);
}
}
else if (type === 'forward') {
// Get formatted data for forward
@ -209,9 +219,10 @@ export default function ComposeEmail(props: ComposeEmailProps) {
setEmailContent(content);
}
// If the original email has attachments, include them
if (initialEmail.attachments && initialEmail.attachments.length > 0) {
const formattedAttachments = initialEmail.attachments.map(att => ({
// Handle attachments for forward (original attachments + extracted inline images)
if (formatted.attachments && formatted.attachments.length > 0) {
console.log(`Processing ${formatted.attachments.length} attachments for forwarded email`);
const formattedAttachments = formatted.attachments.map(att => ({
name: att.filename || 'attachment',
type: att.contentType || 'application/octet-stream',
content: att.content || ''

View File

@ -36,6 +36,11 @@ export interface FormattedEmail {
cc?: string;
subject: string;
content: EmailContent;
attachments?: Array<{
filename: string;
contentType: string;
content?: string;
}>;
}
/**
@ -252,6 +257,71 @@ function getFormattedHeaderInfo(email: any): {
return { fromStr, toStr, ccStr, dateStr, subject };
}
/**
* Extract image attachments from HTML content
*/
function extractInlineImages(htmlContent: string): Array<{
filename: string;
contentType: string;
content?: string;
}> {
const images: Array<{
filename: string;
contentType: string;
content?: string;
}> = [];
try {
if (!htmlContent || typeof window === 'undefined') return images;
// Create a temporary DOM element to parse the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = htmlContent;
// Find all image elements
const imgElements = tempDiv.querySelectorAll('img');
// Process each image
imgElements.forEach((img, index) => {
const src = img.getAttribute('src');
if (!src) return;
// Only process data URLs and non-tracking pixels
if (src.startsWith('data:image')) {
const contentType = src.split(',')[0].split(':')[1].split(';')[0];
const imageData = src.split(',')[1];
// Skip tiny images (likely tracking pixels)
if (imageData.length < 100) return;
images.push({
filename: `inline-image-${index + 1}.${contentType.split('/')[1] || 'png'}`,
contentType,
content: imageData
});
// Replace the image source with a placeholder
img.setAttribute('src', `cid:inline-image-${index + 1}`);
}
else if (src.startsWith('cid:')) {
// Already a CID reference, just add a placeholder
const cid = src.substring(4);
images.push({
filename: `${cid}.png`,
contentType: 'image/png'
});
}
});
// Update the HTML content to use the placeholders
htmlContent = tempDiv.innerHTML;
} catch (error) {
console.error('Error extracting inline images:', error);
}
return images;
}
/**
* Format email for reply
*/
@ -276,45 +346,79 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
// Get header information
const { fromStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail);
// Extract content using centralized utility
// Extract content using centralized utility - get simpler text version when possible
const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(originalEmail);
// Prefer HTML content when available, but simplify it for Quill compatibility
// Simpler approach - prefer text content when available for clean replies
let replyBody = '';
let textReply = '';
// Create a header that works in both HTML and plain text
const headerHtml = `<div style="margin-top: 20px; margin-bottom: 10px; color: #666;">On ${dateStr}, ${fromStr} wrote:</div>`;
if (originalHtmlContent) {
try {
// Sanitize the original HTML to remove problematic elements
const sanitizedHtml = sanitizeHtml(originalHtmlContent);
// Use extracted text content when available for cleaner replies
if (originalTextContent) {
// Use text content with proper line breaks - limit to a reasonable size
const maxChars = 1500;
const truncatedText = originalTextContent.length > maxChars
? originalTextContent.slice(0, maxChars) + '... [message truncated]'
: originalTextContent;
// Wrap the content in a blockquote with styling
replyBody = `
${headerHtml}
<blockquote style="margin: 10px 0; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${sanitizedHtml}
</blockquote>
`;
} catch (error) {
console.error('Error processing HTML for reply:', error);
// Fallback to text if HTML processing fails
replyBody = `
${headerHtml}
<blockquote style="margin: 10px 0; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${originalTextContent.replace(/\n/g, '<br>')}
</blockquote>
`;
}
} else if (originalTextContent) {
// Use text content with proper line breaks
replyBody = `
${headerHtml}
<blockquote style="margin: 10px 0; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${originalTextContent.replace(/\n/g, '<br>')}
${truncatedText.replace(/\n/g, '<br>')}
</blockquote>
`;
textReply = `
On ${dateStr}, ${fromStr} wrote:
> ${truncatedText.split('\n').join('\n> ')}
`;
}
// If no text, try to sanitize and simplify HTML
else if (originalHtmlContent) {
try {
// Sanitize the original HTML to remove problematic elements and simplify
const sanitizedHtml = sanitizeHtml(originalHtmlContent);
// Extract the text content from the sanitized HTML for the plaintext version
const tempDiv = document.createElement('div');
tempDiv.innerHTML = sanitizedHtml;
const extractedText = tempDiv.textContent || tempDiv.innerText || '';
// Limit to a reasonable size
const maxChars = 1500;
const truncatedText = extractedText.length > maxChars
? extractedText.slice(0, maxChars) + '... [message truncated]'
: extractedText;
replyBody = `
${headerHtml}
<blockquote style="margin: 10px 0; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${truncatedText.replace(/\n/g, '<br>')}
</blockquote>
`;
textReply = `
On ${dateStr}, ${fromStr} wrote:
> ${truncatedText.split('\n').join('\n> ')}
`;
} catch (error) {
console.error('Error processing HTML for reply:', error);
// Fallback to a basic template if everything fails
replyBody = `
${headerHtml}
<blockquote style="margin: 10px 0; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
[Original message content could not be processed]
</blockquote>
`;
textReply = `
On ${dateStr}, ${fromStr} wrote:
> [Original message content could not be processed]
`;
}
} else {
// Empty or unrecognized content
replyBody = `
@ -323,27 +427,31 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
[Original message content not available]
</blockquote>
`;
textReply = `
On ${dateStr}, ${fromStr} wrote:
> [Original message content not available]
`;
}
// Process the content with proper direction
const processed = processContentWithDirection(replyBody);
// Create plain text content
const textContent = `
On ${dateStr}, ${fromStr} wrote:
> ${originalTextContent.split('\n').join('\n> ')}
`;
// Extract any inline images as attachments
const inlineImages = extractInlineImages(originalHtmlContent);
return {
to,
cc,
subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`,
content: {
text: textContent,
text: textReply.trim(),
html: processed.html,
isHtml: true,
direction: processed.direction
}
},
// Include inline images as attachments if any were found
attachments: inlineImages.length > 0 ? inlineImages : undefined
};
}
@ -367,11 +475,12 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
// Get header information
const { fromStr, toStr, ccStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail);
// Extract content using centralized utility
// Extract content using centralized utility - get simpler text version when possible
const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(originalEmail);
// Prefer HTML content when available, but simplify it for Quill compatibility
// Simpler approach - prefer text content when available for clean forwards
let forwardBody = '';
let textForward = '';
// Create metadata header that works in both HTML and plain text
const headerHtml = `
@ -385,36 +494,87 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
</div>
`;
if (originalHtmlContent) {
try {
// Sanitize the original HTML to remove problematic elements
const sanitizedHtml = sanitizeHtml(originalHtmlContent);
// Use extracted text content when available for cleaner forwards
if (originalTextContent) {
// Use text content with proper line breaks - limit to a reasonable size
const maxChars = 2000;
const truncatedText = originalTextContent.length > maxChars
? originalTextContent.slice(0, maxChars) + '... [message truncated]'
: originalTextContent;
// Wrap the content in a blockquote with styling
forwardBody = `
${headerHtml}
<blockquote style="margin-top: 10px; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${sanitizedHtml}
</blockquote>
`;
} catch (error) {
console.error('Error processing HTML for forward:', error);
// Fallback to text if HTML processing fails
forwardBody = `
${headerHtml}
<blockquote style="margin-top: 10px; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${originalTextContent.replace(/\n/g, '<br>')}
</blockquote>
`;
}
} else if (originalTextContent) {
// Use text content with proper line breaks
forwardBody = `
${headerHtml}
<blockquote style="margin-top: 10px; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${originalTextContent.replace(/\n/g, '<br>')}
${truncatedText.replace(/\n/g, '<br>')}
</blockquote>
`;
textForward = `
---------- Forwarded message ---------
From: ${fromStr}
Date: ${dateStr}
Subject: ${subject || ''}
To: ${toStr}
${ccStr ? `Cc: ${ccStr}\n` : ''}
${truncatedText}
`;
}
// If no text, try to sanitize and simplify HTML
else if (originalHtmlContent) {
try {
// Sanitize the original HTML to remove problematic elements and simplify
const sanitizedHtml = sanitizeHtml(originalHtmlContent);
// Extract the text content from the sanitized HTML for the plaintext version
const tempDiv = document.createElement('div');
tempDiv.innerHTML = sanitizedHtml;
const extractedText = tempDiv.textContent || tempDiv.innerText || '';
// Limit to a reasonable size
const maxChars = 2000;
const truncatedText = extractedText.length > maxChars
? extractedText.slice(0, maxChars) + '... [message truncated]'
: extractedText;
forwardBody = `
${headerHtml}
<blockquote style="margin-top: 10px; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
${truncatedText.replace(/\n/g, '<br>')}
</blockquote>
`;
textForward = `
---------- Forwarded message ---------
From: ${fromStr}
Date: ${dateStr}
Subject: ${subject || ''}
To: ${toStr}
${ccStr ? `Cc: ${ccStr}\n` : ''}
${truncatedText}
`;
} catch (error) {
console.error('Error processing HTML for forward:', error);
// Fallback to a basic template if everything fails
forwardBody = `
${headerHtml}
<blockquote style="margin-top: 10px; padding-left: 10px; border-left: 2px solid #ddd; color: #505050;">
[Original message content could not be processed]
</blockquote>
`;
textForward = `
---------- Forwarded message ---------
From: ${fromStr}
Date: ${dateStr}
Subject: ${subject || ''}
To: ${toStr}
${ccStr ? `Cc: ${ccStr}\n` : ''}
[Original message content could not be processed]
`;
}
} else {
// Empty or unrecognized content
forwardBody = `
@ -423,14 +583,8 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
[Original message content not available]
</blockquote>
`;
}
// Process the content with proper direction
const processed = processContentWithDirection(forwardBody);
// Create plain text content
const textContent = `
textForward = `
---------- Forwarded message ---------
From: ${fromStr}
Date: ${dateStr}
@ -438,18 +592,40 @@ Subject: ${subject || ''}
To: ${toStr}
${ccStr ? `Cc: ${ccStr}\n` : ''}
${originalTextContent}
`;
[Original message content not available]
`;
}
// Process the content with proper direction
const processed = processContentWithDirection(forwardBody);
// Check if the original email has attachments
const originalAttachments = originalEmail.attachments || [];
// Extract any inline images and add to attachments
const inlineImages = extractInlineImages(originalHtmlContent);
// Combine original attachments and inline images
const combinedAttachments = [
...originalAttachments.map(att => ({
filename: att.filename || 'attachment',
contentType: att.contentType || 'application/octet-stream',
content: att.content
})),
...inlineImages
];
return {
to: '',
subject: subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`,
content: {
text: textContent,
text: textForward.trim(),
html: processed.html,
isHtml: true,
direction: 'ltr' as const
}
direction: 'ltr'
},
// Include attachments if any were found
attachments: combinedAttachments.length > 0 ? combinedAttachments : undefined
};
}