feat: implement hover functionality for calendar events and enhance email deletion confirmation
This commit is contained in:
@@ -211,16 +211,35 @@ export default function CalendarPage() {
|
||||
setShowEventModal(true);
|
||||
}, []);
|
||||
|
||||
const handleSelectEvent = useCallback((event: CalendarEvent, anchorRect: DOMRect) => {
|
||||
setDetailEvent(event);
|
||||
setDetailAnchorRect(anchorRect);
|
||||
}, []);
|
||||
const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const closeDetail = useCallback(() => {
|
||||
if (hoverTimerRef.current) { clearTimeout(hoverTimerRef.current); hoverTimerRef.current = null; }
|
||||
setDetailEvent(null);
|
||||
setDetailAnchorRect(null);
|
||||
}, []);
|
||||
|
||||
const handleSelectEvent = useCallback((event: CalendarEvent, _anchorRect: DOMRect) => {
|
||||
// Click opens the sidebar for viewing/editing
|
||||
closeDetail();
|
||||
openEditModal(event);
|
||||
}, [closeDetail, openEditModal]);
|
||||
|
||||
const handleHoverEvent = useCallback((event: CalendarEvent, anchorRect: DOMRect) => {
|
||||
if (hoverTimerRef.current) { clearTimeout(hoverTimerRef.current); hoverTimerRef.current = null; }
|
||||
// Don't show hover popover if the sidebar is already open for this event
|
||||
if (showEventModal && editEvent?.id === event.id) return;
|
||||
setDetailEvent(event);
|
||||
setDetailAnchorRect(anchorRect);
|
||||
}, [showEventModal, editEvent]);
|
||||
|
||||
const handleHoverLeave = useCallback(() => {
|
||||
hoverTimerRef.current = setTimeout(() => {
|
||||
setDetailEvent(null);
|
||||
setDetailAnchorRect(null);
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
const handleEditFromDetail = useCallback(() => {
|
||||
if (detailEvent) {
|
||||
const ev = detailEvent;
|
||||
@@ -581,6 +600,8 @@ export default function CalendarPage() {
|
||||
calendars={calendars}
|
||||
onSelectDate={handleSelectDate}
|
||||
onSelectEvent={handleSelectEvent}
|
||||
onHoverEvent={handleHoverEvent}
|
||||
onHoverLeave={handleHoverLeave}
|
||||
firstDayOfWeek={firstDayOfWeek}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
@@ -593,6 +614,8 @@ export default function CalendarPage() {
|
||||
calendars={calendars}
|
||||
onSelectDate={handleSelectDate}
|
||||
onSelectEvent={handleSelectEvent}
|
||||
onHoverEvent={handleHoverEvent}
|
||||
onHoverLeave={handleHoverLeave}
|
||||
onCreateAtTime={openCreateModal}
|
||||
firstDayOfWeek={firstDayOfWeek}
|
||||
timeFormat={timeFormat}
|
||||
@@ -606,6 +629,8 @@ export default function CalendarPage() {
|
||||
events={visibleEvents}
|
||||
calendars={calendars}
|
||||
onSelectEvent={handleSelectEvent}
|
||||
onHoverEvent={handleHoverEvent}
|
||||
onHoverLeave={handleHoverLeave}
|
||||
onCreateAtTime={openCreateModal}
|
||||
timeFormat={timeFormat}
|
||||
isMobile={isMobile}
|
||||
@@ -618,6 +643,8 @@ export default function CalendarPage() {
|
||||
events={visibleEvents}
|
||||
calendars={calendars}
|
||||
onSelectEvent={handleSelectEvent}
|
||||
onHoverEvent={handleHoverEvent}
|
||||
onHoverLeave={handleHoverLeave}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
);
|
||||
@@ -739,6 +766,8 @@ export default function CalendarPage() {
|
||||
onClose={closeDetail}
|
||||
onSaveNote={handleSaveNoteFromDetail}
|
||||
onRsvp={handleRsvpFromDetail}
|
||||
onMouseEnter={() => { if (hoverTimerRef.current) { clearTimeout(hoverTimerRef.current); hoverTimerRef.current = null; } }}
|
||||
onMouseLeave={handleHoverLeave}
|
||||
currentUserEmails={currentUserEmails}
|
||||
timeFormat={timeFormat}
|
||||
isMobile={isMobile}
|
||||
|
||||
+29
-4
@@ -454,10 +454,35 @@ export default function Home() {
|
||||
const handleDelete = async () => {
|
||||
if (!client || !selectedEmail) return;
|
||||
|
||||
try {
|
||||
await deleteEmail(client, selectedEmail.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to delete email:", error);
|
||||
// Check if we're currently in the trash folder
|
||||
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
|
||||
const isInTrash = currentMailbox?.role === 'trash';
|
||||
|
||||
if (isInTrash) {
|
||||
// In trash: confirm before permanently deleting
|
||||
const confirmed = await confirmDialog({
|
||||
title: t('email_list.permanent_delete_confirm_title'),
|
||||
message: t('email_list.permanent_delete_confirm_message'),
|
||||
confirmText: t('email_list.permanent_delete'),
|
||||
variant: "destructive",
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
await deleteEmail(client, selectedEmail.id, true);
|
||||
} catch (error) {
|
||||
console.error("Failed to permanently delete email:", error);
|
||||
}
|
||||
} else {
|
||||
// Not in trash: always move to trash
|
||||
const trashMailbox = mailboxes.find(m => m.role === 'trash' && !m.isShared);
|
||||
if (trashMailbox) {
|
||||
try {
|
||||
await moveToMailbox(client, selectedEmail.id, trashMailbox.id);
|
||||
} catch (error) {
|
||||
console.error("Failed to move email to trash:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ interface CalendarAgendaViewProps {
|
||||
events: CalendarEvent[];
|
||||
calendars: Calendar[];
|
||||
onSelectEvent: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverEvent?: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverLeave?: () => void;
|
||||
timeFormat?: "12h" | "24h";
|
||||
}
|
||||
|
||||
@@ -28,6 +30,8 @@ export function CalendarAgendaView({
|
||||
events,
|
||||
calendars,
|
||||
onSelectEvent,
|
||||
onHoverEvent,
|
||||
onHoverLeave,
|
||||
timeFormat = "24h",
|
||||
}: CalendarAgendaViewProps) {
|
||||
const t = useTranslations("calendar");
|
||||
@@ -140,6 +144,8 @@ export function CalendarAgendaView({
|
||||
<button
|
||||
key={ev.id}
|
||||
onClick={(e) => onSelectEvent(ev, e.currentTarget.getBoundingClientRect())}
|
||||
onMouseEnter={(e) => onHoverEvent?.(ev, e.currentTarget.getBoundingClientRect())}
|
||||
onMouseLeave={() => onHoverLeave?.()}
|
||||
className="w-full flex items-start gap-3 px-4 py-3 hover:bg-muted/50 transition-colors text-left"
|
||||
>
|
||||
<div className="flex flex-col items-center pt-0.5 min-w-[60px]">
|
||||
|
||||
@@ -15,6 +15,8 @@ interface CalendarDayViewProps {
|
||||
events: CalendarEvent[];
|
||||
calendars: Calendar[];
|
||||
onSelectEvent: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverEvent?: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverLeave?: () => void;
|
||||
onCreateAtTime: (date: Date, endDate?: Date) => void;
|
||||
timeFormat?: "12h" | "24h";
|
||||
isMobile?: boolean;
|
||||
@@ -28,6 +30,8 @@ export function CalendarDayView({
|
||||
events,
|
||||
calendars,
|
||||
onSelectEvent,
|
||||
onHoverEvent,
|
||||
onHoverLeave,
|
||||
onCreateAtTime,
|
||||
timeFormat = "24h",
|
||||
isMobile,
|
||||
@@ -134,6 +138,8 @@ export function CalendarDayView({
|
||||
calendar={calendarMap.get(calId)}
|
||||
variant="chip"
|
||||
onClick={(rect) => onSelectEvent(ev, rect)}
|
||||
onMouseEnter={(rect) => onHoverEvent?.(ev, rect)}
|
||||
onMouseLeave={onHoverLeave}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -205,6 +211,8 @@ export function CalendarDayView({
|
||||
calendar={calendarMap.get(calId)}
|
||||
variant="block"
|
||||
onClick={(rect) => onSelectEvent(ev, rect)}
|
||||
onMouseEnter={(rect) => onHoverEvent?.(ev, rect)}
|
||||
onMouseLeave={onHoverLeave}
|
||||
draggable
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState, useCallback, type DragEvent } from "react";
|
||||
import { useMemo, useState, useCallback, useRef, useEffect, type DragEvent } from "react";
|
||||
import { useTranslations, useFormatter } from "next-intl";
|
||||
import {
|
||||
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
||||
@@ -20,6 +20,8 @@ interface CalendarMonthViewProps {
|
||||
calendars: Calendar[];
|
||||
onSelectDate: (date: Date) => void;
|
||||
onSelectEvent: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverEvent?: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverLeave?: () => void;
|
||||
firstDayOfWeek?: number;
|
||||
isMobile?: boolean;
|
||||
}
|
||||
@@ -30,6 +32,8 @@ export function CalendarMonthView({
|
||||
calendars,
|
||||
onSelectDate,
|
||||
onSelectEvent,
|
||||
onHoverEvent,
|
||||
onHoverLeave,
|
||||
firstDayOfWeek = 1,
|
||||
isMobile,
|
||||
}: CalendarMonthViewProps) {
|
||||
@@ -88,6 +92,32 @@ export function CalendarMonthView({
|
||||
}, [days]);
|
||||
|
||||
const [dropDayKey, setDropDayKey] = useState<string | null>(null);
|
||||
const [overflowDay, setOverflowDay] = useState<{ key: string; events: CalendarEvent[]; anchorRect: DOMRect; dayLabel: string } | null>(null);
|
||||
const overflowRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!overflowDay) return;
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (overflowRef.current && !overflowRef.current.contains(e.target as Node)) {
|
||||
setOverflowDay(null);
|
||||
}
|
||||
};
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setOverflowDay(null);
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, [overflowDay]);
|
||||
|
||||
const handleMoreClick = useCallback((e: React.MouseEvent, dayKey: string, dayEvents: CalendarEvent[], dayLabel: string) => {
|
||||
e.stopPropagation();
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
setOverflowDay({ key: dayKey, events: dayEvents, anchorRect: rect, dayLabel });
|
||||
}, []);
|
||||
|
||||
const handleCellDragOver = useCallback((e: DragEvent<HTMLDivElement>, dayKey: string) => {
|
||||
if (!e.dataTransfer.types.includes("application/x-calendar-event")) return;
|
||||
@@ -214,14 +244,20 @@ export function CalendarMonthView({
|
||||
calendar={calendarMap.get(calId)}
|
||||
variant="chip"
|
||||
onClick={(rect) => onSelectEvent(ev, rect)}
|
||||
onMouseEnter={(rect) => onHoverEvent?.(ev, rect)}
|
||||
onMouseLeave={onHoverLeave}
|
||||
draggable
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{dayEvents.length > maxVisible && (
|
||||
<div className="text-[10px] text-muted-foreground px-1">
|
||||
<button
|
||||
type="button"
|
||||
className="text-[10px] text-muted-foreground px-1 hover:text-foreground hover:underline cursor-pointer text-left w-full"
|
||||
onClick={(e) => handleMoreClick(e, key, dayEvents, fullDateLabel)}
|
||||
>
|
||||
{t("events.more", { count: dayEvents.length - maxVisible })}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -231,6 +267,47 @@ export function CalendarMonthView({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{overflowDay && (() => {
|
||||
const viewportW = typeof window !== "undefined" ? window.innerWidth : 1024;
|
||||
const viewportH = typeof window !== "undefined" ? window.innerHeight : 768;
|
||||
const popoverW = 260;
|
||||
const popoverMaxH = 320;
|
||||
let left = overflowDay.anchorRect.left;
|
||||
let top = overflowDay.anchorRect.bottom + 4;
|
||||
|
||||
if (left + popoverW > viewportW - 8) left = viewportW - popoverW - 8;
|
||||
if (left < 8) left = 8;
|
||||
if (top + popoverMaxH > viewportH - 8) top = overflowDay.anchorRect.top - popoverMaxH - 4;
|
||||
if (top < 8) top = 8;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={overflowRef}
|
||||
className="fixed z-50 bg-background border border-border rounded-lg shadow-lg p-3 space-y-1 overflow-y-auto"
|
||||
style={{ left, top, width: popoverW, maxHeight: popoverMaxH }}
|
||||
role="dialog"
|
||||
aria-label={overflowDay.dayLabel}
|
||||
>
|
||||
<div className="text-xs font-semibold text-foreground mb-2">{overflowDay.dayLabel}</div>
|
||||
{overflowDay.events.map((ev) => {
|
||||
const calId = Object.keys(ev.calendarIds)[0];
|
||||
return (
|
||||
<EventCard
|
||||
key={ev.id}
|
||||
event={ev}
|
||||
calendar={calendarMap.get(calId)}
|
||||
variant="chip"
|
||||
onClick={(rect) => { setOverflowDay(null); onSelectEvent(ev, rect); }}
|
||||
onMouseEnter={(rect) => onHoverEvent?.(ev, rect)}
|
||||
onMouseLeave={onHoverLeave}
|
||||
draggable
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ interface CalendarWeekViewProps {
|
||||
calendars: Calendar[];
|
||||
onSelectDate: (date: Date) => void;
|
||||
onSelectEvent: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverEvent?: (event: CalendarEvent, anchorRect: DOMRect) => void;
|
||||
onHoverLeave?: () => void;
|
||||
onCreateAtTime: (date: Date, endDate?: Date) => void;
|
||||
firstDayOfWeek?: number;
|
||||
timeFormat?: "12h" | "24h";
|
||||
@@ -33,6 +35,8 @@ export function CalendarWeekView({
|
||||
calendars,
|
||||
onSelectDate,
|
||||
onSelectEvent,
|
||||
onHoverEvent,
|
||||
onHoverLeave,
|
||||
onCreateAtTime,
|
||||
firstDayOfWeek = 1,
|
||||
timeFormat = "24h",
|
||||
@@ -162,6 +166,8 @@ export function CalendarWeekView({
|
||||
calendar={calendarMap.get(calId)}
|
||||
variant="chip"
|
||||
onClick={(rect) => onSelectEvent(ev, rect)}
|
||||
onMouseEnter={(rect) => onHoverEvent?.(ev, rect)}
|
||||
onMouseLeave={onHoverLeave}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -280,6 +286,8 @@ export function CalendarWeekView({
|
||||
calendar={calendarMap.get(calId)}
|
||||
variant="block"
|
||||
onClick={(rect) => onSelectEvent(ev, rect)}
|
||||
onMouseEnter={(rect) => onHoverEvent?.(ev, rect)}
|
||||
onMouseLeave={onHoverLeave}
|
||||
draggable
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -13,6 +13,8 @@ interface EventCardProps {
|
||||
calendar?: Calendar;
|
||||
variant: "chip" | "block";
|
||||
onClick?: (anchorRect: DOMRect) => void;
|
||||
onMouseEnter?: (anchorRect: DOMRect) => void;
|
||||
onMouseLeave?: () => void;
|
||||
isSelected?: boolean;
|
||||
draggable?: boolean;
|
||||
}
|
||||
@@ -56,7 +58,7 @@ function createEventDragPreview(title: string, timeRange: string, color: string)
|
||||
return el;
|
||||
}
|
||||
|
||||
export function EventCard({ event, calendar, variant, onClick, isSelected, draggable: isDraggable }: EventCardProps) {
|
||||
export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onMouseLeave, isSelected, draggable: isDraggable }: EventCardProps) {
|
||||
const t = useTranslations("calendar");
|
||||
const [isBeingDragged, setIsBeingDragged] = useState(false);
|
||||
const color = getEventColor(event, calendar);
|
||||
@@ -101,6 +103,8 @@ export function EventCard({ event, calendar, variant, onClick, isSelected, dragg
|
||||
return (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onClick?.(e.currentTarget.getBoundingClientRect()); }}
|
||||
onMouseEnter={(e) => onMouseEnter?.(e.currentTarget.getBoundingClientRect())}
|
||||
onMouseLeave={() => onMouseLeave?.()}
|
||||
aria-label={ariaLabel}
|
||||
{...dragProps}
|
||||
className={cn(
|
||||
@@ -124,6 +128,8 @@ export function EventCard({ event, calendar, variant, onClick, isSelected, dragg
|
||||
return (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onClick?.(e.currentTarget.getBoundingClientRect()); }}
|
||||
onMouseEnter={(e) => onMouseEnter?.(e.currentTarget.getBoundingClientRect())}
|
||||
onMouseLeave={() => onMouseLeave?.()}
|
||||
aria-label={ariaLabel}
|
||||
{...dragProps}
|
||||
data-calendar-event
|
||||
|
||||
@@ -29,6 +29,8 @@ interface EventDetailPopoverProps {
|
||||
onClose: () => void;
|
||||
onSaveNote: (note: string) => void;
|
||||
onRsvp?: (status: CalendarParticipant["participationStatus"]) => void;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
currentUserEmails?: string[];
|
||||
timeFormat?: "12h" | "24h";
|
||||
isMobile?: boolean;
|
||||
@@ -119,6 +121,8 @@ export function EventDetailPopover({
|
||||
onClose,
|
||||
onSaveNote,
|
||||
onRsvp,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
currentUserEmails = [],
|
||||
timeFormat = "24h",
|
||||
isMobile,
|
||||
@@ -203,17 +207,14 @@ export function EventDetailPopover({
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
const handleScroll = () => onClose();
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
document.addEventListener("scroll", handleScroll, true);
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
document.removeEventListener("scroll", handleScroll, true);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
@@ -252,6 +253,8 @@ export function EventDetailPopover({
|
||||
ref={popoverRef}
|
||||
role="dialog"
|
||||
aria-label={event.title || t("events.no_title")}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={cn(
|
||||
"fixed z-[60] bg-background border border-border shadow-xl overflow-hidden transition-[opacity,transform] duration-150 ease-out",
|
||||
isMobile
|
||||
|
||||
@@ -146,10 +146,19 @@ export function EmailList({
|
||||
const handleBatchDelete = async () => {
|
||||
if (!client || isProcessing) return;
|
||||
|
||||
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
|
||||
const isInTrash = currentMailbox?.role === 'trash';
|
||||
|
||||
const confirmed = await confirmDialog({
|
||||
title: t('batch_actions.delete_confirm_title'),
|
||||
message: t('batch_actions.delete_confirm_message', { count: selectedEmailIds.size }),
|
||||
confirmText: t('batch_actions.delete'),
|
||||
title: isInTrash
|
||||
? t('permanent_delete_confirm_title')
|
||||
: t('batch_actions.delete_confirm_title'),
|
||||
message: isInTrash
|
||||
? t('permanent_delete_confirm_batch_message', { count: selectedEmailIds.size })
|
||||
: t('batch_actions.delete_confirm_message', { count: selectedEmailIds.size }),
|
||||
confirmText: isInTrash
|
||||
? t('permanent_delete')
|
||||
: t('batch_actions.delete'),
|
||||
variant: "destructive",
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Als gelesen markieren",
|
||||
"mark_unread": "Als ungelesen markieren",
|
||||
"delete": "Löschen",
|
||||
"delete_confirm_title": "E-Mails löschen",
|
||||
"delete_confirm_message": "Möchten Sie {count, plural, one {1 E-Mail} other {# E-Mails}} wirklich löschen?",
|
||||
"clear_selection": "Auswahl aufheben"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Endgültig löschen",
|
||||
"permanent_delete_confirm_title": "Endgültig löschen",
|
||||
"permanent_delete_confirm_message": "Diese E-Mail wird endgültig gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"permanent_delete_confirm_batch_message": "Diese {count, plural, one {1 E-Mail} other {# E-Mails}} werden endgültig gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "Keine E-Mail ausgewählt",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Mark as read",
|
||||
"mark_unread": "Mark as unread",
|
||||
"delete": "Delete",
|
||||
"delete_confirm_title": "Delete emails",
|
||||
"delete_confirm_message": "Are you sure you want to delete {count, plural, one {1 email} other {# emails}}?",
|
||||
"clear_selection": "Clear selection"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Delete permanently",
|
||||
"permanent_delete_confirm_title": "Permanently delete",
|
||||
"permanent_delete_confirm_message": "This email will be permanently deleted. This action cannot be undone.",
|
||||
"permanent_delete_confirm_batch_message": "These {count, plural, one {1 email} other {# emails}} will be permanently deleted. This action cannot be undone."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "No email selected",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Marcar como leído",
|
||||
"mark_unread": "Marcar como no leído",
|
||||
"delete": "Eliminar",
|
||||
"delete_confirm_title": "Eliminar correos",
|
||||
"delete_confirm_message": "¿Estás seguro de que quieres eliminar {count, plural, one {1 correo} other {# correos}}?",
|
||||
"clear_selection": "Limpiar selección"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Eliminar permanentemente",
|
||||
"permanent_delete_confirm_title": "Eliminar permanentemente",
|
||||
"permanent_delete_confirm_message": "Este correo se eliminará permanentemente. Esta acción no se puede deshacer.",
|
||||
"permanent_delete_confirm_batch_message": "Estos {count, plural, one {1 correo} other {# correos}} se eliminarán permanentemente. Esta acción no se puede deshacer."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "Ningún correo seleccionado",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Marquer comme lu",
|
||||
"mark_unread": "Marquer comme non lu",
|
||||
"delete": "Supprimer",
|
||||
"delete_confirm_title": "Supprimer les e-mails",
|
||||
"delete_confirm_message": "Voulez-vous vraiment supprimer {count, plural, one {1 e-mail} other {# e-mails}} ?",
|
||||
"clear_selection": "Effacer la sélection"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Supprimer définitivement",
|
||||
"permanent_delete_confirm_title": "Supprimer définitivement",
|
||||
"permanent_delete_confirm_message": "Cet e-mail sera définitivement supprimé. Cette action est irréversible.",
|
||||
"permanent_delete_confirm_batch_message": "Ces {count, plural, one {1 e-mail} other {# e-mails}} seront définitivement supprimés. Cette action est irréversible."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "Aucun email sélectionné",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Segna come letto",
|
||||
"mark_unread": "Segna come non letto",
|
||||
"delete": "Elimina",
|
||||
"delete_confirm_title": "Elimina e-mail",
|
||||
"delete_confirm_message": "Sei sicuro di voler eliminare {count, plural, one {1 e-mail} other {# e-mail}}?",
|
||||
"clear_selection": "Cancella selezione"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Elimina definitivamente",
|
||||
"permanent_delete_confirm_title": "Elimina definitivamente",
|
||||
"permanent_delete_confirm_message": "Questa e-mail verrà eliminata definitivamente. Questa azione non può essere annullata.",
|
||||
"permanent_delete_confirm_batch_message": "Queste {count, plural, one {1 e-mail} other {# e-mail}} verranno eliminate definitivamente. Questa azione non può essere annullata."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "Nessun messaggio selezionato",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "既読にする",
|
||||
"mark_unread": "未読にする",
|
||||
"delete": "削除",
|
||||
"delete_confirm_title": "メールを削除",
|
||||
"delete_confirm_message": "{count, plural, one {1件} other {#件}}のメールを削除しますか?",
|
||||
"clear_selection": "選択を解除"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "完全に削除",
|
||||
"permanent_delete_confirm_title": "完全に削除",
|
||||
"permanent_delete_confirm_message": "このメールは完全に削除されます。この操作は取り消せません。",
|
||||
"permanent_delete_confirm_batch_message": "{count, plural, one {1件} other {#件}}のメールが完全に削除されます。この操作は取り消せません。"
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "メールが選択されていません",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Markeren als gelezen",
|
||||
"mark_unread": "Markeren als ongelezen",
|
||||
"delete": "Verwijderen",
|
||||
"delete_confirm_title": "E-mails verwijderen",
|
||||
"delete_confirm_message": "Weet u zeker dat u {count, plural, one {1 e-mail} other {# e-mails}} wilt verwijderen?",
|
||||
"clear_selection": "Selectie wissen"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Definitief verwijderen",
|
||||
"permanent_delete_confirm_title": "Definitief verwijderen",
|
||||
"permanent_delete_confirm_message": "Deze e-mail wordt definitief verwijderd. Deze actie kan niet ongedaan worden gemaakt.",
|
||||
"permanent_delete_confirm_batch_message": "Deze {count, plural, one {1 e-mail} other {# e-mails}} worden definitief verwijderd. Deze actie kan niet ongedaan worden gemaakt."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "Geen e-mail geselecteerd",
|
||||
|
||||
@@ -120,8 +120,14 @@
|
||||
"mark_read": "Marcar como lido",
|
||||
"mark_unread": "Marcar como não lido",
|
||||
"delete": "Excluir",
|
||||
"delete_confirm_title": "Excluir e-mails",
|
||||
"delete_confirm_message": "Tem certeza que deseja excluir {count, plural, one {1 e-mail} other {# e-mails}}?",
|
||||
"clear_selection": "Limpar seleção"
|
||||
}
|
||||
},
|
||||
"permanent_delete": "Excluir permanentemente",
|
||||
"permanent_delete_confirm_title": "Excluir permanentemente",
|
||||
"permanent_delete_confirm_message": "Este e-mail será permanentemente excluído. Esta ação não pode ser desfeita.",
|
||||
"permanent_delete_confirm_batch_message": "Estes {count, plural, one {1 e-mail} other {# e-mails}} serão permanentemente excluídos. Esta ação não pode ser desfeita."
|
||||
},
|
||||
"email_viewer": {
|
||||
"no_email_selected": "Nenhum e-mail selecionado",
|
||||
|
||||
@@ -60,7 +60,7 @@ interface EmailStore {
|
||||
fetchEmailContent: (client: JMAPClient, emailId: string) => Promise<Email | null>;
|
||||
fetchQuota: (client: JMAPClient) => Promise<void>;
|
||||
sendEmail: (client: JMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], identityId?: string, fromEmail?: string, draftId?: string, fromName?: string) => Promise<void>;
|
||||
deleteEmail: (client: JMAPClient, emailId: string) => Promise<void>;
|
||||
deleteEmail: (client: JMAPClient, emailId: string, forceDelete?: boolean) => Promise<void>;
|
||||
markAsRead: (client: JMAPClient, emailId: string, read: boolean) => Promise<void>;
|
||||
moveToMailbox: (client: JMAPClient, emailId: string, mailboxId: string) => Promise<void>;
|
||||
searchEmails: (client: JMAPClient, query: string) => Promise<void>;
|
||||
@@ -384,7 +384,7 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
deleteEmail: async (client, emailId) => {
|
||||
deleteEmail: async (client, emailId, forceDelete) => {
|
||||
try {
|
||||
// Get the email to check if it's unread and which mailboxes it belongs to
|
||||
const email = get().emails.find(e => e.id === emailId);
|
||||
@@ -401,8 +401,8 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const currentMailbox = mailboxes.find(mb => mb.id === selectedMailboxId);
|
||||
const accountId = currentMailbox?.isShared ? currentMailbox.accountId : undefined;
|
||||
|
||||
// If deleteAction is 'trash', try to move to trash mailbox
|
||||
if (deleteAction === 'trash') {
|
||||
// If deleteAction is 'trash' and not forced permanent delete, try to move to trash mailbox
|
||||
if (deleteAction === 'trash' && !forceDelete) {
|
||||
// Find trash mailbox for the correct account
|
||||
const trashMailbox = mailboxes.find(mb => {
|
||||
if (accountId) {
|
||||
|
||||
Reference in New Issue
Block a user