compose mime
This commit is contained in:
parent
e7a627a322
commit
9a46e8839c
@ -94,23 +94,35 @@ export default function ComposeEmail({
|
|||||||
try {
|
try {
|
||||||
const originalContent = replyTo?.body || forwardFrom?.body || '';
|
const originalContent = replyTo?.body || forwardFrom?.body || '';
|
||||||
|
|
||||||
const response = await fetch('/api/parse-email', {
|
// Create initial content without waiting for parsing
|
||||||
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();
|
|
||||||
|
|
||||||
content = `
|
content = `
|
||||||
<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;">
|
<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;">
|
||||||
<br/>
|
<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 ? `
|
${forwardFrom ? `
|
||||||
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
||||||
---------- Forwarded message ---------<br/>
|
---------- Forwarded message ---------<br/>
|
||||||
@ -120,43 +132,38 @@ export default function ComposeEmail({
|
|||||||
To: ${forwardFrom.to}<br/>
|
To: ${forwardFrom.to}<br/>
|
||||||
${forwardFrom.cc ? `Cc: ${forwardFrom.cc}<br/>` : ''}
|
${forwardFrom.cc ? `Cc: ${forwardFrom.cc}<br/>` : ''}
|
||||||
<br/>
|
<br/>
|
||||||
${parsed.html || parsed.text}
|
${decodedContent.html || decodedContent.text || 'No content available'}
|
||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
<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:
|
On ${new Date(replyTo?.date || '').toLocaleString()}, ${replyTo?.from} wrote:
|
||||||
</div>
|
</div>
|
||||||
<blockquote style="margin: 0; padding-left: 1em; border-left: 2px solid #e5e7eb; color: #6b7280;">
|
<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>
|
</blockquote>
|
||||||
`}
|
`}
|
||||||
</div>
|
`;
|
||||||
`;
|
|
||||||
|
// Replace placeholder with actual content
|
||||||
|
const placeholder = composeBodyRef.current.querySelector('#reply-placeholder');
|
||||||
|
if (placeholder) {
|
||||||
|
placeholder.insertAdjacentHTML('beforebegin', quotedContent);
|
||||||
|
placeholder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing email:', 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 {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content = `<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;"></div>`;
|
content = `<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px; color: #000000;"></div>`;
|
||||||
}
|
|
||||||
|
|
||||||
if (composeBodyRef.current) {
|
|
||||||
composeBodyRef.current.innerHTML = content;
|
composeBodyRef.current.innerHTML = content;
|
||||||
setLocalContent(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;
|
if (!composeArea) return;
|
||||||
|
|
||||||
const content = composeArea.innerHTML;
|
const content = composeArea.innerHTML;
|
||||||
setLocalContent(content);
|
if (!content.trim()) {
|
||||||
setComposeBody(content);
|
setLocalContent('');
|
||||||
|
setComposeBody('');
|
||||||
|
} else {
|
||||||
|
setLocalContent(content);
|
||||||
|
setComposeBody(content);
|
||||||
|
}
|
||||||
|
|
||||||
if (onBodyChange) {
|
if (onBodyChange) {
|
||||||
onBodyChange(content);
|
onBodyChange(content);
|
||||||
@ -179,40 +191,20 @@ export default function ComposeEmail({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSendEmail = async () => {
|
const handleSendEmail = async () => {
|
||||||
// Ensure we have content before sending
|
if (!composeBodyRef.current) return;
|
||||||
if (!composeBodyRef.current) {
|
|
||||||
console.error('Compose body ref is not available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeArea = composeBodyRef.current.querySelector('.compose-area');
|
const composeArea = composeBodyRef.current.querySelector('.compose-area');
|
||||||
if (!composeArea) {
|
if (!composeArea) return;
|
||||||
console.error('Compose area not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current content
|
|
||||||
const content = composeArea.innerHTML;
|
const content = composeArea.innerHTML;
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
console.error('Email content is empty');
|
console.error('Email content is empty');
|
||||||
return;
|
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 {
|
try {
|
||||||
|
const encodedContent = await encodeComposeContent(content);
|
||||||
|
setComposeBody(encodedContent);
|
||||||
await handleSend();
|
await handleSend();
|
||||||
setShowCompose(false);
|
setShowCompose(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -3,73 +3,47 @@
|
|||||||
* Handles basic email content without creating nested structures
|
* Handles basic email content without creating nested structures
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function decodeComposeContent(content: string): string {
|
import { simpleParser } from 'mailparser';
|
||||||
if (!content) return '';
|
|
||||||
|
|
||||||
// Basic HTML cleaning without creating nested structures
|
interface ParsedContent {
|
||||||
let cleaned = content
|
html: string | null;
|
||||||
// Remove script and style tags
|
text: string | null;
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeComposeContent(content: string): string {
|
export async function decodeComposeContent(content: string): Promise<ParsedContent> {
|
||||||
if (!content) return '';
|
if (!content.trim()) {
|
||||||
|
return { html: null, text: null };
|
||||||
|
}
|
||||||
|
|
||||||
// Basic HTML encoding without adding structure
|
try {
|
||||||
const encoded = content
|
const parsed = await simpleParser(content);
|
||||||
.replace(/&/g, '&')
|
return {
|
||||||
.replace(/</g, '<')
|
html: parsed.html || null,
|
||||||
.replace(/>/g, '>')
|
text: parsed.text || null
|
||||||
.replace(/"/g, '"')
|
};
|
||||||
.replace(/'/g, ''')
|
} catch (error) {
|
||||||
.replace(/\n/g, '<br>');
|
console.error('Error parsing email content:', error);
|
||||||
|
return {
|
||||||
return encoded;
|
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