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
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:
@@ -17,6 +17,7 @@ type EmailMailbox = {
|
|||||||
name: string
|
name: string
|
||||||
delimiter?: string | null
|
delimiter?: string | null
|
||||||
specialUse?: string | null
|
specialUse?: string | null
|
||||||
|
exists?: number
|
||||||
unseen?: number
|
unseen?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +85,8 @@ const expandedMailboxPaths = ref<string[]>([])
|
|||||||
const syncedMailboxPaths = ref<string[]>([])
|
const syncedMailboxPaths = ref<string[]>([])
|
||||||
const actionLoading = ref("")
|
const actionLoading = ref("")
|
||||||
const moveTargetMailboxPath = ref("")
|
const moveTargetMailboxPath = ref("")
|
||||||
|
const draggedMessageId = ref<string | null>(null)
|
||||||
|
const dragOverMailboxPath = ref<string | null>(null)
|
||||||
let deepLinkApplied = false
|
let deepLinkApplied = false
|
||||||
|
|
||||||
const selectedAccount = computed(() =>
|
const selectedAccount = computed(() =>
|
||||||
@@ -412,8 +415,11 @@ function openComposer(mode: "reply" | "replyAll" | "forward") {
|
|||||||
|
|
||||||
function removeMessageFromCurrentList(messageId: string) {
|
function removeMessageFromCurrentList(messageId: string) {
|
||||||
const currentIndex = messages.value.findIndex((message) => message.id === messageId)
|
const currentIndex = messages.value.findIndex((message) => message.id === messageId)
|
||||||
|
const wasSelected = selectedMessage.value?.id === messageId
|
||||||
messages.value = messages.value.filter((message) => message.id !== messageId)
|
messages.value = messages.value.filter((message) => message.id !== messageId)
|
||||||
|
|
||||||
|
if (!wasSelected) return
|
||||||
|
|
||||||
const nextMessage = messages.value[currentIndex] || messages.value[currentIndex - 1] || null
|
const nextMessage = messages.value[currentIndex] || messages.value[currentIndex - 1] || null
|
||||||
selectedMessage.value = null
|
selectedMessage.value = null
|
||||||
|
|
||||||
@@ -467,17 +473,45 @@ async function deleteSelectedMessage() {
|
|||||||
async function moveSelectedMessage() {
|
async function moveSelectedMessage() {
|
||||||
if (!selectedMessage.value || !moveTargetMailboxPath.value) return
|
if (!selectedMessage.value || !moveTargetMailboxPath.value) return
|
||||||
|
|
||||||
const messageId = selectedMessage.value.id
|
await moveMessageToMailbox(selectedMessage.value.id, moveTargetMailboxPath.value, "move")
|
||||||
actionLoading.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 {
|
try {
|
||||||
await $api(`/api/email/messages/${messageId}/move`, {
|
await $api(`/api/email/messages/${messageId}/move`, {
|
||||||
method: "POST",
|
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)
|
removeMessageFromCurrentList(messageId)
|
||||||
toast.add({ title: "E-Mail verschoben", color: "success" })
|
const targetMailbox = mailboxes.value.find((mailbox) => mailbox.path === mailboxPath)
|
||||||
moveTargetMailboxPath.value = ""
|
toast.add({
|
||||||
|
title: "E-Mail verschoben",
|
||||||
|
description: targetMailbox ? `Abgelegt in „${mailboxLabel(targetMailbox)}“` : undefined,
|
||||||
|
color: "success",
|
||||||
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast.add({
|
toast.add({
|
||||||
title: "Verschieben fehlgeschlagen",
|
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]) {
|
async function downloadAttachment(attachment: NonNullable<EmailMessage["attachments"]>[number]) {
|
||||||
actionLoading.value = `attachment-${attachment.id}`
|
actionLoading.value = `attachment-${attachment.id}`
|
||||||
try {
|
try {
|
||||||
@@ -682,13 +763,27 @@ onMounted(loadAccounts)
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav v-else class="p-2">
|
<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
|
<button
|
||||||
v-for="row in mailboxRows"
|
v-for="row in mailboxRows"
|
||||||
:key="row.mailbox.id"
|
: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="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` }"
|
:style="{ paddingLeft: `${12 + row.depth * 18}px` }"
|
||||||
@click="selectMailbox(row.mailbox)"
|
@click="selectMailbox(row.mailbox)"
|
||||||
|
@dragover="dragMessageOverMailbox($event, row.mailbox)"
|
||||||
|
@dragleave="leaveMailboxDropTarget($event, row.mailbox)"
|
||||||
|
@drop="dropMessageOnMailbox($event, row.mailbox)"
|
||||||
>
|
>
|
||||||
<UIcon
|
<UIcon
|
||||||
v-if="row.hasChildren"
|
v-if="row.hasChildren"
|
||||||
@@ -739,9 +834,15 @@ onMounted(loadAccounts)
|
|||||||
<button
|
<button
|
||||||
v-for="message in filteredMessages"
|
v-for="message in filteredMessages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
class="block w-full border-l-2 px-4 py-3 text-left transition-colors"
|
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)'"
|
: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)"
|
@click="selectMessage(message)"
|
||||||
|
@dragstart="startMessageDrag($event, message)"
|
||||||
|
@dragend="finishMessageDrag"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<p class="min-w-0 truncate text-sm" :class="message.seen ? 'font-medium' : 'font-semibold'">
|
<p class="min-w-0 truncate text-sm" :class="message.seen ? 'font-medium' : 'font-semibold'">
|
||||||
|
|||||||
Reference in New Issue
Block a user