compose mime
This commit is contained in:
parent
e7a627a322
commit
9a46e8839c
@ -94,23 +94,35 @@ export default function ComposeEmail({
|
||||
try {
|
||||
const originalContent = replyTo?.body || forwardFrom?.body || '';
|
||||
|
||||
const response = await fetch('/api/parse-email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ emailContent: originalContent }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to parse email');
|
||||
}
|
||||
|
||||
const parsed = await response.json();
|
||||
|
||||
// Create initial content without waiting for parsing
|
||||
content = `
|
||||
<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;">
|
||||
<br/>
|
||||
<div id="reply-placeholder">Loading original message...</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Set initial content immediately
|
||||
composeBodyRef.current.innerHTML = content;
|
||||
setLocalContent(content);
|
||||
|
||||
// Place cursor at the beginning
|
||||
const composeArea = composeBodyRef.current.querySelector('.compose-area');
|
||||
if (composeArea) {
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.setStart(composeArea, 0);
|
||||
range.collapse(true);
|
||||
sel?.removeAllRanges();
|
||||
sel?.addRange(range);
|
||||
(composeArea as HTMLElement).focus();
|
||||
}
|
||||
|
||||
// Now parse the email content
|
||||
if (originalContent.trim()) {
|
||||
const decodedContent = await decodeComposeContent(originalContent);
|
||||
|
||||
const quotedContent = `
|
||||
${forwardFrom ? `
|
||||
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
||||
---------- Forwarded message ---------<br/>
|
||||
@ -120,43 +132,38 @@ export default function ComposeEmail({
|
||||
To: ${forwardFrom.to}<br/>
|
||||
${forwardFrom.cc ? `Cc: ${forwardFrom.cc}<br/>` : ''}
|
||||
<br/>
|
||||
${parsed.html || parsed.text}
|
||||
${decodedContent.html || decodedContent.text || 'No content available'}
|
||||
</div>
|
||||
` : `
|
||||
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
||||
On ${new Date(replyTo?.date || '').toLocaleString()}, ${replyTo?.from} wrote:
|
||||
</div>
|
||||
<blockquote style="margin: 0; padding-left: 1em; border-left: 2px solid #e5e7eb; color: #6b7280;">
|
||||
${parsed.html || parsed.text}
|
||||
${decodedContent.html || decodedContent.text || 'No content available'}
|
||||
</blockquote>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
|
||||
// Replace placeholder with actual content
|
||||
const placeholder = composeBodyRef.current.querySelector('#reply-placeholder');
|
||||
if (placeholder) {
|
||||
placeholder.insertAdjacentHTML('beforebegin', quotedContent);
|
||||
placeholder.remove();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
content = `<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;"></div>`;
|
||||
const placeholder = composeBodyRef.current.querySelector('#reply-placeholder');
|
||||
if (placeholder) {
|
||||
placeholder.textContent = 'Error loading original message.';
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
content = `<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;"></div>`;
|
||||
}
|
||||
|
||||
if (composeBodyRef.current) {
|
||||
composeBodyRef.current.innerHTML = content;
|
||||
setLocalContent(content);
|
||||
|
||||
// Place cursor at the beginning of the compose area
|
||||
const composeArea = composeBodyRef.current.querySelector('.compose-area');
|
||||
if (composeArea) {
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.setStart(composeArea, 0);
|
||||
range.collapse(true);
|
||||
sel?.removeAllRanges();
|
||||
sel?.addRange(range);
|
||||
(composeArea as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -170,8 +177,13 @@ export default function ComposeEmail({
|
||||
if (!composeArea) return;
|
||||
|
||||
const content = composeArea.innerHTML;
|
||||
setLocalContent(content);
|
||||
setComposeBody(content);
|
||||
if (!content.trim()) {
|
||||
setLocalContent('');
|
||||
setComposeBody('');
|
||||
} else {
|
||||
setLocalContent(content);
|
||||
setComposeBody(content);
|
||||
}
|
||||
|
||||
if (onBodyChange) {
|
||||
onBodyChange(content);
|
||||
@ -179,40 +191,20 @@ export default function ComposeEmail({
|
||||
};
|
||||
|
||||
const handleSendEmail = async () => {
|
||||
// Ensure we have content before sending
|
||||
if (!composeBodyRef.current) {
|
||||
console.error('Compose body ref is not available');
|
||||
return;
|
||||
}
|
||||
if (!composeBodyRef.current) return;
|
||||
|
||||
const composeArea = composeBodyRef.current.querySelector('.compose-area');
|
||||
if (!composeArea) {
|
||||
console.error('Compose area not found');
|
||||
return;
|
||||
}
|
||||
if (!composeArea) return;
|
||||
|
||||
// Get the current content
|
||||
const content = composeArea.innerHTML;
|
||||
if (!content.trim()) {
|
||||
console.error('Email content is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create MIME headers
|
||||
const mimeHeaders = {
|
||||
'MIME-Version': '1.0',
|
||||
'Content-Type': 'text/html; charset="utf-8"',
|
||||
'Content-Transfer-Encoding': 'quoted-printable'
|
||||
};
|
||||
|
||||
// Combine headers and content
|
||||
const mimeContent = Object.entries(mimeHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n') + '\n\n' + content;
|
||||
|
||||
setComposeBody(mimeContent);
|
||||
|
||||
try {
|
||||
const encodedContent = await encodeComposeContent(content);
|
||||
setComposeBody(encodedContent);
|
||||
await handleSend();
|
||||
setShowCompose(false);
|
||||
} catch (error) {
|
||||
|
||||
@ -3,73 +3,47 @@
|
||||
* Handles basic email content without creating nested structures
|
||||
*/
|
||||
|
||||
export function decodeComposeContent(content: string): string {
|
||||
if (!content) return '';
|
||||
|
||||
// Basic HTML cleaning without creating nested structures
|
||||
let cleaned = content
|
||||
// Remove script and style tags
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||
// Remove meta tags
|
||||
.replace(/<meta[^>]*>/gi, '')
|
||||
// Remove head and title
|
||||
.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '')
|
||||
.replace(/<title[^>]*>[\s\S]*?<\/title>/gi, '')
|
||||
// Remove body tags
|
||||
.replace(/<body[^>]*>/gi, '')
|
||||
.replace(/<\/body>/gi, '')
|
||||
// Remove html tags
|
||||
.replace(/<html[^>]*>/gi, '')
|
||||
.replace(/<\/html>/gi, '')
|
||||
// Handle basic formatting
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<p[^>]*>/gi, '\n')
|
||||
.replace(/<\/p>/gi, '\n')
|
||||
// Handle lists
|
||||
.replace(/<ul[^>]*>/gi, '\n')
|
||||
.replace(/<\/ul>/gi, '\n')
|
||||
.replace(/<ol[^>]*>/gi, '\n')
|
||||
.replace(/<\/ol>/gi, '\n')
|
||||
.replace(/<li[^>]*>/gi, '• ')
|
||||
.replace(/<\/li>/gi, '\n')
|
||||
// Handle basic text formatting
|
||||
.replace(/<strong[^>]*>/gi, '**')
|
||||
.replace(/<\/strong>/gi, '**')
|
||||
.replace(/<b[^>]*>/gi, '**')
|
||||
.replace(/<\/b>/gi, '**')
|
||||
.replace(/<em[^>]*>/gi, '*')
|
||||
.replace(/<\/em>/gi, '*')
|
||||
.replace(/<i[^>]*>/gi, '*')
|
||||
.replace(/<\/i>/gi, '*')
|
||||
// Handle links
|
||||
.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '$2 ($1)')
|
||||
// Handle basic entities
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
// Clean up whitespace
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Do NOT wrap in additional divs
|
||||
return cleaned;
|
||||
import { simpleParser } from 'mailparser';
|
||||
|
||||
interface ParsedContent {
|
||||
html: string | null;
|
||||
text: string | null;
|
||||
}
|
||||
|
||||
export function encodeComposeContent(content: string): string {
|
||||
if (!content) return '';
|
||||
|
||||
// Basic HTML encoding without adding structure
|
||||
const encoded = content
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
return encoded;
|
||||
export async function decodeComposeContent(content: string): Promise<ParsedContent> {
|
||||
if (!content.trim()) {
|
||||
return { html: null, text: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = await simpleParser(content);
|
||||
return {
|
||||
html: parsed.html || null,
|
||||
text: parsed.text || null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error parsing email content:', error);
|
||||
return {
|
||||
html: content,
|
||||
text: content
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function encodeComposeContent(content: string): Promise<string> {
|
||||
if (!content.trim()) {
|
||||
throw new Error('Email content is empty');
|
||||
}
|
||||
|
||||
// Create MIME headers
|
||||
const mimeHeaders = {
|
||||
'MIME-Version': '1.0',
|
||||
'Content-Type': 'text/html; charset="utf-8"',
|
||||
'Content-Transfer-Encoding': 'quoted-printable'
|
||||
};
|
||||
|
||||
// Combine headers and content
|
||||
return Object.entries(mimeHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n') + '\n\n' + content;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user