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
This commit is contained in:
Matt 2025-06-10 02:20:17 +02:00
parent e388779e11
commit bb2f5d37c8
3 changed files with 61 additions and 1 deletions

View File

@ -30,6 +30,7 @@
<!-- Email Composer -->
<EmailComposer
ref="emailComposer"
:interest="interest"
@sent="onEmailSent"
@eoiGenerated="onEoiGenerated"
@ -40,6 +41,7 @@
<EmailThreadView
ref="threadView"
:interest="interest"
@reply-to-email="onReplyToEmail"
/>
</template>
</v-card-text>
@ -67,6 +69,7 @@ const emit = defineEmits<Emits>();
const isConnected = ref(false);
const connectedEmail = ref('');
const threadView = ref<InstanceType<typeof EmailThreadView>>();
const emailComposer = ref<InstanceType<typeof EmailComposer>>();
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');

View File

@ -14,7 +14,6 @@
prepend-inner-icon="mdi-email-outline"
:rules="[rules.required, rules.email]"
:disabled="sending"
readonly
/>
<v-text-field
@ -329,4 +328,19 @@ onMounted(() => {
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
});
</script>

View File

@ -90,6 +90,18 @@
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
size="small"
variant="text"
color="primary"
prepend-icon="mdi-reply"
@click="replyToEmail(email, thread)"
>
Reply
</v-btn>
</v-card-actions>
</v-card>
</v-timeline-item>
</v-timeline>
@ -129,6 +141,9 @@ interface EmailThread {
}
const props = defineProps<Props>();
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();