300 lines
10 KiB
JavaScript
300 lines
10 KiB
JavaScript
const i18n = {
|
|
de: {
|
|
badge_local: "E-Mail Service",
|
|
label_user: "Benutzer:",
|
|
compose_btn: "Neue E-Mail",
|
|
folder_inbox: "Posteingang",
|
|
folder_sent: "Gesendet",
|
|
folder_trash: "Papierkorb",
|
|
proto_title: "Client-Verbindung",
|
|
proto_desc: "Für Thunderbird, Outlook & K-9 Mail:",
|
|
loading: "Lädt Nachrichten...",
|
|
select_message_prompt: "Wähle eine E-Mail aus, um sie zu lesen.",
|
|
btn_reply: "Antworten",
|
|
btn_delete: "Löschen",
|
|
label_from: "Von:",
|
|
label_to: "An:",
|
|
label_date: "Datum:",
|
|
label_subject: "Betreff:",
|
|
label_body: "Nachricht:",
|
|
btn_cancel: "Abbrechen",
|
|
btn_send: "Senden",
|
|
modal_compose_title: "Neue Nachricht verfassen",
|
|
no_messages: "Keine Nachrichten in diesem Ordner",
|
|
send_success: "E-Mail erfolgreich gesendet!",
|
|
send_error: "Fehler beim Senden der E-Mail.",
|
|
reply_prefix: "Re: "
|
|
},
|
|
en: {
|
|
badge_local: "Email Service",
|
|
label_user: "User:",
|
|
compose_btn: "New Email",
|
|
folder_inbox: "Inbox",
|
|
folder_sent: "Sent",
|
|
folder_trash: "Trash",
|
|
proto_title: "Client Connection",
|
|
proto_desc: "For Thunderbird, Outlook & K-9 Mail:",
|
|
loading: "Loading messages...",
|
|
select_message_prompt: "Select an email to view its contents.",
|
|
btn_reply: "Reply",
|
|
btn_delete: "Delete",
|
|
label_from: "From:",
|
|
label_to: "To:",
|
|
label_date: "Date:",
|
|
label_subject: "Subject:",
|
|
label_body: "Message:",
|
|
btn_cancel: "Cancel",
|
|
btn_send: "Send",
|
|
modal_compose_title: "Compose New Message",
|
|
no_messages: "No messages in this folder",
|
|
send_success: "Email sent successfully!",
|
|
send_error: "Failed to send email.",
|
|
reply_prefix: "Re: "
|
|
}
|
|
};
|
|
|
|
let currentLang = localStorage.getItem("benzcloud_lang") || "de";
|
|
let currentFolder = "INBOX";
|
|
let currentUser = "admin";
|
|
let currentMessages = [];
|
|
let selectedMessageId = null;
|
|
|
|
function setLang(lang) {
|
|
currentLang = lang;
|
|
localStorage.setItem("benzcloud_lang", lang);
|
|
document.documentElement.lang = lang;
|
|
|
|
document.querySelectorAll("[data-i18n]").forEach(el => {
|
|
const key = el.getAttribute("data-i18n");
|
|
if (i18n[lang] && i18n[lang][key]) {
|
|
el.textContent = i18n[lang][key];
|
|
}
|
|
});
|
|
|
|
document.getElementById("btnDe").classList.toggle("active", lang === "de");
|
|
document.getElementById("btnEn").classList.toggle("active", lang === "en");
|
|
|
|
updateFolderTitle();
|
|
}
|
|
|
|
function updateFolderTitle() {
|
|
const titleEl = document.getElementById("currentFolderName");
|
|
if (currentFolder === "INBOX") titleEl.textContent = i18n[currentLang].folder_inbox;
|
|
else if (currentFolder === "SENT") titleEl.textContent = i18n[currentLang].folder_sent;
|
|
else if (currentFolder === "TRASH") titleEl.textContent = i18n[currentLang].folder_trash;
|
|
}
|
|
|
|
// Fetch users
|
|
async function loadUsers() {
|
|
try {
|
|
const res = await fetch("/api/mail/users");
|
|
if (res.ok) {
|
|
const users = await res.json();
|
|
const select = document.getElementById("userSelect");
|
|
select.innerHTML = "";
|
|
users.forEach(u => {
|
|
const opt = document.createElement("option");
|
|
opt.value = u;
|
|
opt.textContent = u;
|
|
select.appendChild(opt);
|
|
});
|
|
if (users.length > 0) {
|
|
currentUser = users[0];
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn("Could not load users list, using admin default", e);
|
|
}
|
|
}
|
|
|
|
// Fetch messages
|
|
async function loadMessages() {
|
|
const listEl = document.getElementById("messageList");
|
|
listEl.innerHTML = `<div class="empty-state">${i18n[currentLang].loading}</div>`;
|
|
|
|
try {
|
|
const res = await fetch(`/api/mail/messages?user=${encodeURIComponent(currentUser)}&folder=${currentFolder}`);
|
|
if (!res.ok) throw new Error("HTTP error " + res.status);
|
|
|
|
const msgs = await res.json();
|
|
currentMessages = msgs || [];
|
|
|
|
renderMessageList();
|
|
updateUnreadCount();
|
|
} catch (err) {
|
|
listEl.innerHTML = `<div class="empty-state">Error loading messages: ${err.message}</div>`;
|
|
}
|
|
}
|
|
|
|
function updateUnreadCount() {
|
|
const unread = currentMessages.filter(m => !m.read && m.folder === "INBOX").length;
|
|
const badge = document.getElementById("unreadBadge");
|
|
badge.textContent = unread;
|
|
badge.setAttribute("data-count", unread);
|
|
}
|
|
|
|
function renderMessageList() {
|
|
const listEl = document.getElementById("messageList");
|
|
listEl.innerHTML = "";
|
|
|
|
if (currentMessages.length === 0) {
|
|
listEl.innerHTML = `<div class="empty-state">${i18n[currentLang].no_messages}</div>`;
|
|
return;
|
|
}
|
|
|
|
currentMessages.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
|
|
currentMessages.forEach(msg => {
|
|
const item = document.createElement("div");
|
|
item.className = `mail-item ${!msg.read ? "unread" : ""} ${msg.id === selectedMessageId ? "selected" : ""}`;
|
|
|
|
const dateStr = new Date(msg.date).toLocaleDateString(currentLang === "de" ? "de-DE" : "en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit"
|
|
});
|
|
|
|
const displayContact = currentFolder === "SENT" ? `To: ${msg.to}` : msg.from;
|
|
|
|
item.innerHTML = `
|
|
<div class="mail-item-top">
|
|
<span class="mail-sender">${escapeHtml(displayContact)}</span>
|
|
<span class="mail-time">${dateStr}</span>
|
|
</div>
|
|
<div class="mail-subject">${escapeHtml(msg.subject || "(No Subject)")}</div>
|
|
<div class="mail-preview">${escapeHtml(msg.body.substring(0, 80))}</div>
|
|
`;
|
|
|
|
item.addEventListener("click", () => selectMessage(msg));
|
|
listEl.appendChild(item);
|
|
});
|
|
}
|
|
|
|
function selectMessage(msg) {
|
|
selectedMessageId = msg.id;
|
|
msg.read = true;
|
|
|
|
// API call to mark as read
|
|
fetch(`/api/mail/read?user=${encodeURIComponent(currentUser)}&id=${encodeURIComponent(msg.id)}`, { method: "POST" });
|
|
|
|
document.getElementById("emptyDetail").style.display = "none";
|
|
document.getElementById("activeDetail").style.display = "flex";
|
|
|
|
document.getElementById("detailSubject").textContent = msg.subject || "(No Subject)";
|
|
document.getElementById("detailFrom").textContent = msg.from;
|
|
document.getElementById("detailTo").textContent = msg.to;
|
|
document.getElementById("detailDate").textContent = new Date(msg.date).toLocaleString(currentLang === "de" ? "de-DE" : "en-US");
|
|
document.getElementById("detailBody").textContent = msg.body;
|
|
|
|
renderMessageList();
|
|
updateUnreadCount();
|
|
}
|
|
|
|
async function deleteSelectedMessage() {
|
|
if (!selectedMessageId) return;
|
|
try {
|
|
await fetch(`/api/mail/delete?user=${encodeURIComponent(currentUser)}&id=${encodeURIComponent(selectedMessageId)}`, { method: "POST" });
|
|
selectedMessageId = null;
|
|
document.getElementById("emptyDetail").style.display = "flex";
|
|
document.getElementById("activeDetail").style.display = "none";
|
|
await loadMessages();
|
|
} catch (err) {
|
|
alert("Delete failed: " + err.message);
|
|
}
|
|
}
|
|
|
|
function openCompose(replyTo = null) {
|
|
const modal = document.getElementById("composeModal");
|
|
modal.style.display = "flex";
|
|
|
|
if (replyTo) {
|
|
document.getElementById("composeTo").value = replyTo.from;
|
|
const prefix = i18n[currentLang].reply_prefix;
|
|
document.getElementById("composeSubject").value = replyTo.subject.startsWith("Re:") ? replyTo.subject : `${prefix}${replyTo.subject}`;
|
|
document.getElementById("composeBody").value = `\n\n--- Original Message ---\nFrom: ${replyTo.from}\nDate: ${replyTo.date}\n\n${replyTo.body}`;
|
|
} else {
|
|
document.getElementById("composeTo").value = "";
|
|
document.getElementById("composeSubject").value = "";
|
|
document.getElementById("composeBody").value = "";
|
|
}
|
|
}
|
|
|
|
function closeCompose() {
|
|
document.getElementById("composeModal").style.display = "none";
|
|
}
|
|
|
|
async function handleSend(e) {
|
|
e.preventDefault();
|
|
const to = document.getElementById("composeTo").value.trim();
|
|
const subject = document.getElementById("composeSubject").value.trim();
|
|
const body = document.getElementById("composeBody").value.trim();
|
|
|
|
try {
|
|
const res = await fetch("/api/mail/send", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
from: `${currentUser}@benzcloud.local`,
|
|
to: to,
|
|
subject: subject,
|
|
body: body
|
|
})
|
|
});
|
|
|
|
if (!res.ok) throw new Error("Failed to deliver message");
|
|
|
|
closeCompose();
|
|
await loadMessages();
|
|
} catch (err) {
|
|
alert(i18n[currentLang].send_error + ": " + err.message);
|
|
}
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
return (str || "").replace(/[&<>"']/g, function (m) {
|
|
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m];
|
|
});
|
|
}
|
|
|
|
// Event Listeners
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
setLang(currentLang);
|
|
|
|
document.getElementById("btnDe").addEventListener("click", () => setLang("de"));
|
|
document.getElementById("btnEn").addEventListener("click", () => setLang("en"));
|
|
|
|
document.querySelectorAll(".nav-item").forEach(btn => {
|
|
btn.addEventListener("click", () => {
|
|
document.querySelectorAll(".nav-item").forEach(b => b.classList.remove("active"));
|
|
btn.classList.add("active");
|
|
currentFolder = btn.getAttribute("data-folder");
|
|
selectedMessageId = null;
|
|
document.getElementById("emptyDetail").style.display = "flex";
|
|
document.getElementById("activeDetail").style.display = "none";
|
|
updateFolderTitle();
|
|
loadMessages();
|
|
});
|
|
});
|
|
|
|
document.getElementById("userSelect").addEventListener("change", (e) => {
|
|
currentUser = e.target.value;
|
|
loadMessages();
|
|
});
|
|
|
|
document.getElementById("btnRefresh").addEventListener("click", () => loadMessages());
|
|
document.getElementById("btnCompose").addEventListener("click", () => openCompose());
|
|
document.getElementById("btnModalClose").addEventListener("click", closeCompose);
|
|
document.getElementById("btnCancelCompose").addEventListener("click", closeCompose);
|
|
document.getElementById("composeForm").addEventListener("submit", handleSend);
|
|
|
|
document.getElementById("btnDeleteMsg").addEventListener("click", deleteSelectedMessage);
|
|
document.getElementById("btnReply").addEventListener("click", () => {
|
|
const msg = currentMessages.find(m => m.id === selectedMessageId);
|
|
if (msg) openCompose(msg);
|
|
});
|
|
|
|
await loadUsers();
|
|
await loadMessages();
|
|
});
|