KI-AGENT: E-Mails per Drag-and-drop verschiebbar machen
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 22s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 22s
Build and Push Docker Images / build-central-services-api (push) Successful in 21s
Build and Push Docker Images / build-central-services-admin (push) Successful in 21s
Build and Push Docker Images / build-docs (push) Successful in 21s

This commit is contained in:
2026-09-07 19:59:44 +02:00
parent 298f601564
commit 2c7cd17e2b

View File

@@ -17,6 +17,7 @@ type EmailMailbox = {
name: string
delimiter?: string | null
specialUse?: string | null
exists?: number
unseen?: number
}
@@ -84,6 +85,8 @@ const expandedMailboxPaths = ref<string[]>([])
const syncedMailboxPaths = ref<string[]>([])
const actionLoading = ref("")
const moveTargetMailboxPath = ref("")
const draggedMessageId = ref<string | null>(null)
const dragOverMailboxPath = ref<string | null>(null)
let deepLinkApplied = false
const selectedAccount = computed(() =>
@@ -412,8 +415,11 @@ function openComposer(mode: "reply" | "replyAll" | "forward") {
function removeMessageFromCurrentList(messageId: string) {
const currentIndex = messages.value.findIndex((message) => message.id === messageId)
const wasSelected = selectedMessage.value?.id === messageId
messages.value = messages.value.filter((message) => message.id !== messageId)
if (!wasSelected) return
const nextMessage = messages.value[currentIndex] || messages.value[currentIndex - 1] || null
selectedMessage.value = null
@@ -467,17 +473,45 @@ async function deleteSelectedMessage() {
async function moveSelectedMessage() {
if (!selectedMessage.value || !moveTargetMailboxPath.value) return
const messageId = selectedMessage.value.id
actionLoading.value = "move"
await moveMessageToMailbox(selectedMessage.value.id, moveTargetMailboxPath.value, "move")
moveTargetMailboxPath.value = ""
}
async function moveMessageToMailbox(messageId: string, mailboxPath: string, loadingKey = `move-${messageId}`) {
const message = messages.value.find((item) => item.id === messageId)
if (!message || message.mailboxPath === mailboxPath) return
actionLoading.value = loadingKey
try {
await $api(`/api/email/messages/${messageId}/move`, {
method: "POST",
body: { mailbox: moveTargetMailboxPath.value },
body: { mailbox: mailboxPath },
})
mailboxes.value = mailboxes.value.map((mailbox) => {
if (mailbox.path === message.mailboxPath) {
return {
...mailbox,
exists: Math.max(0, Number(mailbox.exists || 0) - 1),
unseen: Math.max(0, Number(mailbox.unseen || 0) - (message.seen ? 0 : 1)),
}
}
if (mailbox.path === mailboxPath) {
return {
...mailbox,
exists: Number(mailbox.exists || 0) + 1,
unseen: Number(mailbox.unseen || 0) + (message.seen ? 0 : 1),
}
}
return mailbox
})
removeMessageFromCurrentList(messageId)
toast.add({ title: "E-Mail verschoben", color: "success" })
moveTargetMailboxPath.value = ""
const targetMailbox = mailboxes.value.find((mailbox) => mailbox.path === mailboxPath)
toast.add({
title: "E-Mail verschoben",
description: targetMailbox ? `Abgelegt in „${mailboxLabel(targetMailbox)}` : undefined,
color: "success",
})
} catch (err: any) {
toast.add({
title: "Verschieben fehlgeschlagen",
@@ -489,6 +523,53 @@ async function moveSelectedMessage() {
}
}
function startMessageDrag(event: DragEvent, message: EmailMessage) {
if (!event.dataTransfer || actionLoading.value) {
event.preventDefault()
return
}
draggedMessageId.value = message.id
event.dataTransfer.effectAllowed = "move"
event.dataTransfer.setData("application/x-fedeo-email-id", message.id)
event.dataTransfer.setData("text/plain", message.id)
}
function finishMessageDrag() {
draggedMessageId.value = null
dragOverMailboxPath.value = null
}
function dragMessageOverMailbox(event: DragEvent, mailbox: EmailMailbox) {
const message = messages.value.find((item) => item.id === draggedMessageId.value)
if (!message || message.mailboxPath === mailbox.path) {
if (event.dataTransfer) event.dataTransfer.dropEffect = "none"
return
}
event.preventDefault()
dragOverMailboxPath.value = mailbox.path
if (event.dataTransfer) event.dataTransfer.dropEffect = "move"
}
function leaveMailboxDropTarget(event: DragEvent, mailbox: EmailMailbox) {
const currentTarget = event.currentTarget as HTMLElement | null
const relatedTarget = event.relatedTarget as Node | null
if (currentTarget && relatedTarget && currentTarget.contains(relatedTarget)) return
if (dragOverMailboxPath.value === mailbox.path) dragOverMailboxPath.value = null
}
async function dropMessageOnMailbox(event: DragEvent, mailbox: EmailMailbox) {
event.preventDefault()
const messageId = draggedMessageId.value
|| event.dataTransfer?.getData("application/x-fedeo-email-id")
|| ""
finishMessageDrag()
if (!messageId) return
await moveMessageToMailbox(messageId, mailbox.path)
}
async function downloadAttachment(attachment: NonNullable<EmailMessage["attachments"]>[number]) {
actionLoading.value = `attachment-${attachment.id}`
try {
@@ -682,13 +763,27 @@ onMounted(loadAccounts)
</div>
<nav v-else class="p-2">
<div
v-if="draggedMessageId"
class="mx-1 mb-2 flex items-center gap-2 rounded-md bg-primary/10 px-3 py-2 text-xs text-primary"
>
<UIcon name="i-heroicons-folder-arrow-down" class="size-4" />
E-Mail in einem Ordner ablegen
</div>
<button
v-for="row in mailboxRows"
:key="row.mailbox.id"
class="flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors"
:class="selectedMailboxPath === row.mailbox.path ? 'bg-primary/10 text-primary' : 'hover:bg-(--ui-bg-muted)'"
:class="[
selectedMailboxPath === row.mailbox.path ? 'bg-primary/10 text-primary' : 'hover:bg-(--ui-bg-muted)',
dragOverMailboxPath === row.mailbox.path ? 'bg-primary/15 text-primary ring-2 ring-primary' : '',
draggedMessageId && selectedMailboxPath === row.mailbox.path ? 'cursor-not-allowed opacity-60' : '',
]"
:style="{ paddingLeft: `${12 + row.depth * 18}px` }"
@click="selectMailbox(row.mailbox)"
@dragover="dragMessageOverMailbox($event, row.mailbox)"
@dragleave="leaveMailboxDropTarget($event, row.mailbox)"
@drop="dropMessageOnMailbox($event, row.mailbox)"
>
<UIcon
v-if="row.hasChildren"
@@ -739,9 +834,15 @@ onMounted(loadAccounts)
<button
v-for="message in filteredMessages"
:key="message.id"
class="block w-full border-l-2 px-4 py-3 text-left transition-colors"
:class="selectedMessage?.id === message.id ? 'border-primary bg-primary/10' : 'border-transparent hover:bg-(--ui-bg-muted)'"
class="block w-full cursor-grab border-l-2 px-4 py-3 text-left transition-colors active:cursor-grabbing"
:class="[
selectedMessage?.id === message.id ? 'border-primary bg-primary/10' : 'border-transparent hover:bg-(--ui-bg-muted)',
draggedMessageId === message.id ? 'opacity-50' : '',
]"
draggable="true"
@click="selectMessage(message)"
@dragstart="startMessageDrag($event, message)"
@dragend="finishMessageDrag"
>
<div class="flex items-start justify-between gap-3">
<p class="min-w-0 truncate text-sm" :class="message.seen ? 'font-medium' : 'font-semibold'">