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 --> <!-- Email Composer -->
<EmailComposer <EmailComposer
ref="emailComposer"
:interest="interest" :interest="interest"
@sent="onEmailSent" @sent="onEmailSent"
@eoiGenerated="onEoiGenerated" @eoiGenerated="onEoiGenerated"
@ -40,6 +41,7 @@
<EmailThreadView <EmailThreadView
ref="threadView" ref="threadView"
:interest="interest" :interest="interest"
@reply-to-email="onReplyToEmail"
/> />
</template> </template>
</v-card-text> </v-card-text>
@ -67,6 +69,7 @@ const emit = defineEmits<Emits>();
const isConnected = ref(false); const isConnected = ref(false);
const connectedEmail = ref(''); const connectedEmail = ref('');
const threadView = ref<InstanceType<typeof EmailThreadView>>(); const threadView = ref<InstanceType<typeof EmailThreadView>>();
const emailComposer = ref<InstanceType<typeof EmailComposer>>();
const checkConnection = () => { const checkConnection = () => {
// Check if we have a session and connected email // Check if we have a session and connected email
@ -100,6 +103,29 @@ const onEoiGenerated = () => {
emit('interestUpdated'); 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 = () => { const disconnect = () => {
// Clear session storage // Clear session storage
sessionStorage.removeItem('emailSessionId'); sessionStorage.removeItem('emailSessionId');

View File

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

View File

@ -90,6 +90,18 @@
</div> </div>
</div> </div>
</v-card-text> </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-card>
</v-timeline-item> </v-timeline-item>
</v-timeline> </v-timeline>
@ -129,6 +141,9 @@ interface EmailThread {
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
const emit = defineEmits<{
'reply-to-email': [email: EmailMessage, thread: EmailThread];
}>();
const user = useDirectusUser(); const user = useDirectusUser();
const toast = useToast(); 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 // Reload emails when an email is sent
const reloadEmails = () => { const reloadEmails = () => {
loadEmails(); loadEmails();