feat: add right-click context menu to mail folders sidebar

This commit is contained in:
Linus Rath
2026-04-25 16:26:05 +02:00
parent b80678b00f
commit 4788e8a91a
21 changed files with 1057 additions and 1 deletions
+190
View File
@@ -14,6 +14,7 @@ import { useAccountStore } from "@/stores/account-store";
import type { UnifiedAccountClient } from "@/lib/unified-mailbox";
import { KeyboardShortcutsModal } from "@/components/keyboard-shortcuts-modal";
import { useEmailStore } from "@/stores/email-store";
import { toast } from "@/stores/toast-store";
import { useAuthStore, redirectToLogin } from "@/stores/auth-store";
import { useSettingsStore } from "@/stores/settings-store";
import { useContactStore } from "@/stores/contact-store";
@@ -162,6 +163,11 @@ export default function Home() {
fetchUnifiedEmails: fetchUnifiedEmailsAction,
refreshUnifiedCounts,
exitUnifiedView,
emptyMailbox,
markMailboxAsRead,
createMailbox,
renameMailbox,
deleteMailbox,
} = useEmailStore();
const enableUnifiedMailbox = useSettingsStore((s) => s.enableUnifiedMailbox);
@@ -1097,6 +1103,181 @@ export default function Home() {
}
};
const tCtxMenu = t;
const handleMarkFolderRead = async (mailboxId: string) => {
if (!client) return;
try {
const count = await markMailboxAsRead(client, mailboxId);
await fetchMailboxes(client);
if (selectedMailbox === mailboxId) await fetchEmails(client, mailboxId);
if (count > 0) {
toast.success(tCtxMenu('mailbox_context_menu.toast_marked_read_count', { count }));
} else {
toast.success(tCtxMenu('mailbox_context_menu.toast_already_read'));
}
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_mark_read'));
}
};
const handleMarkFolderTreeRead = async (mailboxId: string) => {
if (!client) return;
const collectIds = (rootId: string): string[] => {
const ids: string[] = [rootId];
const stack = [rootId];
while (stack.length > 0) {
const current = stack.pop()!;
for (const mb of mailboxes) {
if (mb.parentId === current) {
ids.push(mb.id);
stack.push(mb.id);
}
}
}
return ids;
};
try {
const ids = collectIds(mailboxId);
let total = 0;
for (const id of ids) {
total += await markMailboxAsRead(client, id);
}
await fetchMailboxes(client);
if (selectedMailbox && ids.includes(selectedMailbox)) await fetchEmails(client, selectedMailbox);
if (total > 0) {
toast.success(tCtxMenu('mailbox_context_menu.toast_marked_read_count', { count: total }));
} else {
toast.success(tCtxMenu('mailbox_context_menu.toast_already_read'));
}
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_mark_read'));
}
};
const handleMarkAllFoldersRead = async () => {
if (!client) return;
const confirmed = await confirmDialog({
title: tCtxMenu('mailbox_context_menu.mark_all_confirm_title'),
message: tCtxMenu('mailbox_context_menu.mark_all_confirm_message'),
confirmText: tCtxMenu('mailbox_context_menu.mark_all_folders_read'),
variant: "default",
});
if (!confirmed) return;
try {
const total = await client.markAllAsRead();
await fetchMailboxes(client);
if (selectedMailbox) await fetchEmails(client, selectedMailbox);
if (total > 0) {
toast.success(tCtxMenu('mailbox_context_menu.toast_marked_read_count', { count: total }));
} else {
toast.success(tCtxMenu('mailbox_context_menu.toast_already_read'));
}
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_mark_read'));
}
};
const handleEmptyFolderFromContextMenu = async (mailboxId: string) => {
if (!client) return;
const mailbox = mailboxes.find(mb => mb.id === mailboxId);
if (!mailbox) return;
const confirmed = await confirmDialog({
title: tCtxMenu('email_list.empty_folder.confirm_title'),
message: tCtxMenu('email_list.empty_folder.confirm_message'),
confirmText: tCtxMenu('email_list.empty_folder.confirm_button'),
variant: "destructive",
});
if (!confirmed) return;
try {
await emptyMailbox(client, mailboxId);
toast.success(tCtxMenu('mailbox_context_menu.toast_emptied'));
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_empty'));
}
};
const handleCreateSubfolderFromContextMenu = async (parentId: string) => {
if (!client) return;
const name = window.prompt(tCtxMenu('mailbox_context_menu.prompt_new_subfolder'));
if (!name || !name.trim()) return;
try {
await createMailbox(client, name.trim(), parentId);
toast.success(tCtxMenu('mailbox_context_menu.toast_folder_created'));
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_create'));
}
};
const handleCreateFolderFromContextMenu = async () => {
if (!client) return;
const name = window.prompt(tCtxMenu('mailbox_context_menu.prompt_new_folder'));
if (!name || !name.trim()) return;
try {
await createMailbox(client, name.trim());
toast.success(tCtxMenu('mailbox_context_menu.toast_folder_created'));
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_create'));
}
};
const handleRenameFolderFromContextMenu = async (mailboxId: string) => {
if (!client) return;
const mailbox = mailboxes.find(mb => mb.id === mailboxId);
if (!mailbox) return;
const name = window.prompt(tCtxMenu('mailbox_context_menu.prompt_rename'), mailbox.name);
if (!name || !name.trim() || name.trim() === mailbox.name) return;
try {
await renameMailbox(client, mailboxId, name.trim());
toast.success(tCtxMenu('mailbox_context_menu.toast_folder_renamed'));
} catch {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_rename'));
}
};
const handleDeleteFolderFromContextMenu = async (mailboxId: string) => {
if (!client) return;
const mailbox = mailboxes.find(mb => mb.id === mailboxId);
if (!mailbox) return;
const confirmed = await confirmDialog({
title: tCtxMenu('mailbox_context_menu.delete_confirm_title'),
message: tCtxMenu('mailbox_context_menu.delete_confirm_message', { name: mailbox.name }),
confirmText: tCtxMenu('mailbox_context_menu.delete_folder'),
variant: "destructive",
});
if (!confirmed) return;
try {
await deleteMailbox(client, mailboxId);
toast.success(tCtxMenu('mailbox_context_menu.toast_folder_deleted'));
} catch (err: unknown) {
const jmapType = (err as Error & { jmapType?: string })?.jmapType;
if (jmapType === 'mailboxHasChild') {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_delete_has_children'));
} else if (jmapType === 'mailboxHasEmail') {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_delete_has_email'));
} else {
toast.error(tCtxMenu('mailbox_context_menu.toast_error_delete'));
}
}
};
const handleRefreshMailboxes = async () => {
if (!client) return;
try {
await fetchMailboxes(client);
if (selectedMailbox) await fetchEmails(client, selectedMailbox);
} catch {
// silent
}
};
const handleLogout = logout;
const handleSearch = async (query: string) => {
@@ -1454,6 +1635,15 @@ export default function Home() {
onMailboxSelect={handleMailboxSelect}
onTagSelect={handleTagSelect}
onUnreadFilterClick={handleUnreadFilterClick}
onMarkFolderRead={handleMarkFolderRead}
onMarkFolderTreeRead={handleMarkFolderTreeRead}
onMarkAllFoldersRead={handleMarkAllFoldersRead}
onEmptyFolder={handleEmptyFolderFromContextMenu}
onCreateSubfolder={handleCreateSubfolderFromContextMenu}
onCreateFolder={handleCreateFolderFromContextMenu}
onRenameFolder={handleRenameFolderFromContextMenu}
onDeleteFolder={handleDeleteFolderFromContextMenu}
onRefreshMailboxes={handleRefreshMailboxes}
onCompose={() => {
setComposerMode('compose');
setShowComposer(true);
+171
View File
@@ -0,0 +1,171 @@
"use client";
import { useTranslations } from "next-intl";
import { Mailbox } from "@/lib/jmap/types";
import {
ContextMenu,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuHeader,
} from "@/components/ui/context-menu";
import {
CheckCheck,
MailOpen,
Mails,
Trash2,
FolderPlus,
Pencil,
FolderX,
RefreshCw,
} from "lucide-react";
interface Position {
x: number;
y: number;
}
export type MailboxContextTarget =
| { kind: "mailbox"; mailbox: Mailbox; hasChildren: boolean }
| { kind: "folders-section" };
interface MailboxContextMenuProps {
target: MailboxContextTarget | null;
position: Position;
isOpen: boolean;
onClose: () => void;
menuRef: React.RefObject<HTMLDivElement | null>;
onMarkFolderRead?: (mailboxId: string) => void;
onMarkFolderTreeRead?: (mailboxId: string) => void;
onMarkAllFoldersRead?: () => void;
onEmptyFolder?: (mailboxId: string) => void;
onCreateSubfolder?: (parentId: string) => void;
onCreateFolder?: () => void;
onRenameFolder?: (mailboxId: string) => void;
onDeleteFolder?: (mailboxId: string) => void;
onRefresh?: () => void;
}
export function MailboxContextMenu({
target,
position,
isOpen,
onClose,
menuRef,
onMarkFolderRead,
onMarkFolderTreeRead,
onMarkAllFoldersRead,
onEmptyFolder,
onCreateSubfolder,
onCreateFolder,
onRenameFolder,
onDeleteFolder,
onRefresh,
}: MailboxContextMenuProps) {
const t = useTranslations("mailbox_context_menu");
const handleAction = (action: () => void) => {
action();
onClose();
};
if (!target) return null;
if (target.kind === "folders-section") {
return (
<ContextMenu ref={menuRef} isOpen={isOpen} position={position} onClose={onClose}>
<ContextMenuItem
icon={CheckCheck}
label={t("mark_all_folders_read")}
onClick={() => handleAction(onMarkAllFoldersRead!)}
disabled={!onMarkAllFoldersRead}
/>
<ContextMenuSeparator />
<ContextMenuItem
icon={FolderPlus}
label={t("new_folder")}
onClick={() => handleAction(onCreateFolder!)}
disabled={!onCreateFolder}
/>
<ContextMenuItem
icon={RefreshCw}
label={t("refresh")}
onClick={() => handleAction(onRefresh!)}
disabled={!onRefresh}
/>
</ContextMenu>
);
}
const mailbox = target.mailbox;
const isTrashOrJunk = mailbox.role === "trash" || mailbox.role === "junk";
const isSystem =
!!mailbox.role &&
["inbox", "sent", "drafts", "trash", "junk", "archive"].includes(mailbox.role);
const canRename = mailbox.myRights?.mayRename !== false && !isSystem;
const canDelete = mailbox.myRights?.mayDelete !== false && !isSystem;
const canCreateChild = mailbox.myRights?.mayCreateChild !== false;
const canSetSeen = mailbox.myRights?.maySetSeen !== false;
const canRemoveItems = mailbox.myRights?.mayRemoveItems !== false;
return (
<ContextMenu ref={menuRef} isOpen={isOpen} position={position} onClose={onClose}>
<ContextMenuHeader>{mailbox.name}</ContextMenuHeader>
<ContextMenuItem
icon={MailOpen}
label={t("mark_folder_read")}
onClick={() => handleAction(() => onMarkFolderRead?.(mailbox.id))}
disabled={!onMarkFolderRead || !canSetSeen}
/>
{target.hasChildren && (
<ContextMenuItem
icon={Mails}
label={t("mark_folder_tree_read")}
onClick={() => handleAction(() => onMarkFolderTreeRead?.(mailbox.id))}
disabled={!onMarkFolderTreeRead || !canSetSeen}
/>
)}
<ContextMenuSeparator />
<ContextMenuItem
icon={FolderPlus}
label={t("new_subfolder")}
onClick={() => handleAction(() => onCreateSubfolder?.(mailbox.id))}
disabled={!onCreateSubfolder || !canCreateChild}
/>
<ContextMenuItem
icon={Pencil}
label={t("rename")}
onClick={() => handleAction(() => onRenameFolder?.(mailbox.id))}
disabled={!onRenameFolder || !canRename}
/>
<ContextMenuSeparator />
<ContextMenuItem
icon={FolderX}
label={isTrashOrJunk ? t("empty_folder") : t("empty_folder_generic")}
onClick={() => handleAction(() => onEmptyFolder?.(mailbox.id))}
disabled={!onEmptyFolder || mailbox.totalEmails === 0 || !canRemoveItems}
destructive
/>
<ContextMenuItem
icon={Trash2}
label={t("delete_folder")}
onClick={() => handleAction(() => onDeleteFolder?.(mailbox.id))}
disabled={!onDeleteFolder || !canDelete}
destructive
/>
<ContextMenuSeparator />
<ContextMenuItem
icon={RefreshCw}
label={t("refresh")}
onClick={() => handleAction(onRefresh!)}
disabled={!onRefresh}
/>
</ContextMenu>
);
}
+64 -1
View File
@@ -31,6 +31,8 @@ import {
} from "lucide-react";
import { cn, buildMailboxTree, MailboxNode } from "@/lib/utils";
import { Mailbox } from "@/lib/jmap/types";
import { useContextMenu } from "@/hooks/use-context-menu";
import { MailboxContextMenu, type MailboxContextTarget } from "./mailbox-context-menu";
import { useAccountStore } from '@/stores/account-store';
import { UNIFIED_MAILBOX_IDS } from '@/lib/jmap/types';
import type { UnifiedMailboxRole } from '@/lib/jmap/types';
@@ -56,6 +58,15 @@ interface SidebarProps {
onCompose?: () => void;
onSidebarClose?: () => void;
onUnreadFilterClick?: (mailboxId: string) => void;
onMarkFolderRead?: (mailboxId: string) => void;
onMarkFolderTreeRead?: (mailboxId: string) => void;
onMarkAllFoldersRead?: () => void;
onEmptyFolder?: (mailboxId: string) => void;
onCreateSubfolder?: (parentId: string) => void;
onCreateFolder?: () => void;
onRenameFolder?: (mailboxId: string) => void;
onDeleteFolder?: (mailboxId: string) => void;
onRefreshMailboxes?: () => void;
className?: string;
}
@@ -187,6 +198,7 @@ interface SidebarRowProps {
dropHandlers?: Record<string, unknown>;
isValidDropTarget?: boolean;
isInvalidDropTarget?: boolean;
onContextMenu?: (e: React.MouseEvent) => void;
}
function SidebarRow({
@@ -206,6 +218,7 @@ function SidebarRow({
dropHandlers,
isValidDropTarget,
isInvalidDropTarget,
onContextMenu,
}: SidebarRowProps) {
const t = useTranslations('sidebar');
const leftPad = isCollapsed ? 0 : ROW_PX_BASE + depth * INDENT_STEP;
@@ -213,6 +226,7 @@ function SidebarRow({
return (
<div
{...(dropHandlers || {})}
onContextMenu={onContextMenu}
style={{ paddingBlock: 'var(--density-sidebar-py)' }}
className={cn(
"group w-full flex items-center max-lg:min-h-[44px] text-sm transition-colors duration-150",
@@ -365,6 +379,7 @@ function MailboxTreeItem({
isCollapsed,
onUnreadFilterClick,
colorful,
onContextMenu,
}: {
node: MailboxNode;
selectedMailbox: string;
@@ -374,6 +389,7 @@ function MailboxTreeItem({
isCollapsed: boolean;
onUnreadFilterClick?: (mailboxId: string) => void;
colorful: boolean;
onContextMenu?: (e: React.MouseEvent, node: MailboxNode) => void;
}) {
const tNotifications = useTranslations('notifications');
const hasChildren = node.children.length > 0;
@@ -423,6 +439,7 @@ function MailboxTreeItem({
dropHandlers={globalDragging ? (dropHandlers as Record<string, unknown>) : undefined}
isValidDropTarget={isValidDropTarget}
isInvalidDropTarget={isInvalidDropTarget}
onContextMenu={onContextMenu && !isVirtualNode ? (e) => onContextMenu(e, node) : undefined}
/>
{hasChildren && isExpanded && !isCollapsed && node.children.map((child) => (
@@ -436,6 +453,7 @@ function MailboxTreeItem({
isCollapsed={isCollapsed}
onUnreadFilterClick={onUnreadFilterClick}
colorful={colorful}
onContextMenu={onContextMenu}
/>
))}
</>
@@ -610,6 +628,15 @@ export function Sidebar({
onCompose: _onCompose,
onSidebarClose,
onUnreadFilterClick,
onMarkFolderRead,
onMarkFolderTreeRead,
onMarkAllFoldersRead,
onEmptyFolder,
onCreateSubfolder,
onCreateFolder,
onRenameFolder,
onDeleteFolder,
onRefreshMailboxes,
className,
}: SidebarProps) {
const router = useRouter();
@@ -790,6 +817,23 @@ export function Sidebar({
router.push('/settings');
};
const {
contextMenu: mailboxContextMenu,
openContextMenu: openMailboxContextMenu,
closeContextMenu: closeMailboxContextMenu,
menuRef: mailboxMenuRef,
} = useContextMenu<MailboxContextTarget>();
const handleMailboxContextMenu = (e: React.MouseEvent, node: MailboxNode) => {
const mailbox = mailboxes.find(mb => mb.id === node.id);
if (!mailbox) return;
openMailboxContextMenu(e, { kind: "mailbox", mailbox, hasChildren: node.children.length > 0 });
};
const handleFoldersHeaderContextMenu = (e: React.MouseEvent) => {
openMailboxContextMenu(e, { kind: "folders-section" });
};
return (
<div
className={cn(
@@ -866,7 +910,7 @@ export function Sidebar({
</div>
)}
<div>
<div onContextMenu={handleFoldersHeaderContextMenu}>
<SidebarSectionHeader
label={t("folders")}
expanded={foldersExpanded}
@@ -894,6 +938,7 @@ export function Sidebar({
isCollapsed={isCollapsed}
onUnreadFilterClick={onUnreadFilterClick}
colorful={colorfulSidebarIcons}
onContextMenu={handleMailboxContextMenu}
/>
))
)}
@@ -934,6 +979,7 @@ export function Sidebar({
isCollapsed={isCollapsed}
onUnreadFilterClick={onUnreadFilterClick}
colorful={colorfulSidebarIcons}
onContextMenu={handleMailboxContextMenu}
/>
))}
</div>
@@ -975,6 +1021,23 @@ export function Sidebar({
{!isCollapsed && <PluginSlot name="sidebar-widget" className="border-t border-border" />}
</div>
<MailboxContextMenu
target={mailboxContextMenu.data}
position={mailboxContextMenu.position}
isOpen={mailboxContextMenu.isOpen}
onClose={closeMailboxContextMenu}
menuRef={mailboxMenuRef}
onMarkFolderRead={onMarkFolderRead}
onMarkFolderTreeRead={onMarkFolderTreeRead}
onMarkAllFoldersRead={onMarkAllFoldersRead}
onEmptyFolder={onEmptyFolder}
onCreateSubfolder={onCreateSubfolder}
onCreateFolder={onCreateFolder}
onRenameFolder={onRenameFolder}
onDeleteFolder={onDeleteFolder}
onRefresh={onRefreshMailboxes}
/>
</div>
);
}
+27
View File
@@ -312,6 +312,33 @@ export class DemoJMAPClient implements IJMAPClient {
return removed;
}
async markMailboxAsRead(mailboxId: string): Promise<number> {
let count = 0;
for (const email of this.data.emails) {
if (email.mailboxIds[mailboxId] && email.keywords.$seen !== true) {
email.keywords.$seen = true;
count++;
}
}
this.recalcMailboxCounts();
return count;
}
async markAllAsRead(excludeMailboxIds: string[] = []): Promise<number> {
const excluded = new Set(excludeMailboxIds);
let count = 0;
for (const email of this.data.emails) {
if (email.keywords.$seen === true) continue;
const mbIds = Object.keys(email.mailboxIds);
const onlyInExcluded = mbIds.length > 0 && mbIds.every(id => excluded.has(id));
if (onlyInExcluded) continue;
email.keywords.$seen = true;
count++;
}
this.recalcMailboxCounts();
return count;
}
async markAsSpam(emailId: string): Promise<void> {
const email = this.data.emails.find(e => e.id === emailId);
const junkMb = this.data.mailboxes.find(m => m.role === 'junk');
+2
View File
@@ -94,6 +94,8 @@ export interface IJMAPClient {
): Promise<void>;
moveEmail(emailId: string, toMailboxId: string, accountId?: string): Promise<void>;
emptyMailbox(mailboxId: string): Promise<number>;
markMailboxAsRead(mailboxId: string, accountId?: string): Promise<number>;
markAllAsRead(excludeMailboxIds?: string[], accountId?: string): Promise<number>;
markAsSpam(emailId: string, accountId?: string): Promise<void>;
undoSpam(emailId: string, originalMailboxId: string, accountId?: string): Promise<void>;
+93
View File
@@ -1361,6 +1361,99 @@ export class JMAPClient implements IJMAPClient {
return totalDestroyed;
}
async markMailboxAsRead(mailboxId: string, accountId?: string): Promise<number> {
const targetAccountId = accountId || this.accountId;
let totalMarked = 0;
let hasMore = true;
while (hasMore) {
const queryResponse = await this.request([
["Email/query", {
accountId: targetAccountId,
filter: {
operator: "AND",
conditions: [
{ inMailbox: mailboxId },
{ notKeyword: "$seen" },
],
},
limit: 500,
}, "0"],
]);
const ids: string[] = queryResponse.methodResponses?.[0]?.[1]?.ids || [];
if (ids.length === 0) break;
const updates = Object.fromEntries(
ids.map((id) => [id, { "keywords/$seen": true }])
);
await this.request([
["Email/set", { accountId: targetAccountId, update: updates }, "0"],
]);
totalMarked += ids.length;
hasMore = ids.length === 500;
}
return totalMarked;
}
async markAllAsRead(excludeMailboxIds: string[] = [], accountId?: string): Promise<number> {
const targetAccountId = accountId || this.accountId;
const excludeSet = new Set(excludeMailboxIds);
let totalMarked = 0;
let hasMore = true;
let position = 0;
while (hasMore) {
const response = await this.request([
["Email/query", {
accountId: targetAccountId,
filter: { notKeyword: "$seen" },
limit: 500,
position,
}, "0"],
["Email/get", {
accountId: targetAccountId,
"#ids": { resultOf: "0", name: "Email/query", path: "/ids" },
properties: ["id", "mailboxIds"],
}, "1"],
]);
const queryResult = response.methodResponses?.[0]?.[1];
const getResult = response.methodResponses?.[1]?.[1];
const ids: string[] = queryResult?.ids || [];
const emails: Array<{ id: string; mailboxIds?: Record<string, boolean> }> = getResult?.list || [];
if (ids.length === 0) break;
const targetIds = excludeSet.size === 0
? ids
: emails
.filter(e => {
const mbIds = e.mailboxIds ? Object.keys(e.mailboxIds) : [];
return mbIds.some(id => !excludeSet.has(id));
})
.map(e => e.id);
if (targetIds.length > 0) {
const updates = Object.fromEntries(
targetIds.map((id) => [id, { "keywords/$seen": true }])
);
await this.request([
["Email/set", { accountId: targetAccountId, update: updates }, "0"],
]);
totalMarked += targetIds.length;
}
hasMore = ids.length === 500;
position += ids.length;
}
return totalMarked;
}
async markAsSpam(emailId: string, accountId?: string): Promise<void> {
const targetAccountId = accountId || this.accountId;
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} E-Mails ausgewählt",
"edit_draft": "Entwurf bearbeiten"
},
"mailbox_context_menu": {
"mark_folder_read": "Ordner als gelesen markieren",
"mark_folder_tree_read": "Ordner & Unterordner als gelesen markieren",
"mark_all_folders_read": "Alle Ordner als gelesen markieren",
"new_subfolder": "Neuer Unterordner...",
"new_folder": "Neuer Ordner...",
"rename": "Umbenennen...",
"empty_folder": "Ordner leeren",
"empty_folder_generic": "Ordner leeren",
"delete_folder": "Ordner löschen",
"refresh": "Aktualisieren",
"mark_all_confirm_title": "Alle Ordner als gelesen markieren",
"mark_all_confirm_message": "Jede ungelesene Nachricht in deinem persönlichen Konto als gelesen markieren?",
"delete_confirm_title": "Ordner löschen",
"delete_confirm_message": "Den Ordner \"{name}\" dauerhaft löschen? Dies kann nicht rückgängig gemacht werden.",
"prompt_new_subfolder": "Name des neuen Unterordners:",
"prompt_new_folder": "Name des neuen Ordners:",
"prompt_rename": "Ordner umbenennen in:",
"toast_marked_read": "Ordner als gelesen markiert",
"toast_marked_read_count": "{count, plural, one {1 Nachricht} other {# Nachrichten}} als gelesen markiert",
"toast_already_read": "Keine ungelesenen Nachrichten",
"toast_marked_all_read": "Alle Ordner als gelesen markiert",
"toast_emptied": "Ordner geleert",
"toast_folder_created": "Ordner erstellt",
"toast_folder_renamed": "Ordner umbenannt",
"toast_folder_deleted": "Ordner gelöscht",
"toast_error_mark_read": "Konnte nicht als gelesen markiert werden",
"toast_error_empty": "Ordner konnte nicht geleert werden",
"toast_error_create": "Ordner konnte nicht erstellt werden",
"toast_error_rename": "Ordner konnte nicht umbenannt werden",
"toast_error_delete": "Ordner konnte nicht gelöscht werden",
"toast_error_delete_has_children": "Ordner enthält Unterordner. Entferne diese zuerst.",
"toast_error_delete_has_email": "Ordner ist nicht leer. Leere ihn zuerst."
},
"shortcuts": {
"title": "Tastaturkürzel",
"tip": "Drücken Sie ? jederzeit, um diese Hilfe anzuzeigen",
+34
View File
@@ -1594,6 +1594,40 @@
"items_selected": "{count} emails selected",
"edit_draft": "Edit Draft"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Keyboard Shortcuts",
"tip": "Press ? anytime to show this help",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} correos seleccionados",
"edit_draft": "Editar borrador"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Atajos de Teclado",
"tip": "Presione ? en cualquier momento para mostrar esta ayuda",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} emails sélectionnés",
"edit_draft": "Modifier le brouillon"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Raccourcis clavier",
"tip": "Appuyez sur ? à tout moment pour afficher cette aide",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} messaggi selezionati",
"edit_draft": "Modifica bozza"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Scorciatoie da tastiera",
"tip": "Premi ? in qualsiasi momento per mostrare questo aiuto",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count}件のメールを選択",
"edit_draft": "下書きを編集"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "キーボードショートカット",
"tip": "? キーを押すといつでもこのヘルプを表示できます",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count}개의 메일 선택됨",
"edit_draft": "임시보관 메일 수정"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "단축키",
"tip": "언제든 ? 키를 누르면 이 도움말을 볼 수 있어요",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} vēstules atlasītas",
"edit_draft": "Rediģēt melnrakstu"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Īsinājumtaustiņi",
"tip": "Nospiediet ? jebkurā laikā, lai skatītu palīdzību",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} e-mails geselecteerd",
"edit_draft": "Concept bewerken"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Sneltoetsen",
"tip": "Druk op ? om deze hulp te tonen",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} zaznaczonych wiadomości",
"edit_draft": "Edytuj szkic"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Skróty klawiszowe",
"tip": "Naciśnij ? w dowolnym momencie, aby wyświetlić tę pomoc",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} e-mails selecionados",
"edit_draft": "Editar rascunho"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Atalhos de Teclado",
"tip": "Pressione ? a qualquer momento para mostrar esta ajuda",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "{count} писем выбрано",
"edit_draft": "Редактировать черновик"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Сочетания клавиш",
"tip": "Нажмите ? в любое время для отображения справки",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "Вибрано електронних листів: {count}",
"edit_draft": "Редагувати чернетку"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "Комбінації клавіш",
"tip": "Натисніть ? у будь-який час, щоб показати цю допомогу",
+34
View File
@@ -1590,6 +1590,40 @@
"items_selected": "已选择 {count} 封邮件",
"edit_draft": "编辑草稿"
},
"mailbox_context_menu": {
"mark_folder_read": "Mark folder as read",
"mark_folder_tree_read": "Mark folder & subfolders as read",
"mark_all_folders_read": "Mark all folders as read",
"new_subfolder": "New subfolder...",
"new_folder": "New folder...",
"rename": "Rename...",
"empty_folder": "Empty folder",
"empty_folder_generic": "Empty folder",
"delete_folder": "Delete folder",
"refresh": "Refresh",
"mark_all_confirm_title": "Mark all folders as read",
"mark_all_confirm_message": "Mark every unread message in your personal account as read?",
"delete_confirm_title": "Delete folder",
"delete_confirm_message": "Permanently delete the folder \"{name}\"? This action cannot be undone.",
"prompt_new_subfolder": "New subfolder name:",
"prompt_new_folder": "New folder name:",
"prompt_rename": "Rename folder to:",
"toast_marked_read": "Folder marked as read",
"toast_marked_read_count": "Marked {count, plural, one {1 message} other {# messages}} as read",
"toast_already_read": "No unread messages",
"toast_marked_all_read": "All folders marked as read",
"toast_emptied": "Folder emptied",
"toast_folder_created": "Folder created",
"toast_folder_renamed": "Folder renamed",
"toast_folder_deleted": "Folder deleted",
"toast_error_mark_read": "Failed to mark as read",
"toast_error_empty": "Failed to empty folder",
"toast_error_create": "Failed to create folder",
"toast_error_rename": "Failed to rename folder",
"toast_error_delete": "Failed to delete folder",
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
},
"shortcuts": {
"title": "键盘快捷键",
"tip": "按?随时显示此帮助",
+34
View File
@@ -118,6 +118,7 @@ interface EmailStore {
deleteMailbox: (client: IJMAPClient, mailboxId: string) => Promise<void>;
setMailboxRole: (client: IJMAPClient, mailboxId: string, role: string | null) => Promise<void>;
emptyMailbox: (client: IJMAPClient, mailboxId: string) => Promise<void>;
markMailboxAsRead: (client: IJMAPClient, mailboxId: string) => Promise<number>;
// Unified mailbox operations
fetchUnifiedEmails: (accounts: UnifiedAccountClient[], role: UnifiedMailboxRole) => Promise<void>;
@@ -1750,6 +1751,39 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
}
},
markMailboxAsRead: async (client, mailboxId) => {
try {
const mailbox = get().mailboxes.find(mb => mb.id === mailboxId);
const accountId = mailbox?.isShared ? mailbox.accountId : undefined;
const jmapMailboxId = mailbox?.originalId || mailboxId;
const count = await client.markMailboxAsRead(jmapMailboxId, accountId);
// Update local state: mark all emails currently visible in this mailbox as read,
// and zero-out the mailbox unread counter.
set((state) => ({
emails: state.emails.map(e =>
e.mailboxIds && e.mailboxIds[mailboxId]
? { ...e, keywords: { ...e.keywords, $seen: true } }
: e
),
selectedEmail: state.selectedEmail && state.selectedEmail.mailboxIds?.[mailboxId]
? { ...state.selectedEmail, keywords: { ...state.selectedEmail.keywords, $seen: true } }
: state.selectedEmail,
mailboxes: state.mailboxes.map(mb =>
mb.id === mailboxId
? { ...mb, unreadEmails: 0, unreadThreads: 0 }
: mb
),
}));
return count;
} catch (error) {
set({ error: error instanceof Error ? error.message : 'Failed to mark folder as read' });
throw error;
}
},
// Unified mailbox operations
fetchUnifiedEmails: async (accounts, role) => {
set({