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": "全部加星标",