compose mime
This commit is contained in:
parent
b025baa6c1
commit
96d6d50e50
@ -42,21 +42,16 @@ export interface Account {
|
||||
}
|
||||
|
||||
export interface Email {
|
||||
id: number;
|
||||
accountId: number;
|
||||
id: string;
|
||||
from: string;
|
||||
fromName: string;
|
||||
fromName?: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
body: string;
|
||||
content: string;
|
||||
date: string;
|
||||
read: boolean;
|
||||
starred: boolean;
|
||||
folder: string;
|
||||
cc?: string;
|
||||
bcc?: string;
|
||||
flags?: string[];
|
||||
raw: string;
|
||||
attachments?: { name: string; url: string }[];
|
||||
}
|
||||
|
||||
interface Attachment {
|
||||
@ -113,7 +108,7 @@ function EmailContent({ email }: { email: Email }) {
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (!email.body) {
|
||||
if (!email.content) {
|
||||
if (mounted) {
|
||||
setContent(<div className="text-gray-500">No content available</div>);
|
||||
setIsLoading(false);
|
||||
@ -121,7 +116,7 @@ function EmailContent({ email }: { email: Email }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formattedEmail = email.body.trim();
|
||||
const formattedEmail = email.content.trim();
|
||||
if (!formattedEmail) {
|
||||
if (mounted) {
|
||||
setContent(<div className="text-gray-500">No content available</div>);
|
||||
@ -167,7 +162,7 @@ function EmailContent({ email }: { email: Email }) {
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [email?.body]);
|
||||
}, [email?.content]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@ -263,12 +258,12 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
||||
|
||||
async function loadReplyContent() {
|
||||
try {
|
||||
if (!email.body) {
|
||||
if (!email.content) {
|
||||
if (mounted) setContent('');
|
||||
return;
|
||||
}
|
||||
|
||||
const decoded = await decodeEmail(email.body);
|
||||
const decoded = await decodeEmail(email.content);
|
||||
|
||||
if (mounted) {
|
||||
let formattedContent = '';
|
||||
@ -278,7 +273,7 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
||||
<div class="forwarded-message">
|
||||
<p>---------- Forwarded message ---------</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>To: ${decoded.to || ''}</p>
|
||||
<br>
|
||||
@ -288,7 +283,7 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
||||
} else {
|
||||
formattedContent = `
|
||||
<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>
|
||||
${decoded.html || `<pre>${decoded.text || ''}</pre>`}
|
||||
</blockquote>
|
||||
@ -313,7 +308,7 @@ function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-al
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [email.body, type]);
|
||||
}, [email.content, type]);
|
||||
|
||||
if (error) {
|
||||
return <div className="text-red-500">{error}</div>;
|
||||
@ -336,14 +331,14 @@ function EmailPreview({ email }: { email: Email }) {
|
||||
let mounted = true;
|
||||
|
||||
async function loadPreview() {
|
||||
if (!email?.body) {
|
||||
if (!email?.content) {
|
||||
if (mounted) setPreview('No content available');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const decoded = await decodeEmail(email.body);
|
||||
const decoded = await decodeEmail(email.content);
|
||||
if (mounted) {
|
||||
if (decoded.text) {
|
||||
setPreview(decoded.text.substring(0, 150) + '...');
|
||||
@ -371,7 +366,7 @@ function EmailPreview({ email }: { email: Email }) {
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [email?.body]);
|
||||
}, [email?.content]);
|
||||
|
||||
if (isLoading) {
|
||||
return <span className="text-gray-400">Loading preview...</span>;
|
||||
@ -1078,45 +1073,51 @@ export default function CourrierPage() {
|
||||
}, [availableFolders]);
|
||||
|
||||
// Update the email list item to match header checkbox alignment
|
||||
const renderEmailListItem = (email: Email) => {
|
||||
return (
|
||||
<div
|
||||
key={email.id}
|
||||
className={`flex items-center gap-3 px-4 py-2 hover:bg-gray-50/80 cursor-pointer ${
|
||||
selectedEmail?.id === email.id ? 'bg-blue-50/50' : ''
|
||||
} ${!email.read ? 'bg-blue-50/20' : ''}`}
|
||||
onClick={() => handleEmailSelect(email.id)}
|
||||
>
|
||||
const renderEmailListItem = (email: Email) => (
|
||||
<div
|
||||
key={email.id}
|
||||
className={`p-3 hover:bg-gray-50/50 transition-colors cursor-pointer flex items-start gap-3 ${
|
||||
selectedEmail?.id === email.id ? 'bg-gray-50/80' : ''
|
||||
}`}
|
||||
onClick={() => handleEmailSelect(email.id)}
|
||||
>
|
||||
<div className="flex-none pt-1">
|
||||
<Checkbox
|
||||
checked={selectedEmails.includes(email.id.toString())}
|
||||
onCheckedChange={(checked) => {
|
||||
const e = { target: { checked }, stopPropagation: () => {} } as React.ChangeEvent<HTMLInputElement>;
|
||||
handleEmailCheckbox(e, email.id);
|
||||
}}
|
||||
checked={selectedEmails.includes(email.id)}
|
||||
onCheckedChange={(checked) => toggleEmailSelection(email.id)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
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 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) => {
|
||||
setCurrentView(newMailbox);
|
||||
@ -1355,6 +1356,14 @@ export default function CourrierPage() {
|
||||
</AlertDialog>
|
||||
);
|
||||
|
||||
const toggleEmailSelection = (emailId: string) => {
|
||||
setSelectedEmails((prev) =>
|
||||
prev.includes(emailId)
|
||||
? prev.filter((id) => id !== emailId)
|
||||
: [...prev, emailId]
|
||||
);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<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