Add mail context menu
This commit is contained in:
parent
a64a6d187b
commit
4f8a441d51
3 changed files with 208 additions and 16 deletions
149
frontend-js/dist/app.js
vendored
149
frontend-js/dist/app.js
vendored
|
|
@ -129,6 +129,98 @@
|
|||
applyListSearch(input.value);
|
||||
}
|
||||
|
||||
function ensureContextMenu() {
|
||||
let menu = document.getElementById("mailContextMenu");
|
||||
if (menu) return menu;
|
||||
menu = document.createElement("div");
|
||||
menu.id = "mailContextMenu";
|
||||
menu.className = "mail-context-menu";
|
||||
menu.setAttribute("role", "menu");
|
||||
menu.setAttribute("aria-label", "Nachrichten-Aktionen");
|
||||
menu.innerHTML = `
|
||||
<button type="button" role="menuitem" data-context-action="open"><span aria-hidden="true">⛶</span> Oeffnen</button>
|
||||
<button type="button" role="menuitem" data-context-action="reply"><span aria-hidden="true">↩</span> Antworten</button>
|
||||
<button type="button" role="menuitem" data-context-action="forward"><span aria-hidden="true">↪</span> Weiterleiten</button>
|
||||
<div class="sep"></div>
|
||||
<button type="button" role="menuitem" data-context-action="copy-subject"><span aria-hidden="true">⎘</span> Betreff kopieren</button>
|
||||
<button type="button" role="menuitem" data-context-action="copy-from"><span aria-hidden="true">✉</span> Absender kopieren</button>
|
||||
<button type="button" role="menuitem" data-context-action="copy-body"><span aria-hidden="true">☰</span> Text kopieren</button>
|
||||
`;
|
||||
document.body.appendChild(menu);
|
||||
return menu;
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
const menu = document.getElementById("mailContextMenu");
|
||||
if (menu) menu.classList.remove("open");
|
||||
}
|
||||
|
||||
function openContextMenu(x, y, row) {
|
||||
const menu = ensureContextMenu();
|
||||
menu.dataset.rowContext = row ? "1" : "0";
|
||||
const pad = 8;
|
||||
menu.classList.add("open");
|
||||
menu.style.left = "0px";
|
||||
menu.style.top = "0px";
|
||||
const rect = menu.getBoundingClientRect();
|
||||
menu.style.left = `${Math.max(pad, Math.min(x, window.innerWidth - rect.width - pad))}px`;
|
||||
menu.style.top = `${Math.max(pad, Math.min(y, window.innerHeight - rect.height - pad))}px`;
|
||||
}
|
||||
|
||||
function rowMessage(row) {
|
||||
if (!row) return null;
|
||||
return {
|
||||
subject: row.querySelector(".msg-subject")?.textContent?.trim() || "",
|
||||
from: row.querySelector(".msg-from")?.textContent?.trim() || "",
|
||||
date: row.querySelector(".msg-date")?.textContent?.trim() || "",
|
||||
body: ""
|
||||
};
|
||||
}
|
||||
|
||||
function activeMessageFallback() {
|
||||
return rowMessage(document.querySelector(".msg-row.active")) || selectedPreview();
|
||||
}
|
||||
|
||||
function waitForPreview() {
|
||||
return new Promise((resolve) => {
|
||||
let done = false;
|
||||
const finish = () => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
document.removeEventListener("htmx:afterSwap", finish);
|
||||
resolve();
|
||||
};
|
||||
document.addEventListener("htmx:afterSwap", finish);
|
||||
setTimeout(finish, 350);
|
||||
});
|
||||
}
|
||||
|
||||
async function activateContextRow() {
|
||||
const row = document.querySelector(".msg-row.context-active");
|
||||
if (!row) return null;
|
||||
row.click();
|
||||
await waitForPreview();
|
||||
return row;
|
||||
}
|
||||
|
||||
async function copyText(text) {
|
||||
const value = text || "";
|
||||
if (!value) return;
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(value);
|
||||
return;
|
||||
}
|
||||
const area = document.createElement("textarea");
|
||||
area.value = value;
|
||||
area.style.position = "fixed";
|
||||
area.style.left = "-9999px";
|
||||
document.body.appendChild(area);
|
||||
area.focus();
|
||||
area.select();
|
||||
document.execCommand("copy");
|
||||
area.remove();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", ensureActionBar);
|
||||
function closeActionMenus() {
|
||||
document.querySelectorAll(".action-menu.open").forEach((menu) => {
|
||||
|
|
@ -216,6 +308,63 @@
|
|||
const input = event.target.closest("[data-action-search]");
|
||||
if (input) runSearch(input);
|
||||
});
|
||||
document.addEventListener("contextmenu", (event) => {
|
||||
const row = event.target.closest(".msg-row");
|
||||
const readBody = event.target.closest(".read-body");
|
||||
if (!row && !readBody) return;
|
||||
event.preventDefault();
|
||||
document.querySelectorAll(".msg-row.context-active").forEach((item) => item.classList.remove("context-active"));
|
||||
if (row) {
|
||||
row.classList.add("context-active");
|
||||
row.closest(".pane")?.querySelectorAll(".msg-row.active").forEach((item) => item.classList.remove("active"));
|
||||
row.classList.add("active");
|
||||
}
|
||||
openContextMenu(event.clientX, event.clientY, row);
|
||||
});
|
||||
document.addEventListener("click", async (event) => {
|
||||
const menu = event.target.closest("#mailContextMenu");
|
||||
if (!menu) {
|
||||
closeContextMenu();
|
||||
return;
|
||||
}
|
||||
const item = event.target.closest("[data-context-action]");
|
||||
if (!item) return;
|
||||
event.preventDefault();
|
||||
const action = item.dataset.contextAction;
|
||||
const contextRow = await activateContextRow();
|
||||
closeContextMenu();
|
||||
const rowMessageData = rowMessage(contextRow);
|
||||
const previewMessage = selectedPreview();
|
||||
const message = previewMessage || rowMessageData || activeMessageFallback();
|
||||
if (!message && action !== "open") {
|
||||
window.alert("Bitte zuerst eine Nachricht waehlen.");
|
||||
return;
|
||||
}
|
||||
if (action === "open") {
|
||||
const button = document.querySelector("#read [data-open-mail-modal], #transfer-preview [data-open-mail-modal], .pane-read [data-open-mail-modal]");
|
||||
if (button) button.click();
|
||||
return;
|
||||
}
|
||||
if (action === "reply") {
|
||||
openCompose({ to: message.from, subject: prefixedSubject("Re", message.subject), body: quoteBody(message) });
|
||||
return;
|
||||
}
|
||||
if (action === "forward") {
|
||||
openCompose({ to: "", subject: prefixedSubject("Fw", message.subject), body: forwardBody(message) });
|
||||
return;
|
||||
}
|
||||
if (action === "copy-subject") {
|
||||
await copyText((rowMessageData || message).subject);
|
||||
return;
|
||||
}
|
||||
if (action === "copy-from") {
|
||||
await copyText((rowMessageData || message).from);
|
||||
return;
|
||||
}
|
||||
if (action === "copy-body") {
|
||||
await copyText(message.body);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function () {
|
||||
|
|
|
|||
43
frontend-js/dist/style.css
vendored
43
frontend-js/dist/style.css
vendored
|
|
@ -238,6 +238,49 @@ body.ol2013 {
|
|||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
.mail-context-menu {
|
||||
position: fixed;
|
||||
z-index: 80;
|
||||
min-width: 220px;
|
||||
display: none;
|
||||
padding: 6px;
|
||||
color: var(--ol-text);
|
||||
background: #fff;
|
||||
border: 1px solid var(--ol-border-dark);
|
||||
box-shadow: 0 12px 34px rgba(20, 25, 35, .22);
|
||||
}
|
||||
.mail-context-menu.open { display: block; }
|
||||
.mail-context-menu button {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 0 10px;
|
||||
border: 0;
|
||||
color: var(--ol-text);
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mail-context-menu button:hover {
|
||||
color: #fff;
|
||||
background: var(--ol-blue);
|
||||
}
|
||||
.mail-context-menu button span {
|
||||
width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
.mail-context-menu .sep {
|
||||
height: 1px;
|
||||
margin: 5px 4px;
|
||||
background: var(--ol-border);
|
||||
}
|
||||
.msg-row.context-active {
|
||||
outline: 1px solid var(--ol-blue);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.action-menu {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue