diff --git a/components/layout/__tests__/sidebar-folder-touch-menu.test.tsx b/components/layout/__tests__/sidebar-folder-touch-menu.test.tsx
new file mode 100644
index 000000000..9d266caab
--- /dev/null
+++ b/components/layout/__tests__/sidebar-folder-touch-menu.test.tsx
@@ -0,0 +1,60 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import type { Mailbox } from '@/lib/jmap/types';
+
+// Touch has no right-click: a folder's menu (rename, delete, new subfolder,
+// empty, mark read) opens on a long-press and from the row's ⋮ (R7).
+
+vi.mock('@/components/plugins/plugin-slot', () => ({ PluginSlot: () => null }));
+vi.mock('@/components/tour/tour-provider', () => ({
+ useTour: () => ({ startTour: vi.fn(), resetTourCompletion: vi.fn(), activeTour: null, endTour: vi.fn(), registerStep: vi.fn() }),
+}));
+vi.mock('@/contexts/drag-drop-context', () => ({
+ useDragDropContext: () => ({ isDragging: false }),
+}));
+vi.mock('@/hooks/use-mailbox-drop', () => ({
+ useMailboxDrop: () => ({ dropHandlers: {}, isValidDropTarget: false, isInvalidDropTarget: false }),
+}));
+vi.mock('@/hooks/use-tag-drop', () => ({
+ useTagDrop: () => ({ dropHandlers: {}, isValidDropTarget: false }),
+}));
+vi.mock('../account-switcher', () => ({ AccountSwitcher: () => null }));
+
+const { Sidebar } = await import('../sidebar');
+const { useAuthStore } = await import('@/stores/auth-store');
+const { useSettingsStore } = await import('@/stores/settings-store');
+
+const box = (id: string, name: string, role: string | null) => ({
+ id, name, role, parentId: null, totalEmails: 0, unreadEmails: 0,
+ myRights: { mayAddItems: true, mayRename: true, mayDelete: true, mayCreateChild: true },
+}) as unknown as Mailbox;
+
+const mailboxes: Mailbox[] = [box('inbox', 'Inbox', 'inbox'), box('travel', 'Travel', null)];
+const row = (mailboxId: string) =>
+ screen.getAllByTestId('folder-row').find((el) => el.getAttribute('data-mailbox-id') === mailboxId)!;
+
+describe('folder menu on touch', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ useAuthStore.setState({ activeAccountId: 'login-a', client: { getAccountId: () => 'me', getCapabilities: () => ({}) } } as never);
+ useSettingsStore.setState({ hideAccountSwitcher: true } as never);
+ });
+ afterEach(() => vi.useRealTimers());
+
+ it('opens on a long-press without opening the folder', () => {
+ const onMailboxSelect = vi.fn();
+ render();
+ fireEvent.touchStart(row('travel'), { touches: [{ clientX: 40, clientY: 200 }] });
+ act(() => { vi.advanceTimersByTime(600); });
+ fireEvent.touchEnd(row('travel'));
+ expect(screen.getByRole('menu')).toBeTruthy();
+ expect(screen.getByText('rename')).toBeTruthy();
+ expect(onMailboxSelect).not.toHaveBeenCalled();
+ });
+
+ it('opens from the row’s options button', () => {
+ render();
+ fireEvent.click(row('travel').querySelector('[data-testid="folder-row-menu"]')!);
+ expect(screen.getByRole('menu')).toBeTruthy();
+ });
+});
diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx
index c4dfa600c..a27204797 100644
--- a/components/layout/sidebar.tsx
+++ b/components/layout/sidebar.tsx
@@ -1,6 +1,6 @@
"use client";
-import { memo, useState, useEffect, useMemo, Fragment, ReactNode } from "react";
+import { memo, useState, useEffect, useMemo, useRef, Fragment, ReactNode } from "react";
import { useTranslations } from "next-intl";
import { useRouter } from "@/i18n/navigation";
import { PluginSlot } from "@/components/plugins/plugin-slot";
@@ -40,6 +40,7 @@ import {
Mails,
MailOpen,
MoreHorizontal,
+ MoreVertical,
} from "@/components/icons";
import { cn, buildMailboxTree, MailboxNode } from "@/lib/utils";
import { localizeMailboxName } from "@/lib/mailbox-label";
@@ -55,6 +56,8 @@ import { useKeywordFormat } from "@/hooks/use-keyword-format";
import { isEditableEventTarget } from "@/lib/keyboard";
import { Mailbox } from "@/lib/jmap/types";
import { useContextMenu } from "@/hooks/use-context-menu";
+import { useLongPress } from "@/hooks/use-long-press";
+import { IconButton } from "@/components/ui/icon-button";
import { MailboxContextMenu, type MailboxContextTarget, type MailboxNotificationActions } from "./mailbox-context-menu";
import { useAccountStore } from '@/stores/account-store';
import { useShallow } from "zustand/react/shallow";
@@ -299,6 +302,23 @@ function SidebarRowCounts({
);
}
+/**
+ * What a row's menu handler needs from an event, for a menu opened by a
+ * long-press (at the finger) or from the row's ⋮ (under the button).
+ */
+function menuEventAt(element: HTMLElement, point?: { clientX: number; clientY: number }): React.MouseEvent {
+ return {
+ type: point ? "contextmenu" : "click",
+ detail: 0,
+ clientX: point?.clientX ?? 0,
+ clientY: point?.clientY ?? 0,
+ currentTarget: element,
+ target: element,
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ } as unknown as React.MouseEvent;
+}
+
interface SidebarRowProps {
icon: ReactNode;
label: string;
@@ -357,11 +377,23 @@ function SidebarRow({
const t = useTranslations('sidebar');
const leftPad = isCollapsed ? 0 : ROW_PX_BASE + depth * INDENT_STEP;
const [labelRef, shortenedLabel] = useShortenedText(labelCandidates ?? [label]);
+ // Touch has no right-click: a long-press opens the same menu (R7), and a
+ // ⋮ shows it too.
+ const rowRef = useRef(null);
+ const hasMenu = !!onContextMenu && !isVirtual;
+ const longPress = useLongPress((point) => {
+ if (rowRef.current && onContextMenu) onContextMenu(menuEventAt(rowRef.current, point));
+ }, hasMenu);
return (
)}
+ {hasMenu && !isCollapsed && (
+ {
+ e.stopPropagation();
+ onContextMenu(menuEventAt(e.currentTarget));
+ }}
+ >
+
+
+ )}
);
}
diff --git a/locales/ar/common.json b/locales/ar/common.json
index 04c5af611..ba58ad482 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -155,6 +155,7 @@
"shared": "مشترك",
"shared_account_hidden": "تم الإخفاء من الشريط الجانبي. أعد إظهاره من الإعدادات › التخطيط.",
"notifications_muted": "الإشعارات مكتومة",
+ "folder_options": "خيارات {name}",
"mail": "البريد",
"nav_label": "التنقل",
"add_app": "التطبيقات",
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 32a85be65..8ad4c952f 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -155,6 +155,7 @@
"shared": "Compartit",
"shared_account_hidden": "Amagat de la barra lateral. Torna'l a mostrar a Configuració › Disposició.",
"notifications_muted": "Notificacions silenciades",
+ "folder_options": "Opcions de {name}",
"mail": "Correu",
"nav_label": "Navegació",
"add_app": "Aplicacions",
diff --git a/locales/cs/common.json b/locales/cs/common.json
index 6e498f5da..5bec7d27d 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -155,6 +155,7 @@
"shared": "Sdílené",
"shared_account_hidden": "Skryto z postranního panelu. Znovu zobrazíte v Nastavení › Vzhled.",
"notifications_muted": "Oznámení ztlumena",
+ "folder_options": "Možnosti pro {name}",
"mail": "Pošta",
"nav_label": "Navigace",
"add_app": "Aplikace",
diff --git a/locales/da/common.json b/locales/da/common.json
index 2350c8aa3..bb2ca721f 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -155,6 +155,7 @@
"shared": "Delt",
"shared_account_hidden": "Skjult i sidepanelet. Vis den igen under Indstillinger › Layout.",
"notifications_muted": "Notifikationer slået fra",
+ "folder_options": "Indstillinger for {name}",
"mail": "Mail",
"nav_label": "Navigation",
"add_app": "Apps",
diff --git a/locales/de/common.json b/locales/de/common.json
index ac52beb27..71e1808b2 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -158,6 +158,7 @@
"shared": "Geteilt",
"shared_account_hidden": "In der Seitenleiste ausgeblendet. Unter Einstellungen › Layout wieder einblenden.",
"notifications_muted": "Benachrichtigungen stummgeschaltet",
+ "folder_options": "Optionen für {name}",
"scheduled": "Geplant",
"unified_all_unread": "Alle ungelesenen",
"unified_all_starred": "Alle markierten",
diff --git a/locales/en/common.json b/locales/en/common.json
index 21341f53f..c2fedda80 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -155,6 +155,7 @@
"shared": "Shared",
"shared_account_hidden": "Hidden from the sidebar. Turn it back on in Settings › Layout.",
"notifications_muted": "Notifications muted",
+ "folder_options": "Options for {name}",
"mail": "Mail",
"nav_label": "Navigation",
"add_app": "Apps",
diff --git a/locales/es/common.json b/locales/es/common.json
index 1b0e26601..e77829694 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -158,6 +158,7 @@
"shared": "Compartido",
"shared_account_hidden": "Oculta en la barra lateral. Vuelve a mostrarla en Configuración › Diseño.",
"notifications_muted": "Notificaciones silenciadas",
+ "folder_options": "Opciones de {name}",
"scheduled": "Programados",
"unified_all_unread": "Todo no leído",
"unified_all_starred": "Todo destacado",
diff --git a/locales/et/common.json b/locales/et/common.json
index 2741e976b..9ce85ff8c 100644
--- a/locales/et/common.json
+++ b/locales/et/common.json
@@ -155,6 +155,7 @@
"shared": "Jagatud",
"shared_account_hidden": "Külgribal peidetud. Lülita see tagasi sisse menüüs Seaded › Paigutus.",
"notifications_muted": "Märguanded vaigistatud",
+ "folder_options": "{name}: valikud",
"mail": "Kirjad",
"nav_label": "Navigeerimine",
"add_app": "Rakendused",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index ce2f88b7a..b2030e463 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -155,6 +155,7 @@
"shared": "اشتراکی",
"shared_account_hidden": "از نوار کناری پنهان شد. از تنظیمات › چیدمان دوباره نمایش دهید.",
"notifications_muted": "اعلانها بیصدا",
+ "folder_options": "گزینههای {name}",
"mail": "ایمیل",
"nav_label": "ناوبری",
"add_app": "برنامهها",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 48ab05e11..cc8ffcec0 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -158,6 +158,7 @@
"shared": "Partagé",
"shared_account_hidden": "Masqué de la barre latérale. Réaffichez-le dans Paramètres › Mise en page.",
"notifications_muted": "Notifications désactivées",
+ "folder_options": "Options de {name}",
"scheduled": "Planifiés",
"unified_all_unread": "Tous les non lus",
"unified_all_starred": "Tous les favoris",
diff --git a/locales/he/common.json b/locales/he/common.json
index 41d1d1641..b9e6ddcf3 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -157,6 +157,7 @@
"shared": "משותף",
"shared_account_hidden": "הוסתר מסרגל הצד. אפשר להציג שוב בהגדרות › פריסה.",
"notifications_muted": "ההתראות מושתקות",
+ "folder_options": "אפשרויות עבור {name}",
"scheduled": "מתוכננים",
"unified_all_unread": "הכל שלא נקראו",
"unified_all_starred": "הכל מסומנים",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index f8ac45648..ab5f85f41 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -155,6 +155,7 @@
"shared": "Megosztott",
"shared_account_hidden": "Elrejtve az oldalsávról. A Beállítások › Elrendezés alatt jelenítheted meg újra.",
"notifications_muted": "Értesítések némítva",
+ "folder_options": "{name} beállításai",
"mail": "Levelek",
"nav_label": "Navigáció",
"add_app": "Alkalmazások",
diff --git a/locales/it/common.json b/locales/it/common.json
index 0bfb10d23..288447373 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -158,6 +158,7 @@
"shared": "Condiviso",
"shared_account_hidden": "Nascosto dalla barra laterale. Puoi mostrarlo di nuovo in Impostazioni › Layout.",
"notifications_muted": "Notifiche disattivate",
+ "folder_options": "Opzioni per {name}",
"scheduled": "Programmate",
"unified_all_unread": "Tutti i non letti",
"unified_all_starred": "Tutti gli speciali",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 36045fa0b..f8fa940bd 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -158,6 +158,7 @@
"shared": "共有",
"shared_account_hidden": "サイドバーから非表示にしました。設定 › レイアウトで再表示できます。",
"notifications_muted": "通知ミュート中",
+ "folder_options": "{name} のオプション",
"scheduled": "予約済み",
"unified_all_unread": "すべての未読",
"unified_all_starred": "すべてのスター付き",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index b22ef39c2..0a8b7d200 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -158,6 +158,7 @@
"shared": "공유됨",
"shared_account_hidden": "사이드바에서 숨겼습니다. 설정 › 레이아웃에서 다시 표시할 수 있습니다.",
"notifications_muted": "알림 꺼짐",
+ "folder_options": "{name} 옵션",
"scheduled": "예약됨",
"unified_all_unread": "모든 읽지 않음",
"unified_all_starred": "모든 별표",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index b76a99c78..f510964d3 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -158,6 +158,7 @@
"shared": "Koplietots",
"shared_account_hidden": "Paslēpts no sānjoslas. Atkal parādiet sadaļā Iestatījumi › Izkārtojums.",
"notifications_muted": "Paziņojumi izslēgti",
+ "folder_options": "{name} opcijas",
"scheduled": "Ieplānots",
"unified_all_unread": "Visi nelasītie",
"unified_all_starred": "Visi ar zvaigzni",
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 537618ccc..9c63c7f78 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -155,6 +155,7 @@
"shared": "Хуваалцсан",
"shared_account_hidden": "Хажуугийн самбараас нуусан. Тохиргоо › Байршил хэсгээс дахин харуулна уу.",
"notifications_muted": "Мэдэгдэл хаалттай",
+ "folder_options": "{name}-ийн сонголтууд",
"mail": "Мэйл",
"nav_label": "Навигаци",
"add_app": "Програмууд",
diff --git a/locales/nb/common.json b/locales/nb/common.json
index c149565ea..1a053a3c6 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -155,6 +155,7 @@
"shared": "Delt",
"shared_account_hidden": "Skjult fra sidepanelet. Vis den igjen under Innstillinger › Oppsett.",
"notifications_muted": "Varsler dempet",
+ "folder_options": "Alternativer for {name}",
"mail": "E-post",
"nav_label": "Navigasjon",
"add_app": "Apper",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index de7a7aeb5..df05d92fb 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -158,6 +158,7 @@
"shared": "Gedeeld",
"shared_account_hidden": "Verborgen in de zijbalk. Zet het weer aan via Instellingen › Indeling.",
"notifications_muted": "Meldingen gedempt",
+ "folder_options": "Opties voor {name}",
"scheduled": "Gepland",
"unified_all_unread": "Alle ongelezen",
"unified_all_starred": "Alle met ster",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index 9e2dbd0dd..013e5420d 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -158,6 +158,7 @@
"shared": "Udostępnione",
"shared_account_hidden": "Ukryto na pasku bocznym. Przywrócisz je w Ustawienia › Układ.",
"notifications_muted": "Powiadomienia wyciszone",
+ "folder_options": "Opcje folderu {name}",
"scheduled": "Zaplanowane",
"unified_all_unread": "Wszystkie nieprzeczytane",
"unified_all_starred": "Wszystkie oznaczone gwiazdką",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index 4d6360b92..6b615562e 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -158,6 +158,7 @@
"shared": "Compartilhado",
"shared_account_hidden": "Oculto da barra lateral. Mostre de novo em Configurações › Layout.",
"notifications_muted": "Notificações silenciadas",
+ "folder_options": "Opções de {name}",
"scheduled": "Agendados",
"unified_all_unread": "Tudo não lido",
"unified_all_starred": "Tudo com estrela",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 6a532e496..0bffdaba8 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -155,6 +155,7 @@
"shared": "Partajat",
"shared_account_hidden": "Ascuns din bara laterală. Îl poți afișa din nou în Setări › Aspect.",
"notifications_muted": "Notificări dezactivate",
+ "folder_options": "Opțiuni pentru {name}",
"mail": "E-mail",
"nav_label": "Navigare",
"add_app": "Aplicații",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index fd5850c4d..cfff06480 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -158,6 +158,7 @@
"shared": "Общие",
"shared_account_hidden": "Скрыто с боковой панели. Вернуть можно в Настройки › Макет.",
"notifications_muted": "Уведомления отключены",
+ "folder_options": "Параметры: {name}",
"scheduled": "Запланировано",
"unified_all_unread": "Все непрочитанные",
"unified_all_starred": "Все помеченные",
diff --git a/locales/sk/common.json b/locales/sk/common.json
index dcbede62e..8a62d1d05 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -155,6 +155,7 @@
"shared": "Zdieľané",
"shared_account_hidden": "Skryté z bočného panela. Znova zobrazíte v Nastavenia › Rozloženie.",
"notifications_muted": "Upozornenia stlmené",
+ "folder_options": "Možnosti pre {name}",
"mail": "Pošta",
"nav_label": "Navigácia",
"add_app": "Aplikácie",
diff --git a/locales/th/common.json b/locales/th/common.json
index 5b5ef67c4..8cde33cfa 100644
--- a/locales/th/common.json
+++ b/locales/th/common.json
@@ -256,6 +256,7 @@
"shared": "แชร์แล้ว",
"shared_account_hidden": "ซ่อนจากแถบด้านข้างแล้ว เปิดใช้งานอีกครั้งได้ที่ การตั้งค่า › รูปลักษณ์",
"notifications_muted": "ปิดเสียงการแจ้งเตือนแล้ว",
+ "folder_options": "ตัวเลือกสำหรับ {name}",
"mail": "จดหมาย",
"nav_label": "การนำทาง",
"add_app": "แอปพลิเคชัน",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index bfeacc476..21c7a5782 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -155,6 +155,7 @@
"shared": "Paylaşılan",
"shared_account_hidden": "Kenar çubuğundan gizlendi. Ayarlar › Düzen bölümünden yeniden gösterebilirsiniz.",
"notifications_muted": "Bildirimler sessizde",
+ "folder_options": "{name} seçenekleri",
"mail": "Posta",
"nav_label": "Gezinme",
"add_app": "Uygulamalar",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index c28e3156f..a88312d50 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -158,6 +158,7 @@
"shared": "Спільні",
"shared_account_hidden": "Приховано з бічної панелі. Повернути можна в Налаштування › Макет.",
"notifications_muted": "Сповіщення вимкнено",
+ "folder_options": "Параметри: {name}",
"scheduled": "Заплановано",
"unified_all_unread": "Усі непрочитані",
"unified_all_starred": "Усі позначені",
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index e9b03cd3c..f6f1a53e4 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -155,6 +155,7 @@
"shared": "共用",
"shared_account_hidden": "已從側邊欄隱藏。可在 設定 › 版面配置 中重新顯示。",
"notifications_muted": "通知已靜音",
+ "folder_options": "{name} 的選項",
"mail": "郵件",
"nav_label": "導覽",
"add_app": "應用程式",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index 5285e846c..d10b82c62 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -158,6 +158,7 @@
"shared": "共享",
"shared_account_hidden": "已从侧边栏隐藏。可在 设置 › 布局 中重新显示。",
"notifications_muted": "通知已静音",
+ "folder_options": "{name} 的选项",
"scheduled": "已计划",
"unified_all_unread": "全部未读",
"unified_all_starred": "全部加星标",