From bb2f5d37c8f6e6bb1e4851ff20184b920769b9be Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 10 Jun 2025 02:20:17 +0200 Subject: [PATCH] Add email reply functionality to thread view - Add reply button to each email in thread timeline - Pre-fill composer with recipient, subject, and quoted content when replying - Remove readonly constraint from recipient field in composer - Handle reply-to-email event to populate composer with proper reply data --- components/EmailCommunication.vue | 26 ++++++++++++++++++++++++++ components/EmailComposer.vue | 16 +++++++++++++++- components/EmailThreadView.vue | 20 ++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/components/EmailCommunication.vue b/components/EmailCommunication.vue index e194d9e..0060bfa 100644 --- a/components/EmailCommunication.vue +++ b/components/EmailCommunication.vue @@ -30,6 +30,7 @@ @@ -67,6 +69,7 @@ const emit = defineEmits(); const isConnected = ref(false); const connectedEmail = ref(''); const threadView = ref>(); +const emailComposer = ref>(); const checkConnection = () => { // Check if we have a session and connected email @@ -100,6 +103,29 @@ const onEoiGenerated = () => { emit('interestUpdated'); }; +const onReplyToEmail = (email: any, thread: any) => { + // Extract the original sender's email address + const replyTo = email.direction === 'received' ? email.from : email.to; + const cleanEmail = replyTo.match(/<(.+)>/)?.[1] || replyTo; + + // Prepare reply content with quote + const quotedContent = email.body + .split('\n') + .map((line: string) => `> ${line}`) + .join('\n'); + + const replyBody = `\n\n\nOn ${new Date(email.timestamp).toLocaleString()}, ${email.from} wrote:\n${quotedContent}`; + + // Pre-fill the composer with reply data + if (emailComposer.value) { + (emailComposer.value as any).setReplyData({ + to: cleanEmail, + subject: thread.subject.startsWith('Re: ') ? thread.subject : `Re: ${thread.subject}`, + body: replyBody + }); + } +}; + const disconnect = () => { // Clear session storage sessionStorage.removeItem('emailSessionId'); diff --git a/components/EmailComposer.vue b/components/EmailComposer.vue index f81f4c3..97ea9bc 100644 --- a/components/EmailComposer.vue +++ b/components/EmailComposer.vue @@ -14,7 +14,6 @@ prepend-inner-icon="mdi-email-outline" :rules="[rules.required, rules.email]" :disabled="sending" - readonly /> { localStorage.setItem('emailSignatureConfig', JSON.stringify(newConfig)); }, { deep: true }); }); + +// Method to set reply data +const setReplyData = (replyData: { to: string; subject: string; body: string }) => { + email.value.to = replyData.to; + email.value.subject = replyData.subject; + email.value.body = replyData.body; + + // Scroll to the top of the composer + form.value?.$el.scrollIntoView({ behavior: 'smooth', block: 'start' }); +}; + +// Expose the setReplyData method to parent +defineExpose({ + setReplyData +}); diff --git a/components/EmailThreadView.vue b/components/EmailThreadView.vue index 2cde014..01bbac1 100644 --- a/components/EmailThreadView.vue +++ b/components/EmailThreadView.vue @@ -90,6 +90,18 @@ + + + + Reply + + @@ -129,6 +141,9 @@ interface EmailThread { } const props = defineProps(); +const emit = defineEmits<{ + 'reply-to-email': [email: EmailMessage, thread: EmailThread]; +}>(); const user = useDirectusUser(); const toast = useToast(); @@ -237,6 +252,11 @@ const loadEmails = async () => { } }; +// Reply to an email +const replyToEmail = (email: EmailMessage, thread: EmailThread) => { + emit('reply-to-email', email, thread); +}; + // Reload emails when an email is sent const reloadEmails = () => { loadEmails();