mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: open a folder's actions on the phone with a long-press or its options button
This commit is contained in:
@@ -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(<Sidebar mailboxes={mailboxes} selectedMailbox="inbox" onMailboxSelect={onMailboxSelect} />);
|
||||
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(<Sidebar mailboxes={mailboxes} selectedMailbox="inbox" />);
|
||||
fireEvent.click(row('travel').querySelector('[data-testid="folder-row-menu"]')!);
|
||||
expect(screen.getByRole('menu')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const hasMenu = !!onContextMenu && !isVirtual;
|
||||
const longPress = useLongPress((point) => {
|
||||
if (rowRef.current && onContextMenu) onContextMenu(menuEventAt(rowRef.current, point));
|
||||
}, hasMenu);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={rowRef}
|
||||
{...(dropHandlers || {})}
|
||||
onContextMenu={onContextMenu}
|
||||
onTouchStart={hasMenu ? longPress.onTouchStart : undefined}
|
||||
onTouchMove={hasMenu ? longPress.onTouchMove : undefined}
|
||||
onTouchEnd={hasMenu ? longPress.onTouchEnd : undefined}
|
||||
onTouchCancel={hasMenu ? longPress.onTouchCancel : undefined}
|
||||
data-testid="folder-row"
|
||||
data-mv-nav-row=""
|
||||
data-folder-role={testRole ?? undefined}
|
||||
@@ -445,6 +477,19 @@ function SidebarRow({
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{hasMenu && !isCollapsed && (
|
||||
<IconButton
|
||||
label={t('folder_options', { name: label })}
|
||||
data-testid="folder-row-menu"
|
||||
className="hidden pointer-coarse:inline-flex ms-1 flex-shrink-0 text-muted-foreground"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onContextMenu(menuEventAt(e.currentTarget));
|
||||
}}
|
||||
>
|
||||
<MoreVertical />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
"shared": "مشترك",
|
||||
"shared_account_hidden": "تم الإخفاء من الشريط الجانبي. أعد إظهاره من الإعدادات › التخطيط.",
|
||||
"notifications_muted": "الإشعارات مكتومة",
|
||||
"folder_options": "خيارات {name}",
|
||||
"mail": "البريد",
|
||||
"nav_label": "التنقل",
|
||||
"add_app": "التطبيقات",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
"shared": "اشتراکی",
|
||||
"shared_account_hidden": "از نوار کناری پنهان شد. از تنظیمات › چیدمان دوباره نمایش دهید.",
|
||||
"notifications_muted": "اعلانها بیصدا",
|
||||
"folder_options": "گزینههای {name}",
|
||||
"mail": "ایمیل",
|
||||
"nav_label": "ناوبری",
|
||||
"add_app": "برنامهها",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"shared": "משותף",
|
||||
"shared_account_hidden": "הוסתר מסרגל הצד. אפשר להציג שוב בהגדרות › פריסה.",
|
||||
"notifications_muted": "ההתראות מושתקות",
|
||||
"folder_options": "אפשרויות עבור {name}",
|
||||
"scheduled": "מתוכננים",
|
||||
"unified_all_unread": "הכל שלא נקראו",
|
||||
"unified_all_starred": "הכל מסומנים",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"shared": "共有",
|
||||
"shared_account_hidden": "サイドバーから非表示にしました。設定 › レイアウトで再表示できます。",
|
||||
"notifications_muted": "通知ミュート中",
|
||||
"folder_options": "{name} のオプション",
|
||||
"scheduled": "予約済み",
|
||||
"unified_all_unread": "すべての未読",
|
||||
"unified_all_starred": "すべてのスター付き",
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"shared": "공유됨",
|
||||
"shared_account_hidden": "사이드바에서 숨겼습니다. 설정 › 레이아웃에서 다시 표시할 수 있습니다.",
|
||||
"notifications_muted": "알림 꺼짐",
|
||||
"folder_options": "{name} 옵션",
|
||||
"scheduled": "예약됨",
|
||||
"unified_all_unread": "모든 읽지 않음",
|
||||
"unified_all_starred": "모든 별표",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
"shared": "Хуваалцсан",
|
||||
"shared_account_hidden": "Хажуугийн самбараас нуусан. Тохиргоо › Байршил хэсгээс дахин харуулна уу.",
|
||||
"notifications_muted": "Мэдэгдэл хаалттай",
|
||||
"folder_options": "{name}-ийн сонголтууд",
|
||||
"mail": "Мэйл",
|
||||
"nav_label": "Навигаци",
|
||||
"add_app": "Програмууд",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ą",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"shared": "Общие",
|
||||
"shared_account_hidden": "Скрыто с боковой панели. Вернуть можно в Настройки › Макет.",
|
||||
"notifications_muted": "Уведомления отключены",
|
||||
"folder_options": "Параметры: {name}",
|
||||
"scheduled": "Запланировано",
|
||||
"unified_all_unread": "Все непрочитанные",
|
||||
"unified_all_starred": "Все помеченные",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -256,6 +256,7 @@
|
||||
"shared": "แชร์แล้ว",
|
||||
"shared_account_hidden": "ซ่อนจากแถบด้านข้างแล้ว เปิดใช้งานอีกครั้งได้ที่ การตั้งค่า › รูปลักษณ์",
|
||||
"notifications_muted": "ปิดเสียงการแจ้งเตือนแล้ว",
|
||||
"folder_options": "ตัวเลือกสำหรับ {name}",
|
||||
"mail": "จดหมาย",
|
||||
"nav_label": "การนำทาง",
|
||||
"add_app": "แอปพลิเคชัน",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"shared": "Спільні",
|
||||
"shared_account_hidden": "Приховано з бічної панелі. Повернути можна в Налаштування › Макет.",
|
||||
"notifications_muted": "Сповіщення вимкнено",
|
||||
"folder_options": "Параметри: {name}",
|
||||
"scheduled": "Заплановано",
|
||||
"unified_all_unread": "Усі непрочитані",
|
||||
"unified_all_starred": "Усі позначені",
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
"shared": "共用",
|
||||
"shared_account_hidden": "已從側邊欄隱藏。可在 設定 › 版面配置 中重新顯示。",
|
||||
"notifications_muted": "通知已靜音",
|
||||
"folder_options": "{name} 的選項",
|
||||
"mail": "郵件",
|
||||
"nav_label": "導覽",
|
||||
"add_app": "應用程式",
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"shared": "共享",
|
||||
"shared_account_hidden": "已从侧边栏隐藏。可在 设置 › 布局 中重新显示。",
|
||||
"notifications_muted": "通知已静音",
|
||||
"folder_options": "{name} 的选项",
|
||||
"scheduled": "已计划",
|
||||
"unified_all_unread": "全部未读",
|
||||
"unified_all_starred": "全部加星标",
|
||||
|
||||
Reference in New Issue
Block a user