1 parent
a3ac2a68fe
commit
2932578424
14 files changed
+424
-33
No files matched your search
@@ -944,10 +944,12 @@
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Touch screens have no hover: the overlay would permanently cover the subject
|
||||
and date. Read/flag stay available in the reader toolbar and in selection
|
||||
mode, and flagged rows keep their left stripe. */
|
||||
@media (hover:none) {
|
||||
.message-quick-actions {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
.message-row .message-quick-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -558,6 +558,177 @@
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
/* Touch long-press: the message context menu as a bottom sheet. */
|
||||
.context-sheet-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 999;
|
||||
background: rgb(0 0 0 / 45%);
|
||||
animation: context-sheet-fade 160ms ease-out;
|
||||
}
|
||||
|
||||
.context-menu.context-sheet {
|
||||
top: auto;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
/* Tablets / landscape: a centred sheet instead of a full-width bar. */
|
||||
max-width: min(100%, 560px);
|
||||
margin-inline: auto;
|
||||
transition: transform 160ms ease-out;
|
||||
max-height: min(85vh, 85dvh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 10px calc(10px + env(safe-area-inset-bottom));
|
||||
border-width: 1px 0 0;
|
||||
/* Follows the style's shape: square styles (terminal, Windows) stay square. */
|
||||
border-radius: min(calc(var(--radius-panel) * 3), 18px) min(calc(var(--radius-panel) * 3), 18px) 0 0;
|
||||
font-size: var(--ui-size);
|
||||
box-shadow: 0 -12px 32px var(--shadow);
|
||||
overflow: hidden;
|
||||
animation: context-sheet-up 200ms ease-out;
|
||||
}
|
||||
|
||||
.context-menu.context-sheet:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.context-sheet-handle,
|
||||
.context-sheet-header {
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.context-sheet-handle {
|
||||
flex: none;
|
||||
width: 38px;
|
||||
height: 4px;
|
||||
margin: 2px auto 8px;
|
||||
border-radius: min(var(--radius-control), 2px);
|
||||
background: var(--line-strong);
|
||||
}
|
||||
|
||||
.context-sheet-header {
|
||||
flex: none;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
padding: 0 6px 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.context-sheet-from,
|
||||
.context-sheet-subject {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.context-sheet-from {
|
||||
font-size: calc(var(--ui-size) * 1.15);
|
||||
}
|
||||
|
||||
.context-sheet-subject {
|
||||
color: var(--muted);
|
||||
font-size: calc(var(--ui-size) * 1.05);
|
||||
}
|
||||
|
||||
.context-sheet-quick {
|
||||
flex: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.context-menu .context-sheet-quick-action {
|
||||
min-height: calc(62px * var(--ui-scale-factor, 1));
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
padding: 8px 2px;
|
||||
border-radius: min(calc(var(--radius-control) * 3), 14px);
|
||||
background: var(--hover);
|
||||
color: var(--text);
|
||||
font-size: calc(var(--ui-size) * 0.92);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.context-sheet-quick-action span {
|
||||
display: -webkit-box;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
}
|
||||
|
||||
.context-sheet-quick-action svg {
|
||||
width: calc(var(--icon-size, 18px) * 1.1);
|
||||
height: calc(var(--icon-size, 18px) * 1.1);
|
||||
}
|
||||
|
||||
.context-menu .context-sheet-quick-action.active {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.context-menu .context-sheet-quick-action.danger {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.context-sheet-list {
|
||||
min-height: 0;
|
||||
padding-top: 4px;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.context-sheet .context-sheet-list button {
|
||||
min-height: max(44px, calc(42px * var(--ui-scale-factor, 1) * var(--density-factor, 1)));
|
||||
padding: 10px 12px;
|
||||
font-size: calc(var(--ui-size) * 1.2);
|
||||
}
|
||||
|
||||
.context-sheet .context-sheet-list button.context-sheet-back {
|
||||
color: var(--muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes context-sheet-up {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes context-sheet-fade {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.context-menu.context-sheet,
|
||||
.context-sheet-backdrop {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover:none) {
|
||||
/* Long press opens the action sheet instead of the iOS text callout. */
|
||||
.message-row {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.message-row.long-pressing {
|
||||
background: var(--hover);
|
||||
transition: background 0.35s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.identity-overview {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
|
||||
@@ -1299,7 +1299,8 @@
|
||||
"Attributes": "Atrybuty",
|
||||
"Yes": "Tak",
|
||||
"No": "Nie",
|
||||
"default": "domyślna"
|
||||
"default": "domyślna",
|
||||
"Message actions": "Akcje wiadomości"
|
||||
},
|
||||
"fixed_interface_style_note": "gotowy styl interfejsu. Włącz edycję poniżej, aby dopasować jego układ i typografię.",
|
||||
"custom_interface_style_note": "RustBox można w pełni dostosować: kolory, odstępy, panele, typografię i zaawansowane ustawienia interfejsu.",
|
||||
@@ -1330,4 +1331,4 @@
|
||||
"account_color_note": "Pasek u góry aplikacji i kropka przy adresie pokazują, które konto jest aktywne.",
|
||||
"contrast_warning": "Niski kontrast",
|
||||
"edited_interface_style_note": "gotowy styl interfejsu z Twoim układem. Zmiana koloru tworzy własną kopię RustBox."
|
||||
}
|
||||
}
|
||||
@@ -290,6 +290,50 @@ function updateActiveMessageRow() {
|
||||
}
|
||||
}
|
||||
|
||||
// Touch screens (no hover) get a long-press action sheet instead of the hover
|
||||
// buttons and the right-click menu.
|
||||
const MESSAGE_LONG_PRESS_MS = 450;
|
||||
|
||||
function isTouchUi() {
|
||||
return matchMedia("(hover: none)").matches;
|
||||
}
|
||||
|
||||
function bindMessageLongPress(row, message) {
|
||||
let timer = null;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
const cancel = () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = null;
|
||||
row.classList.remove("long-pressing");
|
||||
};
|
||||
row.addEventListener("touchstart", (event) => {
|
||||
row.dataset.longPressed = "";
|
||||
if (event.touches.length !== 1 || state.selectionMode) return cancel();
|
||||
startX = event.touches[0].clientX;
|
||||
startY = event.touches[0].clientY;
|
||||
row.classList.add("long-pressing");
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
row.classList.remove("long-pressing");
|
||||
row.dataset.longPressed = "1";
|
||||
navigator.vibrate?.(12);
|
||||
openMessageActionSheet(message);
|
||||
}, MESSAGE_LONG_PRESS_MS);
|
||||
}, { passive: true });
|
||||
row.addEventListener("touchmove", (event) => {
|
||||
if (!timer) return;
|
||||
const touch = event.touches[0];
|
||||
if (Math.hypot(touch.clientX - startX, touch.clientY - startY) > 10) cancel();
|
||||
}, { passive: true });
|
||||
// After a long press the finger lift must not also open the message.
|
||||
row.addEventListener("touchend", (event) => {
|
||||
cancel();
|
||||
if (row.dataset.longPressed === "1" && event.cancelable) event.preventDefault();
|
||||
}, { passive: false });
|
||||
row.addEventListener("touchcancel", cancel, { passive: true });
|
||||
}
|
||||
|
||||
async function quickUpdateMessageFlag(message, flag, add, successMessage) {
|
||||
const folder = message.folder || state.folder;
|
||||
const rollback = applyOptimisticMessageFlag(folder, message.uid, flag, add);
|
||||
@@ -571,7 +615,8 @@ function renderMessages() {
|
||||
row.querySelector(".message-subject").after(wrap);
|
||||
}
|
||||
row.prepend(checkbox);
|
||||
row.draggable = true;
|
||||
// iOS/iPadOS start a drag on long press; that gesture opens the action sheet instead.
|
||||
row.draggable = !isTouchUi();
|
||||
row.addEventListener("dragstart", (event) => {
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("application/x-rustbox-message", JSON.stringify({ folder, uid: message.uid }));
|
||||
@@ -579,6 +624,11 @@ function renderMessages() {
|
||||
});
|
||||
row.addEventListener("dragend", () => row.classList.remove("dragging"));
|
||||
row.addEventListener("click", (event) => {
|
||||
if (row.dataset.longPressed === "1") {
|
||||
row.dataset.longPressed = "";
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (state.selectionMode) {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
@@ -601,7 +651,16 @@ function renderMessages() {
|
||||
openMessage(folder, message.uid);
|
||||
});
|
||||
row.addEventListener("keydown", (event) => { if (event.key === "Enter" && !state.selectionMode) openMessage(folder, message.uid); if (event.key === " ") { event.preventDefault(); setMessageSelected(message, !state.selectedMessages.has(messageKey(message))); } });
|
||||
row.addEventListener("contextmenu", (event) => { event.preventDefault(); openMessageContextMenu(event.clientX, event.clientY, message); });
|
||||
row.addEventListener("contextmenu", (event) => {
|
||||
event.preventDefault();
|
||||
// Android fires contextmenu on long press as well; the sheet may already be open.
|
||||
if (isTouchUi()) {
|
||||
if (!contextSheetOpen()) openMessageActionSheet(message);
|
||||
return;
|
||||
}
|
||||
openMessageContextMenu(event.clientX, event.clientY, message);
|
||||
});
|
||||
bindMessageLongPress(row, message);
|
||||
list.append(row);
|
||||
}
|
||||
updateMessageLoadControls();
|
||||
|
||||
@@ -377,8 +377,8 @@ async function deleteCurrent() {
|
||||
} catch (error) { toast(error.message, "error"); }
|
||||
}
|
||||
|
||||
async function performBulk(action, targetFolder = null) {
|
||||
const items = selectedMessages();
|
||||
async function performBulk(action, targetFolder = null, explicitItems = null) {
|
||||
const items = explicitItems || selectedMessages();
|
||||
if (!items.length) return;
|
||||
const bulkJunkToInbox = action === "junk" && items.every((message) => isJunkFolder(message.folder || state.folder));
|
||||
if (action === "archive" && !detectSpecialFolder("archive")) {
|
||||
@@ -444,7 +444,7 @@ async function performBulk(action, targetFolder = null) {
|
||||
completed += 1;
|
||||
$("#bulk-count").textContent = `${completed}/${items.length}`;
|
||||
}
|
||||
state.selectedMessages.clear();
|
||||
if (!explicitItems) state.selectedMessages.clear();
|
||||
state.searchAllResults = null;
|
||||
const flagsOnly = ["read", "unread", "flag", "unflag"].includes(action);
|
||||
const orderingChanged = ["flag", "unflag"].includes(action) && state.currentFolderFlaggedOnTop && !state.searchContext;
|
||||
@@ -674,11 +674,65 @@ async function deleteFolder() {
|
||||
} catch (error) { toast(error.message, "error"); }
|
||||
}
|
||||
|
||||
function hideContextMenu() {
|
||||
function hideContextMenu({ fromHistory = false } = {}) {
|
||||
closeContextSubmenu();
|
||||
const menu = $("#context-menu");
|
||||
const wasSheet = menu.classList.contains("context-sheet") && !menu.classList.contains("hidden");
|
||||
menu.classList.add("hidden");
|
||||
menu.classList.remove("context-sheet");
|
||||
menu.setAttribute("role", "menu");
|
||||
menu.removeAttribute("aria-label");
|
||||
menu.removeAttribute("aria-modal");
|
||||
menu.removeAttribute("tabindex");
|
||||
menu.style.removeProperty("transform");
|
||||
menu.style.removeProperty("left");
|
||||
menu.style.removeProperty("top");
|
||||
menu.style.removeProperty("max-height");
|
||||
menu.replaceChildren();
|
||||
$("#context-sheet-backdrop")?.remove();
|
||||
if (wasSheet) {
|
||||
// The sheet owns one history entry so Android "back" closes it.
|
||||
if (!fromHistory && history.state?.rustboxActionSheet) history.back();
|
||||
const returnTo = contextSheetReturnFocus;
|
||||
contextSheetReturnFocus = null;
|
||||
if (returnTo?.isConnected) returnTo.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
let contextSheetReturnFocus = null;
|
||||
|
||||
// Drag the handle/header down to dismiss the sheet.
|
||||
function bindContextSheetDrag(menu, zones) {
|
||||
let startY = null;
|
||||
let delta = 0;
|
||||
const move = (event) => {
|
||||
if (startY === null) return;
|
||||
delta = Math.max(0, event.touches[0].clientY - startY);
|
||||
menu.style.transform = `translateY(${delta}px)`;
|
||||
};
|
||||
const end = () => {
|
||||
if (startY === null) return;
|
||||
startY = null;
|
||||
menu.style.removeProperty("transition");
|
||||
if (delta > Math.min(120, menu.offsetHeight / 4)) hideContextMenu();
|
||||
else menu.style.removeProperty("transform");
|
||||
delta = 0;
|
||||
};
|
||||
for (const zone of zones) {
|
||||
zone.addEventListener("touchstart", (event) => {
|
||||
if (event.touches.length !== 1) return;
|
||||
startY = event.touches[0].clientY;
|
||||
delta = 0;
|
||||
menu.style.transition = "none";
|
||||
}, { passive: true });
|
||||
zone.addEventListener("touchmove", move, { passive: true });
|
||||
zone.addEventListener("touchend", end, { passive: true });
|
||||
zone.addEventListener("touchcancel", end, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function contextSheetOpen() {
|
||||
return $("#context-menu")?.classList.contains("context-sheet") && !$("#context-menu").classList.contains("hidden");
|
||||
}
|
||||
|
||||
function contextMenuButtons(menu, items, { submenuHost = null } = {}) {
|
||||
@@ -692,8 +746,8 @@ function contextMenuButtons(menu, items, { submenuHost = null } = {}) {
|
||||
button.setAttribute("aria-haspopup", "menu");
|
||||
button.setAttribute("aria-expanded", "false");
|
||||
button.append(el("span", "context-menu-arrow", "›"));
|
||||
const open = () => openContextSubmenu(button, item.submenu);
|
||||
button.addEventListener("mouseenter", open);
|
||||
const open = () => (menu.closest(".context-sheet") ? openSheetSubmenu(menu, item) : openContextSubmenu(button, item.submenu));
|
||||
if (!menu.closest(".context-sheet")) button.addEventListener("mouseenter", open);
|
||||
button.addEventListener("click", (event) => { event.stopPropagation(); open(); });
|
||||
button.addEventListener("keydown", (event) => {
|
||||
if (event.key === "ArrowRight" || event.key === "Enter" || event.key === " ") {
|
||||
@@ -744,6 +798,19 @@ function openContextSubmenu(anchor, items) {
|
||||
submenu.style.top = `${Math.max(6, Math.min(anchorRect.top - 4, innerHeight - rect.height - 6))}px`;
|
||||
}
|
||||
|
||||
// Touch action sheet: submenus replace the list in place, with a way back.
|
||||
function openSheetSubmenu(list, item) {
|
||||
const previous = [...list.childNodes];
|
||||
list.replaceChildren();
|
||||
const back = el("button", "context-sheet-back", `‹ ${trText(item.label)}`);
|
||||
back.type = "button";
|
||||
back.setAttribute("aria-label", trText("Back"));
|
||||
back.addEventListener("click", (event) => { event.stopPropagation(); list.replaceChildren(...previous); });
|
||||
list.append(back, el("div", "separator"));
|
||||
contextMenuButtons(list, item.submenu);
|
||||
list.scrollTop = 0;
|
||||
}
|
||||
|
||||
function showContextMenu(x, y, items) {
|
||||
const menu = $("#context-menu");
|
||||
closeContextSubmenu();
|
||||
@@ -766,13 +833,72 @@ async function promptFolder(label, exclude = null) {
|
||||
return appSelect(trText(label), options.map((name) => ({ value: name, label: name })), { value: options[0], confirmLabel: trText("Choose folder") });
|
||||
}
|
||||
|
||||
function messageMenuState(message) {
|
||||
const flags = normalizedMessageFlags(message.flags);
|
||||
return { seen: flags.includes("\\Seen"), flagged: flags.includes("\\Flagged"), folder: message.folder || state.folder };
|
||||
}
|
||||
|
||||
function openMessageContextMenu(x, y, message) {
|
||||
const flags = Array.isArray(message.flags) ? message.flags.join(" ") : String(message.flags || "");
|
||||
const seen = flags.includes("\\Seen");
|
||||
const flagged = flags.includes("\\Flagged");
|
||||
const folder = message.folder || state.folder;
|
||||
showContextMenu(x, y, messageContextItems(message));
|
||||
}
|
||||
|
||||
// Long-press menu for touch screens: the desktop context menu as a bottom sheet,
|
||||
// headed by the row's hover actions (read, flag, archive, delete).
|
||||
function openMessageActionSheet(message) {
|
||||
const { seen, flagged } = messageMenuState(message);
|
||||
const menu = $("#context-menu");
|
||||
hideContextMenu();
|
||||
const backdrop = el("div", "context-sheet-backdrop");
|
||||
backdrop.id = "context-sheet-backdrop";
|
||||
document.body.append(backdrop);
|
||||
menu.classList.add("context-sheet");
|
||||
menu.setAttribute("role", "dialog");
|
||||
menu.setAttribute("aria-modal", "true");
|
||||
menu.setAttribute("aria-label", trText("Message actions"));
|
||||
menu.tabIndex = -1;
|
||||
contextSheetReturnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
|
||||
const header = el("div", "context-sheet-header");
|
||||
header.append(
|
||||
el("strong", "context-sheet-from", message.from_name || message.from_address || trText("Unknown")),
|
||||
el("span", "context-sheet-subject", message.subject || trText("(no subject)")),
|
||||
);
|
||||
const quick = el("div", "context-sheet-quick");
|
||||
const quickButton = (icon, label, action, extra = "") => {
|
||||
const button = el("button", `context-sheet-quick-action ${extra}`);
|
||||
button.type = "button";
|
||||
button.innerHTML = svgIcon(icon);
|
||||
button.append(el("span", "", trText(label)));
|
||||
button.addEventListener("click", async (event) => { event.stopPropagation(); hideContextMenu(); await action(); });
|
||||
quick.append(button);
|
||||
};
|
||||
quickButton(seen ? "mail" : "mail-open", seen ? "Mark unread" : "Mark read",
|
||||
() => quickUpdateMessageFlag(message, "\\Seen", !seen, seen ? "Marked unread" : "Marked read"));
|
||||
quickButton("star", flagged ? "Unflag" : "Flag",
|
||||
() => quickUpdateMessageFlag(message, "\\Flagged", !flagged, flagged ? "Flag removed" : "Message flagged"), flagged ? "active" : "");
|
||||
quickButton("archive", "Archive", () => performBulk("archive", null, [message]));
|
||||
quickButton("trash", "Delete", () => performBulk("delete", null, [message]), "danger");
|
||||
|
||||
const list = el("div", "context-sheet-list");
|
||||
list.setAttribute("role", "menu");
|
||||
const handle = el("div", "context-sheet-handle");
|
||||
menu.replaceChildren(handle, header, quick, list);
|
||||
bindContextSheetDrag(menu, [handle, header]);
|
||||
// Read/flag/archive/delete are in the quick row above; keep the rest.
|
||||
const skip = new Set(["Mark read", "Mark unread", "Flag", "Unflag", "Archive", "Delete", "Open full screen"]);
|
||||
const items = messageContextItems(message).filter((item) => item === "separator" || !skip.has(item.label));
|
||||
while (items[0] === "separator") items.shift();
|
||||
while (items.at(-1) === "separator") items.pop();
|
||||
contextMenuButtons(list, items.filter((item, i) => !(item === "separator" && items[i - 1] === "separator")));
|
||||
menu.classList.remove("hidden");
|
||||
if (!history.state?.rustboxActionSheet) history.pushState({ ...(history.state || {}), rustboxActionSheet: true }, "");
|
||||
menu.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
function messageContextItems(message) {
|
||||
const { seen, flagged, folder } = messageMenuState(message);
|
||||
const sieveEnabled = state.authPolicy.sieveEnabled !== false;
|
||||
showContextMenu(x, y, [
|
||||
return [
|
||||
{ label: "Open", action: () => openMessage(folder, message.uid) },
|
||||
{ label: "Open full screen", action: async () => { await ensureMessageOpen(message); enterReaderFullscreen(); } },
|
||||
"separator",
|
||||
@@ -783,8 +909,8 @@ function openMessageContextMenu(x, y, message) {
|
||||
{ label: "Forward as attachment", action: async () => { await ensureMessageOpen(message); await forwardAsAttachment(); } },
|
||||
] },
|
||||
"separator",
|
||||
{ label: seen ? "Mark unread" : "Mark read", action: async () => { await ensureMessageOpen(message); await updateFlag("\\Seen", !seen); } },
|
||||
{ label: flagged ? "Unflag" : "Flag", action: async () => { await ensureMessageOpen(message); await updateFlag("\\Flagged", !flagged); } },
|
||||
{ label: seen ? "Mark unread" : "Mark read", action: () => quickUpdateMessageFlag(message, "\\Seen", !seen, seen ? "Marked unread" : "Marked read") },
|
||||
{ label: flagged ? "Unflag" : "Flag", action: () => quickUpdateMessageFlag(message, "\\Flagged", !flagged, flagged ? "Flag removed" : "Message flagged") },
|
||||
{ label: "Labels", submenu: [
|
||||
{ label: "Add label…", action: async () => { const tag = await promptTag("Add label"); if (tag) await addTagToMessage(message, tag); } },
|
||||
{ label: "Remove label…", action: async () => { const attached = state.tags.filter((tag) => (message.tags || []).some((item) => item.id === tag.id)); const tag = await promptTag("Remove label", attached); if (tag) await removeTagFromMessage(message, tag); } },
|
||||
@@ -821,7 +947,7 @@ function openMessageContextMenu(x, y, message) {
|
||||
] },
|
||||
"separator",
|
||||
{ label: "Delete", danger: true, action: async () => { await ensureMessageOpen(message); await deleteCurrent(); } },
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
async function configureFolderFlaggedOnTop(folder) {
|
||||
|
||||
@@ -675,7 +675,8 @@ function bindEvents() {
|
||||
if (!event.target.closest(".toolbar-menu, .toolbar-menu-popover[data-toolbar-menu-owner]")) $$(".toolbar-menu[open]").forEach((menu) => { menu.open = false; });
|
||||
});
|
||||
window.addEventListener("blur", hideContextMenu);
|
||||
document.addEventListener("scroll", (event) => { if (!event.target?.closest?.(".context-menu")) hideContextMenu(); }, true);
|
||||
// The touch action sheet stays open while the list behind it moves; it closes via the backdrop.
|
||||
document.addEventListener("scroll", (event) => { if (!event.target?.closest?.(".context-menu") && !contextSheetOpen()) hideContextMenu(); }, true);
|
||||
document.addEventListener("keydown", (event) => {
|
||||
const tag = document.activeElement?.tagName || "";
|
||||
if (event.key === "/" && !["INPUT", "TEXTAREA"].includes(tag) && !document.activeElement?.isContentEditable) {
|
||||
@@ -712,6 +713,11 @@ function bindEvents() {
|
||||
else if (event.key === "!") { event.preventDefault(); junkCurrent(); }
|
||||
else if (event.key === "Delete") { event.preventDefault(); deleteCurrent(); }
|
||||
}
|
||||
if (event.key === "Escape" && contextSheetOpen()) {
|
||||
event.preventDefault();
|
||||
hideContextMenu();
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape" && $("#reader")?.classList.contains("reader-fullscreen")) {
|
||||
event.preventDefault();
|
||||
exitReaderFullscreen();
|
||||
@@ -733,6 +739,10 @@ function bindEvents() {
|
||||
if (state.currentDetail && state.prefs.adaptiveEmailColors) renderMessageDetail(state.currentDetail);
|
||||
}, 60000);
|
||||
window.addEventListener("popstate", async (event) => {
|
||||
if (contextSheetOpen() && !event.state?.rustboxActionSheet) {
|
||||
hideContextMenu({ fromHistory: true });
|
||||
return;
|
||||
}
|
||||
const full = event.state?.rustboxMailFullscreen === true;
|
||||
if (!full) {
|
||||
if ($("#reader")?.classList.contains("reader-fullscreen")) setReaderFullscreen(false, { pushHistory: false });
|
||||
|
||||
Reference in new issue
Block a user