feat(calendar): add event notifications with alert evaluation

Add client-side calendar event notification system that evaluates JMAP
CalendarEventAlert triggers and displays toast notifications when alert
times are reached. Includes configurable notification sound, acknowledged
alert persistence, and proactive event fetching for background alerts.
Also mounts ToastContainer globally to fix silent toast failures.
This commit is contained in:
Matthieu MALVACHE
2026-02-16 23:49:35 +01:00
committed by Matthieu MALVACHE
parent 73612313c0
commit fea21d5bcd
24 changed files with 949 additions and 47 deletions
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Standardkalender",
"default_reminder": "Standarderinnerung",
"time_format_12h": "12-Stunden",
"time_format_24h": "24-Stunden"
"time_format_24h": "24-Stunden",
"notifications_enabled": "Ereignisbenachrichtigungen",
"notifications_enabled_desc": "Benachrichtigungen für bevorstehende Termine anzeigen",
"notification_sound": "Benachrichtigungston",
"notification_sound_desc": "Ton für Kalenderbenachrichtigungen abspielen"
},
"days": {
"monday": "Montag",
@@ -1121,7 +1125,10 @@
"event_deleted": "Termin gelöscht",
"calendar_created": "Kalender erstellt",
"calendar_deleted": "Kalender gelöscht",
"event_move_error": "Termin konnte nicht verschoben werden"
"event_move_error": "Termin konnte nicht verschoben werden",
"alert_title": "Bevorstehender Termin",
"alert_now": "Beginnt jetzt",
"alert_in_minutes": "In {count} Min."
},
"status": {
"loading_calendars": "Kalender werden geladen...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Default calendar",
"default_reminder": "Default reminder",
"time_format_12h": "12-hour",
"time_format_24h": "24-hour"
"time_format_24h": "24-hour",
"notifications_enabled": "Event notifications",
"notifications_enabled_desc": "Show alerts for upcoming calendar events",
"notification_sound": "Notification sound",
"notification_sound_desc": "Play a sound for calendar alerts"
},
"days": {
"monday": "Monday",
@@ -1121,7 +1125,10 @@
"event_deleted": "Event deleted",
"calendar_created": "Calendar created",
"calendar_deleted": "Calendar deleted",
"event_move_error": "Failed to move event"
"event_move_error": "Failed to move event",
"alert_title": "Upcoming event",
"alert_now": "Starting now",
"alert_in_minutes": "In {count} min"
},
"status": {
"loading_calendars": "Loading calendars...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Calendario predeterminado",
"default_reminder": "Recordatorio predeterminado",
"time_format_12h": "12 horas",
"time_format_24h": "24 horas"
"time_format_24h": "24 horas",
"notifications_enabled": "Notificaciones de eventos",
"notifications_enabled_desc": "Mostrar alertas para eventos próximos",
"notification_sound": "Sonido de notificación",
"notification_sound_desc": "Reproducir un sonido para las alertas del calendario"
},
"days": {
"monday": "Lunes",
@@ -1121,7 +1125,10 @@
"event_deleted": "Evento eliminado",
"calendar_created": "Calendario creado",
"calendar_deleted": "Calendario eliminado",
"event_move_error": "Error al mover el evento"
"event_move_error": "Error al mover el evento",
"alert_title": "Evento próximo",
"alert_now": "Comienza ahora",
"alert_in_minutes": "En {count} min"
},
"status": {
"loading_calendars": "Cargando calendarios...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Calendrier par défaut",
"default_reminder": "Rappel par défaut",
"time_format_12h": "12 heures",
"time_format_24h": "24 heures"
"time_format_24h": "24 heures",
"notifications_enabled": "Notifications d'événements",
"notifications_enabled_desc": "Afficher les alertes pour les événements à venir",
"notification_sound": "Son de notification",
"notification_sound_desc": "Jouer un son pour les alertes de calendrier"
},
"days": {
"monday": "Lundi",
@@ -1121,7 +1125,10 @@
"event_deleted": "Événement supprimé",
"calendar_created": "Calendrier créé",
"calendar_deleted": "Calendrier supprimé",
"event_move_error": "Échec du déplacement de l'événement"
"event_move_error": "Échec du déplacement de l'événement",
"alert_title": "Événement à venir",
"alert_now": "Commence maintenant",
"alert_in_minutes": "Dans {count} min"
},
"status": {
"loading_calendars": "Chargement des calendriers...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Calendario predefinito",
"default_reminder": "Promemoria predefinito",
"time_format_12h": "12 ore",
"time_format_24h": "24 ore"
"time_format_24h": "24 ore",
"notifications_enabled": "Notifiche eventi",
"notifications_enabled_desc": "Mostra avvisi per gli eventi in arrivo",
"notification_sound": "Suono di notifica",
"notification_sound_desc": "Riproduci un suono per gli avvisi del calendario"
},
"days": {
"monday": "Lunedì",
@@ -1121,7 +1125,10 @@
"event_deleted": "Evento eliminato",
"calendar_created": "Calendario creato",
"calendar_deleted": "Calendario eliminato",
"event_move_error": "Spostamento dell'evento non riuscito"
"event_move_error": "Spostamento dell'evento non riuscito",
"alert_title": "Evento in arrivo",
"alert_now": "Inizia ora",
"alert_in_minutes": "Tra {count} min"
},
"status": {
"loading_calendars": "Caricamento calendari...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "デフォルトのカレンダー",
"default_reminder": "デフォルトのリマインダー",
"time_format_12h": "12時間制",
"time_format_24h": "24時間制"
"time_format_24h": "24時間制",
"notifications_enabled": "イベント通知",
"notifications_enabled_desc": "予定のイベントのアラートを表示する",
"notification_sound": "通知音",
"notification_sound_desc": "カレンダーアラートの音を鳴らす"
},
"days": {
"monday": "月曜日",
@@ -1121,7 +1125,10 @@
"event_deleted": "予定を削除しました",
"calendar_created": "カレンダーを作成しました",
"calendar_deleted": "カレンダーを削除しました",
"event_move_error": "イベントの移動に失敗しました"
"event_move_error": "イベントの移動に失敗しました",
"alert_title": "予定のイベント",
"alert_now": "まもなく開始",
"alert_in_minutes": "{count}分後"
},
"status": {
"loading_calendars": "カレンダーを読み込み中...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Standaardagenda",
"default_reminder": "Standaardherinnering",
"time_format_12h": "12-uurs",
"time_format_24h": "24-uurs"
"time_format_24h": "24-uurs",
"notifications_enabled": "Evenementmeldingen",
"notifications_enabled_desc": "Meldingen weergeven voor aankomende evenementen",
"notification_sound": "Meldingsgeluid",
"notification_sound_desc": "Geluid afspelen voor agendameldingen"
},
"days": {
"monday": "Maandag",
@@ -1121,7 +1125,10 @@
"event_deleted": "Evenement verwijderd",
"calendar_created": "Agenda aangemaakt",
"calendar_deleted": "Agenda verwijderd",
"event_move_error": "Evenement verplaatsen mislukt"
"event_move_error": "Evenement verplaatsen mislukt",
"alert_title": "Aankomend evenement",
"alert_now": "Begint nu",
"alert_in_minutes": "Over {count} min"
},
"status": {
"loading_calendars": "Agenda's laden...",
+9 -2
View File
@@ -1097,7 +1097,11 @@
"default_calendar": "Calendário padrão",
"default_reminder": "Lembrete padrão",
"time_format_12h": "12 horas",
"time_format_24h": "24 horas"
"time_format_24h": "24 horas",
"notifications_enabled": "Notificações de eventos",
"notifications_enabled_desc": "Mostrar alertas para eventos próximos",
"notification_sound": "Som de notificação",
"notification_sound_desc": "Reproduzir um som para alertas do calendário"
},
"days": {
"monday": "Segunda-feira",
@@ -1121,7 +1125,10 @@
"event_deleted": "Evento excluído",
"calendar_created": "Calendário criado",
"calendar_deleted": "Calendário excluído",
"event_move_error": "Falha ao mover o evento"
"event_move_error": "Falha ao mover o evento",
"alert_title": "Evento próximo",
"alert_now": "Começa agora",
"alert_in_minutes": "Em {count} min"
},
"status": {
"loading_calendars": "Carregando calendários...",