Add file attachment support to email composer
- Add file browser integration for selecting attachments - Display attached files with remove functionality in composer - Include attachments in email sending process - Show attachment info in email thread display - Update server endpoints to handle file attachments - Minor UI improvements to EOI status badge layout
This commit is contained in:
@@ -157,9 +157,9 @@ export default defineEventHandler(async (event) => {
|
||||
new Map(allEmails.map(email => [email.id, email])).values()
|
||||
);
|
||||
|
||||
// Sort by timestamp
|
||||
// Sort by timestamp (newest first)
|
||||
uniqueEmails.sort((a, b) =>
|
||||
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||
);
|
||||
|
||||
// Group into threads
|
||||
@@ -414,12 +414,14 @@ function groupIntoThreads(emails: EmailMessage[]): any[] {
|
||||
}
|
||||
});
|
||||
|
||||
// Convert to array format
|
||||
return Array.from(threads.entries()).map(([threadId, emails]) => ({
|
||||
id: threadId,
|
||||
subject: emails[0].subject,
|
||||
emailCount: emails.length,
|
||||
latestTimestamp: emails[emails.length - 1].timestamp,
|
||||
emails: emails
|
||||
}));
|
||||
// Convert to array format and sort threads by latest timestamp (newest first)
|
||||
return Array.from(threads.entries())
|
||||
.map(([threadId, emails]) => ({
|
||||
id: threadId,
|
||||
subject: emails[0].subject,
|
||||
emailCount: emails.length,
|
||||
latestTimestamp: emails[0].timestamp, // First email is newest since we sorted desc
|
||||
emails: emails
|
||||
}))
|
||||
.sort((a, b) => new Date(b.latestTimestamp).getTime() - new Date(a.latestTimestamp).getTime());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import { getCredentialsFromSession, decryptCredentials } from '~/server/utils/encryption';
|
||||
import { uploadFile } from '~/server/utils/minio';
|
||||
import { uploadFile, getMinioClient } from '~/server/utils/minio';
|
||||
import { updateInterest } from '~/server/utils/nocodb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
@@ -19,7 +19,8 @@ export default defineEventHandler(async (event) => {
|
||||
interestId,
|
||||
sessionId,
|
||||
includeSignature = true,
|
||||
signatureConfig
|
||||
signatureConfig,
|
||||
attachments = []
|
||||
} = body;
|
||||
|
||||
if (!to || !subject || !emailBody || !sessionId) {
|
||||
@@ -82,6 +83,36 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare email attachments
|
||||
const emailAttachments = [];
|
||||
if (attachments && attachments.length > 0) {
|
||||
const client = getMinioClient();
|
||||
|
||||
for (const attachment of attachments) {
|
||||
try {
|
||||
// Download file from MinIO
|
||||
const stream = await client.getObject('portnimaradev', attachment.path);
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
stream.on('data', (chunk) => chunks.push(chunk));
|
||||
stream.on('end', resolve);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
|
||||
const content = Buffer.concat(chunks);
|
||||
|
||||
emailAttachments.push({
|
||||
filename: attachment.name,
|
||||
content: content
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to attach file ${attachment.name}:`, error);
|
||||
// Continue with other attachments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send email
|
||||
const fromName = sig.name || defaultName;
|
||||
const info = await transporter.sendMail({
|
||||
@@ -89,7 +120,8 @@ export default defineEventHandler(async (event) => {
|
||||
to: to,
|
||||
subject: subject,
|
||||
text: emailBody, // Plain text version
|
||||
html: htmlBody // HTML version with signature
|
||||
html: htmlBody, // HTML version with signature
|
||||
attachments: emailAttachments
|
||||
});
|
||||
|
||||
// Store email in MinIO for thread history and update EOI Time Sent
|
||||
|
||||
Reference in New Issue
Block a user