diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 4958207a6..a385636a2 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -42,7 +42,8 @@ import { findDraftIdentityIdForLogin, listRowSource, resolveDraftSource, type Dr import { useRemoveFromFolder } from "@/hooks/use-remove-from-folder"; import { groupSearchScopeFolders, SEARCH_SCOPE_ALL_FOLDERS } from "@/lib/search-scope-folders"; import { toast } from "@/stores/toast-store"; -import { cancelFailureToast, partlyRefusedWarning, rescheduleFailureToast } from "@/lib/send-failure-message"; +import { cancelFailureToast, partlyRefusedWarning, rescheduleFailureToast, rescheduledToast } from "@/lib/send-failure-message"; +import { formatScheduledSendAt } from "@/lib/scheduled-send-label"; import { formatRejectedRecipients, RescheduleRestoredError } from "@/lib/jmap/client"; import { subscribePushStateChanges } from "@/lib/push-events"; import { createMailPushListener } from "@/lib/mail-push-listener"; @@ -90,7 +91,7 @@ import { isFilterEmpty, activeFilterCount, DEFAULT_SEARCH_FILTERS } from "@/lib/ import { SearchBox, type ContactSearchField } from "@/components/search/search-box"; import { FilterPanelHost } from "@/components/search/filter-panel-host"; import type { ContactSuggestion } from "@/lib/search-suggestions"; -import type { Attachment } from "@/lib/jmap/types"; +import type { Attachment, SendEmailResult } from "@/lib/jmap/types"; import { peekListAttachments, requestListAttachments, type ListAttachmentSource, type LoadListAttachments } from "@/lib/list-attachments"; import { useSearchHistoryStore } from "@/stores/search-history-store"; import { WelcomeBanner } from "@/components/ui/welcome-banner"; @@ -2431,8 +2432,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { if (!client || !email.emailSubmissionId || !email.scheduledIdentityId) return; try { const result = await rescheduleScheduledEmail(client, email.emailSubmissionId, email.id, email.scheduledIdentityId, delayedUntil, email.scheduledAccountId); - const refused = partlyRefusedWarning(result, tComposer); - if (refused) toast.warning(refused); + announceRescheduled(result, delayedUntil); } catch (error) { console.error('Failed to reschedule the scheduled send:', error); const { title, message } = rescheduleFailureToast(error, tViewer, tComposer, scheduledTime(email.scheduledSendAt)); @@ -2443,6 +2443,12 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { const scheduledTime = (sendAt: string | undefined) => sendAt ? formatDateTime(sendAt, useSettingsStore.getState().timeFormat) : undefined; + // R9: a reschedule says when the message goes out now. + const announceRescheduled = (result: SendEmailResult, delayedUntil: string) => { + const { kind, title, message } = rescheduledToast(result, tViewer, tComposer, formatScheduledSendAt(result.sendAt || delayedUntil, useSettingsStore.getState().timeFormat)); + toast[kind](title, message ? { message } : undefined); + }; + const toastCancelFailure = (error: unknown, cancelFailedTitle: string) => { const { title, message } = cancelFailureToast(error, tViewer, cancelFailedTitle); toast.error(title, message ? { message } : undefined); @@ -5652,8 +5658,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { onRescheduleScheduled={async (delayedUntil) => { if (client && selectedEmail?.emailSubmissionId && selectedEmail.scheduledIdentityId) { const result = await rescheduleScheduledEmail(client, selectedEmail.emailSubmissionId, selectedEmail.id, selectedEmail.scheduledIdentityId, delayedUntil, selectedEmail.scheduledAccountId); - const refused = partlyRefusedWarning(result, tComposer); - if (refused) toast.warning(refused); + announceRescheduled(result, delayedUntil); } }} onCompose={() => { diff --git a/lib/__tests__/send-failure-message.test.ts b/lib/__tests__/send-failure-message.test.ts index 1437e2dc8..3cac8524a 100644 --- a/lib/__tests__/send-failure-message.test.ts +++ b/lib/__tests__/send-failure-message.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { NoLongerScheduledError, RecipientsRejectedError, RequestTimeoutError, ScheduleTooLateError, SubmissionQuotaError, formatRejectedRecipients } from '../jmap/client'; -import { SendCancelledCleanupError, cancelFailureToast, partlyRefusedWarning, rescheduleFailureToast, sendFailureMessage } from '../send-failure-message'; +import { SendCancelledCleanupError, cancelFailureToast, partlyRefusedWarning, rescheduleFailureToast, rescheduledToast, sendFailureMessage } from '../send-failure-message'; // Keys of a namespace, returned with their values so the test sees which // message was chosen and what went into it. @@ -107,6 +107,21 @@ describe('partlyRefusedWarning', () => { }); }); +// A reschedule that went through said nothing at all. +describe('rescheduledToast', () => { + it('says when the message goes out now', () => { + expect(rescheduledToast({}, tViewer, t, 'Thu, Oct 29, 08:00')) + .toEqual({ kind: 'success', title: 'email_viewer.rescheduled_for {"date":"Thu, Oct 29, 08:00"}' }); + }); + + it('warns about the recipients the server refused', () => { + const toast = rescheduledToast({ rejectedRecipients: [{ email: 'nobody@example.net', smtpReply: '550 5.1.1 No such user' }] }, tViewer, t, 'Mon, Nov 2, 08:00'); + expect(toast.kind).toBe('warning'); + expect(toast.title).toBe('email_viewer.rescheduled_for {"date":"Mon, Nov 2, 08:00"}'); + expect(toast.message).toContain('email_composer.recipients_partly_refused'); + }); +}); + describe('cancelFailureToast', () => { it('reports a failed cancel with its reason: the mail may still go out', () => { expect(cancelFailureToast(new Error('The requested message is no longer in the queue.'), tViewer, 'Undo send failed')) diff --git a/lib/send-failure-message.ts b/lib/send-failure-message.ts index 0590c8968..f64f0e422 100644 --- a/lib/send-failure-message.ts +++ b/lib/send-failure-message.ts @@ -93,6 +93,21 @@ export function partlyRefusedWarning(result: { rejectedRecipients?: RejectedReci : null; } +/** + * The toast after a reschedule the server took (R9): when the message goes + * out now (`sendAt`, formatted), and whom the server refused, if anyone. + */ +export function rescheduledToast( + result: { rejectedRecipients?: RejectedRecipient[] }, + tViewer: Translate, + tComposer: Translate, + sendAt: string, +): { kind: 'success' | 'warning'; title: string; message?: string } { + const title = tViewer('rescheduled_for', { date: sendAt }); + const refused = partlyRefusedWarning(result, tComposer); + return refused ? { kind: 'warning', title, message: refused } : { kind: 'success', title }; +} + /** * The toast for a failed "Undo send" or "Cancel and edit". A failed cancel * may still let the mail go, so it must never read as done: it gets diff --git a/locales/ar/common.json b/locales/ar/common.json index 8362d7178..8c9d91835 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -672,6 +672,7 @@ "already_sent": "أُرسلت هذه الرسالة بالفعل.", "no_longer_scheduled": "لم تعد هذه الرسالة مجدولة.", "reschedule_recipients_refused": "لم يتغيّر شيء: رفض الخادم {recipients}.", + "rescheduled_for": "أُعيدت الجدولة إلى {date}", "contact_sidebar": { "title": "جهة الاتصال", "close": "إغلاق الشريط الجانبي", diff --git a/locales/ca/common.json b/locales/ca/common.json index 93443e066..73673373c 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -672,6 +672,7 @@ "already_sent": "Aquest missatge ja s'ha enviat.", "no_longer_scheduled": "Aquest missatge ja no està programat.", "reschedule_recipients_refused": "No s'ha canviat res: el servidor ha rebutjat {recipients}.", + "rescheduled_for": "S'ha reprogramat per a {date}", "contact_sidebar": { "title": "Contacte", "close": "Tanca la barra lateral", diff --git a/locales/cs/common.json b/locales/cs/common.json index 9a36130c2..fc4faf4e4 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -672,6 +672,7 @@ "already_sent": "Tato zpráva již byla odeslána.", "no_longer_scheduled": "Tato zpráva už není naplánována.", "reschedule_recipients_refused": "Nic se nezměnilo: server odmítl {recipients}.", + "rescheduled_for": "Přeplánováno na {date}", "contact_sidebar": { "title": "Kontakt", "close": "Zavřít postranní panel", diff --git a/locales/da/common.json b/locales/da/common.json index 3a02cdc78..d40e96b8d 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -672,6 +672,7 @@ "already_sent": "Denne besked er allerede sendt.", "no_longer_scheduled": "Denne besked er ikke længere planlagt.", "reschedule_recipients_refused": "Intet blev ændret: serveren afviste {recipients}.", + "rescheduled_for": "Omplanlagt til {date}", "contact_sidebar": { "title": "Kontakt", "close": "Luk sidepanel", diff --git a/locales/de/common.json b/locales/de/common.json index c48ca51e2..abfd84d41 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -672,6 +672,7 @@ "already_sent": "Diese Nachricht wurde bereits gesendet.", "no_longer_scheduled": "Diese Nachricht ist nicht mehr geplant.", "reschedule_recipients_refused": "Nichts wurde geändert: Der Server hat {recipients} abgelehnt.", + "rescheduled_for": "Neu geplant für {date}", "contact_sidebar": { "title": "Kontakt", "close": "Seitenleiste schließen", diff --git a/locales/en/common.json b/locales/en/common.json index 4329039cf..57ece13bb 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -672,6 +672,7 @@ "already_sent": "This message was already sent.", "no_longer_scheduled": "This message is no longer scheduled.", "reschedule_recipients_refused": "Nothing was changed: the server refused {recipients}.", + "rescheduled_for": "Rescheduled for {date}", "contact_sidebar": { "title": "Contact", "close": "Close sidebar", diff --git a/locales/es/common.json b/locales/es/common.json index 681722895..3e77e02de 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -672,6 +672,7 @@ "already_sent": "Este mensaje ya se envió.", "no_longer_scheduled": "Este mensaje ya no está programado.", "reschedule_recipients_refused": "No se cambió nada: el servidor rechazó {recipients}.", + "rescheduled_for": "Reprogramado para {date}", "contact_sidebar": { "title": "Contacto", "close": "Cerrar panel", diff --git a/locales/et/common.json b/locales/et/common.json index 6677299c0..17be7c0d0 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -672,6 +672,7 @@ "already_sent": "See kiri on juba saadetud.", "no_longer_scheduled": "See kiri ei ole enam ajastatud.", "reschedule_recipients_refused": "Midagi ei muudetud: server lükkas tagasi {recipients}.", + "rescheduled_for": "Ajastatud ümber: {date}", "contact_sidebar": { "title": "Kontakt", "close": "Sulge külgriba", diff --git a/locales/fa/common.json b/locales/fa/common.json index 32557ba10..74e020032 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -672,6 +672,7 @@ "already_sent": "این پیام قبلاً ارسال شده است.", "no_longer_scheduled": "این پیام دیگر زمان‌بندی نشده است.", "reschedule_recipients_refused": "چیزی تغییر نکرد: سرور {recipients} را رد کرد.", + "rescheduled_for": "زمان‌بندی دوباره برای {date}", "contact_sidebar": { "title": "مخاطب", "close": "بستن نوار کناری", diff --git a/locales/fr/common.json b/locales/fr/common.json index 45c46b169..4e2e789c5 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -672,6 +672,7 @@ "already_sent": "Ce message a déjà été envoyé.", "no_longer_scheduled": "Ce message n’est plus planifié.", "reschedule_recipients_refused": "Rien n’a été modifié : le serveur a refusé {recipients}.", + "rescheduled_for": "Reprogrammé pour le {date}", "contact_sidebar": { "title": "Contact", "close": "Fermer le panneau", diff --git a/locales/he/common.json b/locales/he/common.json index af15c6fe9..8dff936b8 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -637,6 +637,7 @@ "already_sent": "ההודעה הזו כבר נשלחה.", "no_longer_scheduled": "ההודעה הזו כבר אינה מתוזמנת.", "reschedule_recipients_refused": "דבר לא השתנה: השרת דחה את {recipients}.", + "rescheduled_for": "התזמון שונה ל-{date}", "contact_sidebar": { "title": "איש קשר", "close": "סגור סרגל צד", diff --git a/locales/hu/common.json b/locales/hu/common.json index 4aa36c35c..f992a7fc5 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -672,6 +672,7 @@ "already_sent": "Ez az üzenet már el lett küldve.", "no_longer_scheduled": "Ez az üzenet már nincs ütemezve.", "reschedule_recipients_refused": "Semmi sem változott: a kiszolgáló elutasította a következőt: {recipients}.", + "rescheduled_for": "Átütemezve: {date}", "contact_sidebar": { "title": "Névjegy", "close": "Oldalsáv bezárása", diff --git a/locales/it/common.json b/locales/it/common.json index 4d5ec0030..54da3bc61 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -672,6 +672,7 @@ "already_sent": "Questo messaggio è già stato inviato.", "no_longer_scheduled": "Questo messaggio non è più programmato.", "reschedule_recipients_refused": "Non è stato cambiato nulla: il server ha rifiutato {recipients}.", + "rescheduled_for": "Riprogrammato per {date}", "contact_sidebar": { "title": "Contatto", "close": "Chiudi barra laterale", diff --git a/locales/ja/common.json b/locales/ja/common.json index 5d6d4771c..93cefba4a 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -672,6 +672,7 @@ "already_sent": "このメッセージはすでに送信されています。", "no_longer_scheduled": "このメッセージはもう送信予定ではありません。", "reschedule_recipients_refused": "何も変更されていません: サーバーが {recipients} を拒否しました。", + "rescheduled_for": "{date} に再スケジュールしました", "contact_sidebar": { "title": "連絡先", "close": "サイドバーを閉じる", diff --git a/locales/ko/common.json b/locales/ko/common.json index 5f5e78516..e57ccd5ef 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -672,6 +672,7 @@ "already_sent": "이 메시지는 이미 보냈습니다.", "no_longer_scheduled": "이 메시지는 더 이상 예약되어 있지 않습니다.", "reschedule_recipients_refused": "변경된 내용이 없습니다: 서버가 {recipients}을(를) 거부했습니다.", + "rescheduled_for": "{date}(으)로 예약을 변경했습니다", "contact_sidebar": { "title": "연락처", "close": "사이드바 닫기", diff --git a/locales/lv/common.json b/locales/lv/common.json index 4abab7cdc..5c21ef537 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -672,6 +672,7 @@ "already_sent": "Šis ziņojums jau ir nosūtīts.", "no_longer_scheduled": "Šis ziņojums vairs nav ieplānots.", "reschedule_recipients_refused": "Nekas netika mainīts: serveris noraidīja {recipients}.", + "rescheduled_for": "Pārplānots uz {date}", "contact_sidebar": { "title": "Kontaktpersona", "close": "Aizvērt sānjoslu", diff --git a/locales/mn/common.json b/locales/mn/common.json index 6e29449b6..0a74f3713 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -672,6 +672,7 @@ "already_sent": "Энэ мессеж аль хэдийн илгээгдсэн.", "no_longer_scheduled": "Энэ мессеж товлогдоогүй болсон.", "reschedule_recipients_refused": "Юу ч өөрчлөгдсөнгүй: сервер дараах хүлээн авагчдыг татгалзлаа: {recipients}.", + "rescheduled_for": "{date}-нд дахин товлогдлоо", "contact_sidebar": { "title": "Холбоо барих", "close": "Хажуугийн самбарыг хаах", diff --git a/locales/nb/common.json b/locales/nb/common.json index 316709528..7cfd503a3 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -672,6 +672,7 @@ "already_sent": "Denne meldingen er allerede sendt.", "no_longer_scheduled": "Denne meldingen er ikke lenger planlagt.", "reschedule_recipients_refused": "Ingenting ble endret: serveren avviste {recipients}.", + "rescheduled_for": "Planlagt på nytt til {date}", "contact_sidebar": { "title": "Kontakt", "close": "Lukk sidefeltet", diff --git a/locales/nl/common.json b/locales/nl/common.json index a9d8f202d..d39409009 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -672,6 +672,7 @@ "already_sent": "Dit bericht is al verzonden.", "no_longer_scheduled": "Dit bericht staat niet meer gepland.", "reschedule_recipients_refused": "Er is niets gewijzigd: de server heeft {recipients} geweigerd.", + "rescheduled_for": "Opnieuw gepland voor {date}", "contact_sidebar": { "title": "Contact", "close": "Zijbalk sluiten", diff --git a/locales/pl/common.json b/locales/pl/common.json index 69c043ef9..63943db71 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -672,6 +672,7 @@ "already_sent": "Ta wiadomość została już wysłana.", "no_longer_scheduled": "Ta wiadomość nie jest już zaplanowana.", "reschedule_recipients_refused": "Nic nie zostało zmienione: serwer odrzucił {recipients}.", + "rescheduled_for": "Przełożono na {date}", "contact_sidebar": { "title": "Kontakt", "close": "Zamknij panel boczny", diff --git a/locales/pt/common.json b/locales/pt/common.json index 34febc6cd..75a47ff07 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -672,6 +672,7 @@ "already_sent": "Esta mensagem já foi enviada.", "no_longer_scheduled": "Esta mensagem não está mais agendada.", "reschedule_recipients_refused": "Nada foi alterado: o servidor recusou {recipients}.", + "rescheduled_for": "Reagendado para {date}", "contact_sidebar": { "title": "Contato", "close": "Fechar barra lateral", diff --git a/locales/ro/common.json b/locales/ro/common.json index c888e1c34..35d93304c 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -672,6 +672,7 @@ "already_sent": "Acest mesaj a fost deja trimis.", "no_longer_scheduled": "Acest mesaj nu mai este programat.", "reschedule_recipients_refused": "Nu s-a schimbat nimic: serverul a respins {recipients}.", + "rescheduled_for": "Reprogramat pentru {date}", "contact_sidebar": { "title": "Contact", "close": "Închide bara laterală", diff --git a/locales/ru/common.json b/locales/ru/common.json index 26248fc12..db483b539 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -672,6 +672,7 @@ "already_sent": "Это письмо уже отправлено.", "no_longer_scheduled": "Это письмо больше не запланировано.", "reschedule_recipients_refused": "Ничего не изменилось: сервер отклонил {recipients}.", + "rescheduled_for": "Перенесено на {date}", "contact_sidebar": { "title": "Контакт", "close": "Закрыть боковую панель", diff --git a/locales/sk/common.json b/locales/sk/common.json index 5f78df7b6..82b13041e 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -672,6 +672,7 @@ "already_sent": "Táto správa už bola odoslaná.", "no_longer_scheduled": "Táto správa už nie je naplánovaná.", "reschedule_recipients_refused": "Nič sa nezmenilo: server odmietol {recipients}.", + "rescheduled_for": "Preplánované na {date}", "contact_sidebar": { "title": "Kontakt", "close": "Zavrieť postranný panel", diff --git a/locales/th/common.json b/locales/th/common.json index 191606baa..dbf26e7fc 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -1432,6 +1432,7 @@ "already_sent": "ข้อความนี้ถูกส่งออกไปแล้ว", "no_longer_scheduled": "ข้อความนี้ไม่มีกำหนดเวลาส่งแล้ว", "reschedule_recipients_refused": "ไม่มีการเปลี่ยนแปลง: เซิร์ฟเวอร์ปฏิเสธผู้รับ {recipients}", + "rescheduled_for": "กำหนดเวลาใหม่เป็น {date}", "contact_sidebar": { "title": "ข้อมูลผู้ติดต่อ", "close": "ปิด", diff --git a/locales/tr/common.json b/locales/tr/common.json index ccb1b0544..655762b61 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -672,6 +672,7 @@ "already_sent": "Bu ileti zaten gönderildi.", "no_longer_scheduled": "Bu ileti artık zamanlanmış değil.", "reschedule_recipients_refused": "Hiçbir şey değiştirilmedi: sunucu şu alıcıları reddetti: {recipients}.", + "rescheduled_for": "{date} için yeniden zamanlandı", "contact_sidebar": { "title": "Kişi", "close": "Kenar çubuğunu kapat", diff --git a/locales/uk/common.json b/locales/uk/common.json index 735d06df1..7e28b8639 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -672,6 +672,7 @@ "already_sent": "Цей лист уже надіслано.", "no_longer_scheduled": "Цей лист більше не заплановано.", "reschedule_recipients_refused": "Нічого не змінено: сервер відхилив {recipients}.", + "rescheduled_for": "Перенесено на {date}", "contact_sidebar": { "title": "Контакт", "close": "Закрити бічну панель", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 904bc88de..fb0b65992 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -672,6 +672,7 @@ "already_sent": "此郵件已寄出。", "no_longer_scheduled": "此郵件已不在排定傳送中。", "reschedule_recipients_refused": "未作任何變更:伺服器拒絕了 {recipients}。", + "rescheduled_for": "已改為在 {date} 傳送", "contact_sidebar": { "title": "聯絡人", "close": "關閉側邊欄", diff --git a/locales/zh/common.json b/locales/zh/common.json index c0d17e304..6b1ea8150 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -672,6 +672,7 @@ "already_sent": "此邮件已发送。", "no_longer_scheduled": "此邮件已不在定时发送中。", "reschedule_recipients_refused": "未作任何更改:服务器拒绝了 {recipients}。", + "rescheduled_for": "已改为在 {date} 发送", "contact_sidebar": { "title": "联系人", "close": "关闭侧边栏",