compose mime
This commit is contained in:
parent
b025baa6c1
commit
96d6d50e50
@ -42,21 +42,16 @@ export interface Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Email {
|
export interface Email {
|
||||||
id: number;
|
id: string;
|
||||||
accountId: number;
|
|
||||||
from: string;
|
from: string;
|
||||||
fromName: string;
|
fromName?: string;
|
||||||
to: string;
|
to: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
content: string;
|
||||||
date: string;
|
date: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
starred: boolean;
|
starred: boolean;
|
||||||
folder: string;
|
attachments?: { name: string; url: string }[];
|
||||||
cc?: string;
|
|
||||||
bcc?: string;
|
|
||||||
flags?: string[];
|
|
||||||
raw: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Attachment {
|
interface Attachment {
|
||||||
@ -113,7 +108,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
if (!email.body) {
|
if (!email.content) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setContent(<div className="text-gray-500">No content available</div>);
|
setContent(<div className="text-gray-500">No content available</div>);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -121,7 +116,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedEmail = email.body.trim();
|
const formattedEmail = email.content.trim();
|
||||||
if (!formattedEmail) {
|
if (!formattedEmail) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setContent(<div className="text-gray-500">No content available</div>);
|
setContent(<div className="text-gray-500">No content available</div>);
|
||||||
@ -167,7 +162,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email?.body]);
|
}, [email?.content]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@ -263,12 +258,12 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
|||||||
|
|
||||||
async function loadReplyContent() {
|
async function loadReplyContent() {
|
||||||
try {
|
try {
|
||||||
if (!email.body) {
|
if (!email.content) {
|
||||||
if (mounted) setContent('');
|
if (mounted) setContent('');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoded = await decodeEmail(email.body);
|
const decoded = await decodeEmail(email.content);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
let formattedContent = '';
|
let formattedContent = '';
|
||||||
@ -278,7 +273,7 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
|||||||
<div class="forwarded-message">
|
<div class="forwarded-message">
|
||||||
<p>---------- Forwarded message ---------</p>
|
<p>---------- Forwarded message ---------</p>
|
||||||
<p>From: ${decoded.from || ''}</p>
|
<p>From: ${decoded.from || ''}</p>
|
||||||
<p>Date: ${formatDate(decoded.date)}</p>
|
<p>Date: ${formatDate(decoded.date ? new Date(decoded.date) : null)}</p>
|
||||||
<p>Subject: ${decoded.subject || ''}</p>
|
<p>Subject: ${decoded.subject || ''}</p>
|
||||||
<p>To: ${decoded.to || ''}</p>
|
<p>To: ${decoded.to || ''}</p>
|
||||||
<br>
|
<br>
|
||||||
@ -288,7 +283,7 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
|||||||
} else {
|
} else {
|
||||||
formattedContent = `
|
formattedContent = `
|
||||||
<div class="quoted-message">
|
<div class="quoted-message">
|
||||||
<p>On ${formatDate(decoded.date)}, ${decoded.from || ''} wrote:</p>
|
<p>On ${formatDate(decoded.date ? new Date(decoded.date) : null)}, ${decoded.from || ''} wrote:</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
${decoded.html || `<pre>${decoded.text || ''}</pre>`}
|
${decoded.html || `<pre>${decoded.text || ''}</pre>`}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
@ -313,7 +308,7 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email.body, type]);
|
}, [email.content, type]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-red-500">{error}</div>;
|
return <div className="text-red-500">{error}</div>;
|
||||||
@ -336,14 +331,14 @@ function EmailPreview({ email }: { email: Email }) {
|
|||||||
let mounted = true;
|
let mounted = true;
|
||||||
|
|
||||||
async function loadPreview() {
|
async function loadPreview() {
|
||||||
if (!email?.body) {
|
if (!email?.content) {
|
||||||
if (mounted) setPreview('No content available');
|
if (mounted) setPreview('No content available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const decoded = await decodeEmail(email.body);
|
const decoded = await decodeEmail(email.content);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (decoded.text) {
|
if (decoded.text) {
|
||||||
setPreview(decoded.text.substring(0, 150) + '...');
|
setPreview(decoded.text.substring(0, 150) + '...');
|
||||||
@ -371,7 +366,7 @@ function EmailPreview({ email }: { email: Email }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email?.body]);
|
}, [email?.content]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <span className="text-gray-400">Loading preview...</span>;
|
return <span className="text-gray-400">Loading preview...</span>;
|
||||||
@ -1078,45 +1073,51 @@ export default function CourrierPage() {
|
|||||||
}, [availableFolders]);
|
}, [availableFolders]);
|
||||||
|
|
||||||
// Update the email list item to match header checkbox alignment
|
// Update the email list item to match header checkbox alignment
|
||||||
const renderEmailListItem = (email: Email) => {
|
const renderEmailListItem = (email: Email) => (
|
||||||
return (
|
<div
|
||||||
<div
|
key={email.id}
|
||||||
key={email.id}
|
className={`p-3 hover:bg-gray-50/50 transition-colors cursor-pointer flex items-start gap-3 ${
|
||||||
className={`flex items-center gap-3 px-4 py-2 hover:bg-gray-50/80 cursor-pointer ${
|
selectedEmail?.id === email.id ? 'bg-gray-50/80' : ''
|
||||||
selectedEmail?.id === email.id ? 'bg-blue-50/50' : ''
|
}`}
|
||||||
} ${!email.read ? 'bg-blue-50/20' : ''}`}
|
onClick={() => handleEmailSelect(email.id)}
|
||||||
onClick={() => handleEmailSelect(email.id)}
|
>
|
||||||
>
|
<div className="flex-none pt-1">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedEmails.includes(email.id.toString())}
|
checked={selectedEmails.includes(email.id)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => toggleEmailSelection(email.id)}
|
||||||
const e = { target: { checked }, stopPropagation: () => {} } as React.ChangeEvent<HTMLInputElement>;
|
|
||||||
handleEmailCheckbox(e, email.id);
|
|
||||||
}}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
className="mt-0.5"
|
className="mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center justify-between gap-2">
|
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
|
||||||
<span className={`text-sm truncate ${!email.read ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
|
|
||||||
{email.fromName || email.from}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs text-gray-500 whitespace-nowrap">
|
|
||||||
{formatDate(new Date(email.date))}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-sm text-gray-900 truncate">
|
|
||||||
{email.subject || '(No subject)'}
|
|
||||||
</h3>
|
|
||||||
<div className="text-xs text-gray-500 line-clamp-2">
|
|
||||||
{generateEmailPreview(email)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="flex-1 min-w-0">
|
||||||
};
|
<div className="flex items-center justify-between gap-2 mb-1">
|
||||||
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
<span className={`text-sm truncate ${!email.read ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
|
||||||
|
{email.fromName || email.from}
|
||||||
|
</span>
|
||||||
|
{!email.read && (
|
||||||
|
<span className="w-1.5 h-1.5 bg-blue-600 rounded-full flex-shrink-0"></span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-500 whitespace-nowrap">
|
||||||
|
{formatDate(new Date(email.date))}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className={`text-sm truncate mb-0.5 ${!email.read ? 'text-gray-900' : 'text-gray-600'}`}>
|
||||||
|
{email.subject || '(No subject)'}
|
||||||
|
</h3>
|
||||||
|
<EmailPreview email={email} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-none flex items-center gap-1">
|
||||||
|
{email.starred && (
|
||||||
|
<Star className="h-4 w-4 text-yellow-400 fill-yellow-400" />
|
||||||
|
)}
|
||||||
|
{email.attachments && email.attachments.length > 0 && (
|
||||||
|
<Paperclip className="h-4 w-4 text-gray-400" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const handleMailboxChange = async (newMailbox: string) => {
|
const handleMailboxChange = async (newMailbox: string) => {
|
||||||
setCurrentView(newMailbox);
|
setCurrentView(newMailbox);
|
||||||
@ -1355,6 +1356,14 @@ export default function CourrierPage() {
|
|||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const toggleEmailSelection = (emailId: string) => {
|
||||||
|
setSelectedEmails((prev) =>
|
||||||
|
prev.includes(emailId)
|
||||||
|
? prev.filter((id) => id !== emailId)
|
||||||
|
: [...prev, emailId]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[calc(100vh-theme(spacing.12))] items-center justify-center bg-gray-100 mt-12">
|
<div className="flex h-[calc(100vh-theme(spacing.12))] items-center justify-center bg-gray-100 mt-12">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user