From 2ebd856df0624879de5f748e7ff5669674dab506 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Mon, 7 Sep 2026 20:40:42 +0200 Subject: [PATCH] =?UTF-8?q?KI-AGENT:=20Mail-Editor=20=C3=BCbersichtlicher?= =?UTF-8?q?=20gestalten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/pages/email/new.vue | 599 ++++++++++++++++++++++------------- 1 file changed, 387 insertions(+), 212 deletions(-) diff --git a/frontend/pages/email/new.vue b/frontend/pages/email/new.vue index c813582..b190213 100644 --- a/frontend/pages/email/new.vue +++ b/frontend/pages/email/new.vue @@ -18,12 +18,17 @@ const emailAccounts = ref([]) const preloadedContent = ref("") const loadedDocuments = ref([]) const loaded = ref(false) +const sending = ref(false) const noAccountsPresent = ref(false) const sourceMessage = ref(null) const recipientOptions = ref([]) const selectedRecipient = ref(null) const loadingRecipients = ref(false) const preferredCustomerId = ref(null) +const showCc = ref(Boolean(route.query.cc)) +const showBcc = ref(Boolean(route.query.bcc)) +const attachmentInput = ref(null) +const selectedAttachmentFiles = ref([]) const composeMode = computed(() => { const mode = String(route.query.mode || "") return ["reply", "replyAll", "forward"].includes(mode) ? mode : "new" @@ -33,6 +38,20 @@ const pageTitle = computed(() => ({ replyAll: "Allen antworten", forward: "E-Mail weiterleiten", }[composeMode.value] || "Neue E-Mail")) +const canSend = computed(() => Boolean( + String(emailData.value.to || "").trim() + && String(emailData.value.subject || "").trim() + && emailData.value.account +)) +const attachmentCount = computed(() => + selectedAttachmentFiles.value.length + + loadedDocuments.value.length + + (composeMode.value === "forward" ? sourceMessage.value?.attachments?.length || 0 : 0) +) +const attachmentLabel = computed(() => attachmentCount.value === 1 + ? "1 Anhang" + : `${attachmentCount.value} Anhänge` +) const escapeHtml = (value = "") => String(value) .replace(/&/g, "&") @@ -339,15 +358,33 @@ const contentChanged = (content) => { emailData.value.text = content.text } -const selectedAttachments = ref([]) -const renderAttachments = () => { - selectedAttachments.value = Array.from(document.getElementById("inputAttachments").files).map(i => { - return { - filename: i.name, - type: i.type - }}) +const fileKey = file => `${file.name}:${file.size}:${file.lastModified}` + +const addAttachments = (files) => { + const attachments = [...selectedAttachmentFiles.value] + const existingKeys = new Set(attachments.map(fileKey)) + + Array.from(files || []).forEach(file => { + if (!existingKeys.has(fileKey(file))) { + attachments.push(file) + existingKeys.add(fileKey(file)) + } + }) + + selectedAttachmentFiles.value = attachments + if (attachmentInput.value) attachmentInput.value.value = "" } +const renderAttachments = event => addAttachments(event.target.files) +const dropAttachments = event => addAttachments(event.dataTransfer?.files) +const removeAttachment = index => selectedAttachmentFiles.value.splice(index, 1) +const removeLoadedDocument = index => loadedDocuments.value.splice(index, 1) + +const formatFileSize = (bytes = 0) => { + if (!bytes) return "0 KB" + if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB` + return `${(bytes / (1024 * 1024)).toFixed(1).replace(".", ",")} MB` +} @@ -367,47 +404,42 @@ function blobToBase64(blob) { } const sendEmail = async () => { - loaded.value = false - - let body = { - ...emailData.value, - attachments: [], - sourceMessageId: sourceMessage.value?.id, - composeMode: composeMode.value === "new" ? undefined : composeMode.value, - } - - - - - - for await (const file of Array.from(document.getElementById("inputAttachments").files)) { - body.attachments.push({ - filename: file.name, - content: await toBase64(file), - contentType: file.type, - encoding: "base64", - contentDisposition: "attachment" - }) - } - - for await (const doc of loadedDocuments.value) { - - - const res = await useFiles().downloadFile(doc.id, null, true) - - body.attachments.push({ - filename: doc.path.split("/")[doc.path.split("/").length -1], - content: await blobToBase64(res), - contentType: res.type, - encoding: "base64", - contentDisposition: "attachment" - }) - } + if (!canSend.value || sending.value) return + sending.value = true try { + const body = { + ...emailData.value, + attachments: [], + sourceMessageId: sourceMessage.value?.id, + composeMode: composeMode.value === "new" ? undefined : composeMode.value, + } + + for await (const file of selectedAttachmentFiles.value) { + body.attachments.push({ + filename: file.name, + content: await toBase64(file), + contentType: file.type, + encoding: "base64", + contentDisposition: "attachment" + }) + } + + for await (const doc of loadedDocuments.value) { + const res = await useFiles().downloadFile(doc.id, null, true) + + body.attachments.push({ + filename: doc.path.split("/").pop(), + content: await blobToBase64(res), + contentType: res.type, + encoding: "base64", + contentDisposition: "attachment" + }) + } + const res = await useNuxtApp().$api("/api/email/send",{ method: "POST", - body: body, + body, }) if(!res.success) { @@ -423,7 +455,7 @@ const sendEmail = async () => { color: "error", }) } finally { - loaded.value = true + sending.value = false } } @@ -432,196 +464,339 @@ const sendEmail = async () => {