@rapidmx/web-client 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -2
- package/apps/admin/_layout.tsx +8 -2
- package/apps/admin/index.tsx +7 -1
- package/apps/admin/ingest-queue/index.tsx +2 -1
- package/apps/admin/mailboxes/[uid].tsx +23 -10
- package/apps/admin/quarantine/index.tsx +2 -1
- package/apps/admin/signing-certificates/index.tsx +343 -0
- package/apps/escrow/_layout.tsx +8 -2
- package/apps/shared/appearance/AppearanceForm.tsx +392 -0
- package/apps/shared/appearance/AppearanceHead.tsx +49 -0
- package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
- package/apps/shared/appearance/ColorField.tsx +107 -0
- package/apps/shared/appearance/appearanceCache.ts +106 -0
- package/apps/shared/appearance/appearanceContext.ts +77 -0
- package/apps/shared/appearance/bootScript.ts +35 -0
- package/apps/shared/appearance/color.ts +169 -0
- package/apps/shared/appearance/photo.ts +88 -0
- package/apps/shared/appearance/resolvedTheme.ts +67 -0
- package/apps/shared/appearance/theme.ts +355 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
- package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
- package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
- package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
- package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
- package/apps/shared/components/contacts/ContactForm.tsx +11 -5
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
- package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +98 -32
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- package/apps/shared/components/layout/RailIcon.tsx +100 -0
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
- package/apps/shared/components/layout/UserMenu.tsx +75 -6
- package/apps/shared/components/mail/ConversationList.tsx +4 -2
- package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
- package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
- package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
- package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
- package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
- package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
- package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
- package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
- package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
- package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
- package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
- package/apps/shared/components/mail/reading/color.ts +245 -0
- package/apps/shared/components/mail/reading/frameControl.ts +142 -0
- package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
- package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
- package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
- package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
- package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
- package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
- package/apps/shared/mail/folderCounts.ts +9 -3
- package/apps/shared/mail/folderTree.ts +143 -0
- package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
- package/apps/shared/mail/outbox/composeBridge.ts +69 -0
- package/apps/shared/mail/outbox/outboxState.ts +88 -0
- package/apps/shared/mail/outbox/pendingSends.ts +146 -0
- package/apps/shared/mail/outbox/sendDecision.ts +157 -0
- package/apps/shared/mail/outbox/sendJob.ts +427 -0
- package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
- package/apps/shared/mail/outbox/sendState.ts +35 -0
- package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
- package/apps/shared/mail/useMailConnection.ts +81 -28
- package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
- package/apps/shared/mail/useNewMailNotifications.ts +47 -32
- package/apps/shared/navigation/AppRouter.tsx +2 -2
- package/apps/shared/notifications/NotificationCenter.tsx +215 -0
- package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
- package/apps/shared/notifications/apiErrors.ts +98 -0
- package/apps/shared/notifications/headerOffset.ts +35 -0
- package/apps/shared/notifications/pushStatus.ts +56 -0
- package/apps/shared/notifications/store.ts +549 -0
- package/apps/shared/notifications/systemErrors.ts +63 -0
- package/apps/shared/notifications/useNotifications.ts +41 -0
- package/apps/shared/signing/enrollmentStorage.ts +33 -0
- package/apps/shared/signing/enrollmentTracker.ts +385 -0
- package/apps/shared/signing/enrollmentView.ts +251 -0
- package/apps/shared/signing/signingInfo.ts +58 -0
- package/apps/shared/signing/useNow.ts +19 -0
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
- package/apps/shared/styles/app.css +80 -0
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +9 -14
- package/apps/www/contacts/[uid].tsx +5 -8
- package/apps/www/contacts/index.tsx +587 -584
- package/apps/www/index.tsx +38 -27
- package/apps/www/messages/[uid].tsx +3 -1
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +3 -7
- package/apps/www/settings/encryption/index.tsx +151 -116
- package/apps/www/settings/filters/[uid].tsx +3 -1
- package/apps/www/settings/filters/new/index.tsx +3 -1
- package/apps/www/settings/privacy/index.tsx +3 -8
- package/apps/www/settings/read-receipts/index.tsx +3 -7
- package/apps/www/settings/sharing/index.tsx +44 -65
- package/apps/www/settings/signatures/[uid].tsx +3 -1
- package/apps/www/settings/signatures/new/index.tsx +3 -2
- package/apps/www/tasks/index.tsx +11 -16
- package/dist/apps/admin/_layout.d.ts +5 -1
- package/dist/apps/admin/_layout.js +3 -2
- package/dist/apps/admin/index.js +3 -2
- package/dist/apps/admin/ingest-queue/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +9 -6
- package/dist/apps/admin/quarantine/index.js +2 -1
- package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
- package/dist/apps/admin/signing-certificates/index.js +161 -0
- package/dist/apps/escrow/_layout.d.ts +5 -1
- package/dist/apps/escrow/_layout.js +3 -2
- package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
- package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
- package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
- package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
- package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
- package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
- package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
- package/dist/apps/shared/appearance/ColorField.js +40 -0
- package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceCache.js +80 -0
- package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceContext.js +29 -0
- package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
- package/dist/apps/shared/appearance/bootScript.js +33 -0
- package/dist/apps/shared/appearance/color.d.ts +65 -0
- package/dist/apps/shared/appearance/color.js +132 -0
- package/dist/apps/shared/appearance/photo.d.ts +17 -0
- package/dist/apps/shared/appearance/photo.js +79 -0
- package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
- package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
- package/dist/apps/shared/appearance/theme.d.ts +111 -0
- package/dist/apps/shared/appearance/theme.js +251 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
- package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
- package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
- package/dist/apps/shared/components/layout/AppShell.js +59 -13
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
- package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
- package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
- package/dist/apps/shared/components/layout/RailIcon.js +85 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
- package/dist/apps/shared/components/layout/UserMenu.js +41 -7
- package/dist/apps/shared/components/mail/ConversationList.js +3 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
- package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
- package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
- package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
- package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
- package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
- package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
- package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
- package/dist/apps/shared/components/mail/reading/color.js +201 -0
- package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
- package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
- package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
- package/dist/apps/shared/mail/folderCounts.js +9 -3
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
- package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
- package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
- package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
- package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
- package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
- package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
- package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
- package/dist/apps/shared/mail/outbox/sendState.js +8 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
- package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
- package/dist/apps/shared/mail/useMailConnection.js +64 -22
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
- package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
- package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
- package/dist/apps/shared/navigation/AppRouter.js +2 -2
- package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
- package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
- package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
- package/dist/apps/shared/notifications/apiErrors.js +86 -0
- package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
- package/dist/apps/shared/notifications/headerOffset.js +33 -0
- package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
- package/dist/apps/shared/notifications/pushStatus.js +54 -0
- package/dist/apps/shared/notifications/store.d.ts +136 -0
- package/dist/apps/shared/notifications/store.js +416 -0
- package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
- package/dist/apps/shared/notifications/systemErrors.js +58 -0
- package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
- package/dist/apps/shared/notifications/useNotifications.js +18 -0
- package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
- package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
- package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
- package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
- package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
- package/dist/apps/shared/signing/enrollmentView.js +214 -0
- package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
- package/dist/apps/shared/signing/signingInfo.js +48 -0
- package/dist/apps/shared/signing/useNow.d.ts +2 -0
- package/dist/apps/shared/signing/useNow.js +18 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
- package/dist/apps/shared/styles/app.css +80 -0
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +3 -4
- package/dist/apps/www/contacts/[uid].js +5 -7
- package/dist/apps/www/contacts/index.js +14 -21
- package/dist/apps/www/index.js +37 -19
- package/dist/apps/www/messages/[uid].js +3 -1
- package/dist/apps/www/settings/appearance/index.d.ts +4 -0
- package/dist/apps/www/settings/appearance/index.js +13 -0
- package/dist/apps/www/settings/auto-reply/index.js +3 -6
- package/dist/apps/www/settings/encryption/index.js +103 -95
- package/dist/apps/www/settings/filters/[uid].js +3 -1
- package/dist/apps/www/settings/filters/new/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +5 -8
- package/dist/apps/www/settings/read-receipts/index.js +3 -6
- package/dist/apps/www/settings/sharing/index.js +19 -33
- package/dist/apps/www/settings/signatures/[uid].js +3 -1
- package/dist/apps/www/settings/signatures/new/index.js +3 -2
- package/dist/apps/www/tasks/index.js +8 -14
- package/package.json +2 -2
- package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
- package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
|
@@ -13,6 +13,7 @@ import RichTextEditor from "../../../shared/components/mail/compose/RichTextEdit
|
|
|
13
13
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
14
14
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
15
15
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
16
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
16
17
|
const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
17
18
|
/** Signatures aren't backed by a draft/attachment record the way Compose's own inline images are — see
|
|
18
19
|
* `settings/signatures/new/index.tsx`'s identical stub for the full reasoning. */
|
|
@@ -78,7 +79,8 @@ function SignatureDetailContent({ uid }) {
|
|
|
78
79
|
setSaved(true);
|
|
79
80
|
}
|
|
80
81
|
catch (err) {
|
|
81
|
-
|
|
82
|
+
// A pop-up: `error` is only this form's own validation (and the load failure above).
|
|
83
|
+
notifyApiError(err, "Couldn't save the signature");
|
|
82
84
|
}
|
|
83
85
|
finally {
|
|
84
86
|
setSaving(false);
|
|
@@ -6,7 +6,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
import { routedPage } from "../../../_routedPage.js";
|
|
7
7
|
import { useNavigate } from "../../../../shared/navigation/AppRouter.js";
|
|
8
8
|
import { useState } from "react";
|
|
9
|
-
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
10
9
|
import { createMailSignature, listMailSignatures } from "@rapidmx/react-shared/mail/mailSignaturesApi.js";
|
|
11
10
|
import { clearPreviousDefaults } from "../signatureDefaults.js";
|
|
12
11
|
import SettingsShell, { useSettingsShell } from "../../../../shared/components/settings/layout/SettingsShell.js";
|
|
@@ -14,6 +13,7 @@ import RichTextEditor from "../../../../shared/components/mail/compose/RichTextE
|
|
|
14
13
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
15
14
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
16
15
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
16
|
+
import { notifyApiError } from "../../../../shared/notifications/apiErrors.js";
|
|
17
17
|
const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
18
18
|
function NewSignaturePage(props) {
|
|
19
19
|
return (_jsx(SettingsShell, { ...props, active: "signatures", children: _jsx(NewSignatureForm, {}) }));
|
|
@@ -56,7 +56,8 @@ function NewSignatureForm() {
|
|
|
56
56
|
navigate(`/settings/signatures/${encodeURIComponent(created.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`);
|
|
57
57
|
}
|
|
58
58
|
catch (err) {
|
|
59
|
-
|
|
59
|
+
// A pop-up: `error` above is only the form's own validation.
|
|
60
|
+
notifyApiError(err, "Couldn't create the signature");
|
|
60
61
|
}
|
|
61
62
|
finally {
|
|
62
63
|
setSaving(false);
|
|
@@ -21,6 +21,7 @@ import { LIST_PAGE_SIZE, MAX_LIST_PAGES, listAllPages } from "../../shared/mail/
|
|
|
21
21
|
import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
|
|
22
22
|
import { useShortcut } from "../../shared/keyboard/useShortcut.js";
|
|
23
23
|
import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
|
|
24
|
+
import { notifyApiError } from "../../shared/notifications/apiErrors.js";
|
|
24
25
|
const INPUT_CLASS = "text-sm py-1.5 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
25
26
|
function TasksPage(props) {
|
|
26
27
|
return (_jsx(TasksShell, { ...props, children: _jsx(TasksContent, {}) }));
|
|
@@ -172,7 +173,6 @@ function TasksContent() {
|
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
async function handleToggle(task) {
|
|
175
|
-
setError(null);
|
|
176
176
|
const nextCompleted = !task.completed;
|
|
177
177
|
setTasks((prev) => prev.map((t) => (t.uid === task.uid ? { ...t, completed: nextCompleted } : t)));
|
|
178
178
|
try {
|
|
@@ -181,17 +181,16 @@ function TasksContent() {
|
|
|
181
181
|
}
|
|
182
182
|
catch (err) {
|
|
183
183
|
setTasks((prev) => prev.map((t) => (t.uid === task.uid ? task : t)));
|
|
184
|
-
|
|
184
|
+
notifyApiError(err, "Couldn't update the task");
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
async function handleDelete(task) {
|
|
188
|
-
setError(null);
|
|
189
188
|
try {
|
|
190
189
|
await deleteTask(task.uid, task.version);
|
|
191
190
|
setTasks((prev) => prev.filter((t) => t.uid !== task.uid));
|
|
192
191
|
}
|
|
193
192
|
catch (err) {
|
|
194
|
-
|
|
193
|
+
notifyApiError(err, "Couldn't delete the task");
|
|
195
194
|
}
|
|
196
195
|
}
|
|
197
196
|
function handleSelectView(next) {
|
|
@@ -229,47 +228,42 @@ function TasksContent() {
|
|
|
229
228
|
}
|
|
230
229
|
}, [tasks, view, userUid]);
|
|
231
230
|
const checkedTasks = viewFiltered.filter((t) => checkedUids.has(t.uid));
|
|
231
|
+
// The bulk handlers try every task and report each failure: the same failure repeated is one pop-up with a count.
|
|
232
232
|
async function handleBulkComplete() {
|
|
233
|
-
let bulkError = null;
|
|
234
233
|
for (const task of checkedTasks) {
|
|
235
234
|
try {
|
|
236
235
|
await setTaskCompleted(task, true);
|
|
237
236
|
}
|
|
238
237
|
catch (err) {
|
|
239
|
-
|
|
238
|
+
notifyApiError(err, "Couldn't update some of the tasks");
|
|
240
239
|
}
|
|
241
240
|
}
|
|
242
241
|
setCheckedUids(new Set());
|
|
243
242
|
await reload();
|
|
244
|
-
setError(bulkError);
|
|
245
243
|
}
|
|
246
244
|
async function handleBulkAddToMyDay() {
|
|
247
|
-
let bulkError = null;
|
|
248
245
|
for (const task of checkedTasks) {
|
|
249
246
|
try {
|
|
250
247
|
await setTaskMyDay(task, true);
|
|
251
248
|
}
|
|
252
249
|
catch (err) {
|
|
253
|
-
|
|
250
|
+
notifyApiError(err, "Couldn't update some of the tasks");
|
|
254
251
|
}
|
|
255
252
|
}
|
|
256
253
|
setCheckedUids(new Set());
|
|
257
254
|
await reload();
|
|
258
|
-
setError(bulkError);
|
|
259
255
|
}
|
|
260
256
|
async function handleBulkDelete() {
|
|
261
|
-
let bulkError = null;
|
|
262
257
|
for (const task of checkedTasks) {
|
|
263
258
|
try {
|
|
264
259
|
await deleteTask(task.uid, task.version);
|
|
265
260
|
}
|
|
266
261
|
catch (err) {
|
|
267
|
-
|
|
262
|
+
notifyApiError(err, "Couldn't delete some of the tasks");
|
|
268
263
|
}
|
|
269
264
|
}
|
|
270
265
|
setCheckedUids(new Set());
|
|
271
266
|
await reload();
|
|
272
|
-
setError(bulkError);
|
|
273
267
|
}
|
|
274
268
|
function requestDelete(task) {
|
|
275
269
|
setPendingDelete({ type: "single", task });
|
|
@@ -296,7 +290,7 @@ function TasksContent() {
|
|
|
296
290
|
return map;
|
|
297
291
|
}, [viewFiltered]);
|
|
298
292
|
const completedTasks = viewFiltered.filter((t) => t.completed);
|
|
299
|
-
return (_jsxs("div", { className: "flex-1
|
|
293
|
+
return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row", children: [_jsx(TasksSidebar, { mailboxUid: mailboxUid, tasks: tasks, userUid: userUid, active: view, onSelect: handleSelectView }), _jsxs("div", { className: "flex-1 min-w-0 flex flex-col overflow-y-auto", children: [_jsx(TasksToolbar, { viewMode: viewMode, onViewModeChange: setViewMode, selectedCount: checkedTasks.length, onComplete: handleBulkComplete, onDelete: () => setPendingDelete({ type: "bulk" }), onAddToMyDay: handleBulkAddToMyDay }), _jsxs("div", { role: "region", "aria-label": "Tasks list", className: "max-w-2xl mx-auto w-full flex flex-col gap-6 p-6", children: [_jsx("h1", { className: "text-xl font-bold uppercase tracking-wide", children: "Tasks" }), error && _jsx(Alert, { children: error }), truncated && view.type !== "flagged" && (_jsxs(Alert, { children: ["This folder has more tasks than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] })), view.type === "flagged" ? (_jsx(FlaggedEmailList, { loading: flaggedLoading, error: flaggedError, messages: flaggedMessages })) : (_jsxs(_Fragment, { children: [_jsxs("form", { onSubmit: handleCreate, className: "flex flex-wrap items-center gap-2 bg-surface border border-border rounded-md p-2", children: [_jsx("input", { ref: addTaskRef, type: "text", "aria-label": "Add a task", ...addTaskHint, className: `${INPUT_CLASS} min-w-40 basis-40 flex-1`, value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Add a task" }), _jsx("input", { type: "date", "aria-label": "Due date", className: INPUT_CLASS, value: dueDate, onChange: (e) => setDueDate(e.target.value) }), _jsxs("select", { "aria-label": "Priority", className: INPUT_CLASS, value: priority, onChange: (e) => setPriority(e.target.value), children: [_jsx("option", { value: "low", children: "Low" }), _jsx("option", { value: "normal", children: "Normal" }), _jsx("option", { value: "high", children: "High" })] }), writableMailboxes.length > 1 && (_jsx("select", { "aria-label": "Task mailbox", className: `${INPUT_CLASS} max-w-40`,
|
|
300
294
|
// `writableMailboxes` only has entries once `mailboxUid` has resolved.
|
|
301
295
|
value: targetMailboxUid ?? mailboxUid, onChange: (e) => setTargetMailboxUid(e.target.value), children: writableMailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid === userUid ? "" : " (shared)"] }, mb.uid))) })), _jsx(Button, { type: "submit", loading: creating, disabled: creating, className: "!w-auto shrink-0", children: "Add" })] }), createError && _jsx(Alert, { children: createError }), addedElsewhere && (_jsxs("p", { role: "status", className: "text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: ["\u201C", addedElsewhere.title, "\u201D was added to", " ", mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`, className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s tasks" })] })), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : viewFiltered.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No tasks yet." })) : viewMode === "grid" ? (_jsx(TaskTable, { tasks: viewFiltered, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete })) : (_jsxs(_Fragment, { children: [BUCKET_ORDER.map((bucket) => {
|
|
302
296
|
const items = grouped.get(bucket);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rapidmx/web-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "RapidMX's webmail and admin console React UI for web-based clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "https://github.com/rapidmx/web-client",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@dnd-kit/core": "^6.3.1",
|
|
40
40
|
"@journeyapps/wa-sqlite": "^2.0.4",
|
|
41
41
|
"@peculiar/x509": "^2.1.0",
|
|
42
|
-
"@rapidmx/react-shared": "^0.
|
|
42
|
+
"@rapidmx/react-shared": "^0.11.0",
|
|
43
43
|
"@tiptap/core": "^3.31.3",
|
|
44
44
|
"@tiptap/extension-highlight": "^3.31.3",
|
|
45
45
|
"@tiptap/extension-image": "^3.31.3",
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
6
|
-
import { HiOutlineXMark } from "react-icons/hi2";
|
|
7
|
-
import type { NewMailNotice } from "../../mail/newMailNotifications.js";
|
|
8
|
-
import { TOAST_DURATION_MS } from "../../mail/useNewMailNotifications.js";
|
|
9
|
-
|
|
10
|
-
export interface NewMailToastsProps {
|
|
11
|
-
/** The pop-ups to show, oldest first. */
|
|
12
|
-
toasts: NewMailNotice[];
|
|
13
|
-
onDismiss: (uid: string) => void;
|
|
14
|
-
/** Show the "Turn on desktop notifications" offer in the first pop-up. */
|
|
15
|
-
offerDesktop: boolean;
|
|
16
|
-
onEnableDesktop: () => void;
|
|
17
|
-
onDeclineDesktop: () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Whether the tab is out of view - a pop-up's clock stops meanwhile, so it is still there when the reader comes back. */
|
|
21
|
-
function useTabHidden(): boolean {
|
|
22
|
-
const [hidden, setHidden] = useState(false);
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const update = () => setHidden(document.visibilityState === "hidden");
|
|
25
|
-
update();
|
|
26
|
-
document.addEventListener("visibilitychange", update);
|
|
27
|
-
return () => document.removeEventListener("visibilitychange", update);
|
|
28
|
-
}, []);
|
|
29
|
-
return hidden;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface ToastItemProps {
|
|
33
|
-
toast: NewMailNotice;
|
|
34
|
-
tabHidden: boolean;
|
|
35
|
-
onDismiss: (uid: string) => void;
|
|
36
|
-
offer?: { onEnable: () => void; onDecline: () => void };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** One pop-up. Its 8 seconds only run while it is neither hovered nor holding keyboard focus, and the tab is in view. */
|
|
40
|
-
function ToastItem({ toast, tabHidden, onDismiss, offer }: ToastItemProps) {
|
|
41
|
-
const [hovered, setHovered] = useState(false);
|
|
42
|
-
const [focused, setFocused] = useState(false);
|
|
43
|
-
const remainingRef = useRef(TOAST_DURATION_MS);
|
|
44
|
-
const paused = tabHidden || hovered || focused;
|
|
45
|
-
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (paused) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const startedAt = Date.now();
|
|
51
|
-
const timer = setTimeout(() => onDismiss(toast.uid), remainingRef.current);
|
|
52
|
-
return () => {
|
|
53
|
-
clearTimeout(timer);
|
|
54
|
-
remainingRef.current = Math.max(0, remainingRef.current - (Date.now() - startedAt));
|
|
55
|
-
};
|
|
56
|
-
}, [paused, toast.uid, onDismiss]);
|
|
57
|
-
|
|
58
|
-
const sender = toast.senderName || toast.senderAddress;
|
|
59
|
-
return (
|
|
60
|
-
<div
|
|
61
|
-
data-testid="new-mail-toast"
|
|
62
|
-
onMouseEnter={() => setHovered(true)}
|
|
63
|
-
onMouseLeave={() => setHovered(false)}
|
|
64
|
-
onFocus={() => setFocused(true)}
|
|
65
|
-
onBlur={(event) => {
|
|
66
|
-
// Focus moving between this pop-up's own controls is not leaving it.
|
|
67
|
-
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
68
|
-
setFocused(false);
|
|
69
|
-
}
|
|
70
|
-
}}
|
|
71
|
-
className="rr-toast-in pointer-events-auto relative rounded-md border border-border border-l-4 border-l-primary bg-surface text-text shadow-modal"
|
|
72
|
-
>
|
|
73
|
-
<a
|
|
74
|
-
href={toast.href}
|
|
75
|
-
onClick={() => onDismiss(toast.uid)}
|
|
76
|
-
className="block rounded-md p-3 pr-10 hover:bg-surface-alt focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary"
|
|
77
|
-
>
|
|
78
|
-
<span className="block text-xs font-semibold uppercase tracking-wide text-primary-dark">New message</span>
|
|
79
|
-
<span className="mt-0.5 flex min-w-0 items-baseline gap-1.5 text-sm">
|
|
80
|
-
<span className="truncate font-bold">{sender}</span>
|
|
81
|
-
{toast.senderName && <span className="truncate text-xs text-text-muted"><{toast.senderAddress}></span>}
|
|
82
|
-
</span>
|
|
83
|
-
<span className="block truncate text-sm font-semibold">{toast.subject}</span>
|
|
84
|
-
{toast.preview && <span className="mt-0.5 block text-xs text-text-muted [overflow-wrap:anywhere]">{toast.preview}</span>}
|
|
85
|
-
</a>
|
|
86
|
-
<button
|
|
87
|
-
type="button"
|
|
88
|
-
onClick={() => onDismiss(toast.uid)}
|
|
89
|
-
aria-label={`Dismiss notification: ${toast.subject}`}
|
|
90
|
-
className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text focus-visible:outline-2 focus-visible:outline-primary"
|
|
91
|
-
>
|
|
92
|
-
<HiOutlineXMark size={16} aria-hidden="true" />
|
|
93
|
-
</button>
|
|
94
|
-
{offer && (
|
|
95
|
-
<div className="flex flex-wrap items-center gap-2 border-t border-border px-3 py-2 text-xs text-text-muted">
|
|
96
|
-
<span className="basis-full">Get a desktop notification when mail arrives while this tab is in the background.</span>
|
|
97
|
-
<button
|
|
98
|
-
type="button"
|
|
99
|
-
onClick={offer.onEnable}
|
|
100
|
-
className="rounded-sm bg-primary px-2.5 py-1 text-xs font-semibold text-text-on-primary hover:bg-primary-dark"
|
|
101
|
-
>
|
|
102
|
-
Turn on desktop notifications
|
|
103
|
-
</button>
|
|
104
|
-
<button type="button" onClick={offer.onDecline} className="rounded-sm px-2 py-1 text-xs font-medium hover:bg-surface-alt hover:text-text">
|
|
105
|
-
Not now
|
|
106
|
-
</button>
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* The new-mail pop-ups, stacked at the top right (`fixed`, above everything). The container is always rendered - a live region
|
|
115
|
-
* has to exist before its content changes to be announced - and is a polite `status` region, so an arrival is read out without
|
|
116
|
-
* interrupting. Each pop-up shows the sender's name and address, the subject and a short preview, as text; clicking it opens the
|
|
117
|
-
* message; it has a dismiss button and goes by itself after a few seconds (see `ToastItem`). Motion is CSS-only and switched off
|
|
118
|
-
* by `prefers-reduced-motion` (`.rr-toast-in` in `app.css`).
|
|
119
|
-
*/
|
|
120
|
-
export default function NewMailToasts({ toasts, onDismiss, offerDesktop, onEnableDesktop, onDeclineDesktop }: NewMailToastsProps) {
|
|
121
|
-
const tabHidden = useTabHidden();
|
|
122
|
-
return (
|
|
123
|
-
<div
|
|
124
|
-
role="status"
|
|
125
|
-
aria-live="polite"
|
|
126
|
-
aria-label="New mail"
|
|
127
|
-
data-testid="new-mail-toasts"
|
|
128
|
-
className="pointer-events-none fixed right-4 top-4 z-50 flex w-[22rem] max-w-[calc(100vw-2rem)] flex-col gap-2"
|
|
129
|
-
>
|
|
130
|
-
{toasts.map((toast, index) => (
|
|
131
|
-
<ToastItem
|
|
132
|
-
key={toast.uid}
|
|
133
|
-
toast={toast}
|
|
134
|
-
tabHidden={tabHidden}
|
|
135
|
-
onDismiss={onDismiss}
|
|
136
|
-
offer={offerDesktop && index === 0 ? { onEnable: onEnableDesktop, onDecline: onDeclineDesktop } : undefined}
|
|
137
|
-
/>
|
|
138
|
-
))}
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React from "react";
|
|
6
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
7
|
-
import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
|
|
8
|
-
|
|
9
|
-
export interface SendFailureAlertProps {
|
|
10
|
-
/** The plain, human message - always shown. */
|
|
11
|
-
message: string;
|
|
12
|
-
/** The technical facts behind it (`describeSendFailure().lines`): per-recipient SMTP results, a transport error.
|
|
13
|
-
* With none, only the message is shown. */
|
|
14
|
-
lines: string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The banner a compose window shows when a send (or scheduled send) fails: the plain message, prominent, and - when the
|
|
19
|
-
* server said more - a collapsed "Technical details" block of monospace lines, one per fact, that can be selected or
|
|
20
|
-
* copied in one go to send to whoever runs the server. Collapsed, because most people only need the message.
|
|
21
|
-
*/
|
|
22
|
-
export default function SendFailureAlert({ message, lines }: SendFailureAlertProps) {
|
|
23
|
-
return (
|
|
24
|
-
<Alert>
|
|
25
|
-
<div className="flex-1 min-w-0">
|
|
26
|
-
<p className="font-semibold break-words">{message}</p>
|
|
27
|
-
{lines.length > 0 && (
|
|
28
|
-
<details className="mt-2">
|
|
29
|
-
<summary className="cursor-pointer text-xs font-semibold">Technical details</summary>
|
|
30
|
-
<ul
|
|
31
|
-
aria-label="Technical details"
|
|
32
|
-
className="mt-2 max-h-40 overflow-auto rounded-sm border border-border bg-surface p-2 text-xs font-mono text-text select-text"
|
|
33
|
-
>
|
|
34
|
-
{lines.map((line, index) => (
|
|
35
|
-
<li key={index} className="whitespace-pre-wrap break-words">
|
|
36
|
-
{line}
|
|
37
|
-
</li>
|
|
38
|
-
))}
|
|
39
|
-
</ul>
|
|
40
|
-
<div className="mt-2">
|
|
41
|
-
<CopyButton value={lines.join("\n")} label="Copy technical details" />
|
|
42
|
-
</div>
|
|
43
|
-
</details>
|
|
44
|
-
)}
|
|
45
|
-
</div>
|
|
46
|
-
</Alert>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { NewMailNotice } from "../../mail/newMailNotifications.js";
|
|
3
|
-
export interface NewMailToastsProps {
|
|
4
|
-
/** The pop-ups to show, oldest first. */
|
|
5
|
-
toasts: NewMailNotice[];
|
|
6
|
-
onDismiss: (uid: string) => void;
|
|
7
|
-
/** Show the "Turn on desktop notifications" offer in the first pop-up. */
|
|
8
|
-
offerDesktop: boolean;
|
|
9
|
-
onEnableDesktop: () => void;
|
|
10
|
-
onDeclineDesktop: () => void;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The new-mail pop-ups, stacked at the top right (`fixed`, above everything). The container is always rendered - a live region
|
|
14
|
-
* has to exist before its content changes to be announced - and is a polite `status` region, so an arrival is read out without
|
|
15
|
-
* interrupting. Each pop-up shows the sender's name and address, the subject and a short preview, as text; clicking it opens the
|
|
16
|
-
* message; it has a dismiss button and goes by itself after a few seconds (see `ToastItem`). Motion is CSS-only and switched off
|
|
17
|
-
* by `prefers-reduced-motion` (`.rr-toast-in` in `app.css`).
|
|
18
|
-
*/
|
|
19
|
-
export default function NewMailToasts({ toasts, onDismiss, offerDesktop, onEnableDesktop, onDeclineDesktop }: NewMailToastsProps): React.JSX.Element;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
5
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
-
import { useEffect, useRef, useState } from "react";
|
|
7
|
-
import { HiOutlineXMark } from "react-icons/hi2";
|
|
8
|
-
import { TOAST_DURATION_MS } from "../../mail/useNewMailNotifications.js";
|
|
9
|
-
/** Whether the tab is out of view - a pop-up's clock stops meanwhile, so it is still there when the reader comes back. */
|
|
10
|
-
function useTabHidden() {
|
|
11
|
-
const [hidden, setHidden] = useState(false);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const update = () => setHidden(document.visibilityState === "hidden");
|
|
14
|
-
update();
|
|
15
|
-
document.addEventListener("visibilitychange", update);
|
|
16
|
-
return () => document.removeEventListener("visibilitychange", update);
|
|
17
|
-
}, []);
|
|
18
|
-
return hidden;
|
|
19
|
-
}
|
|
20
|
-
/** One pop-up. Its 8 seconds only run while it is neither hovered nor holding keyboard focus, and the tab is in view. */
|
|
21
|
-
function ToastItem({ toast, tabHidden, onDismiss, offer }) {
|
|
22
|
-
const [hovered, setHovered] = useState(false);
|
|
23
|
-
const [focused, setFocused] = useState(false);
|
|
24
|
-
const remainingRef = useRef(TOAST_DURATION_MS);
|
|
25
|
-
const paused = tabHidden || hovered || focused;
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (paused) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const startedAt = Date.now();
|
|
31
|
-
const timer = setTimeout(() => onDismiss(toast.uid), remainingRef.current);
|
|
32
|
-
return () => {
|
|
33
|
-
clearTimeout(timer);
|
|
34
|
-
remainingRef.current = Math.max(0, remainingRef.current - (Date.now() - startedAt));
|
|
35
|
-
};
|
|
36
|
-
}, [paused, toast.uid, onDismiss]);
|
|
37
|
-
const sender = toast.senderName || toast.senderAddress;
|
|
38
|
-
return (_jsxs("div", { "data-testid": "new-mail-toast", onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), onFocus: () => setFocused(true), onBlur: (event) => {
|
|
39
|
-
// Focus moving between this pop-up's own controls is not leaving it.
|
|
40
|
-
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
41
|
-
setFocused(false);
|
|
42
|
-
}
|
|
43
|
-
}, className: "rr-toast-in pointer-events-auto relative rounded-md border border-border border-l-4 border-l-primary bg-surface text-text shadow-modal", children: [_jsxs("a", { href: toast.href, onClick: () => onDismiss(toast.uid), className: "block rounded-md p-3 pr-10 hover:bg-surface-alt focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary", children: [_jsx("span", { className: "block text-xs font-semibold uppercase tracking-wide text-primary-dark", children: "New message" }), _jsxs("span", { className: "mt-0.5 flex min-w-0 items-baseline gap-1.5 text-sm", children: [_jsx("span", { className: "truncate font-bold", children: sender }), toast.senderName && _jsxs("span", { className: "truncate text-xs text-text-muted", children: ["<", toast.senderAddress, ">"] })] }), _jsx("span", { className: "block truncate text-sm font-semibold", children: toast.subject }), toast.preview && _jsx("span", { className: "mt-0.5 block text-xs text-text-muted [overflow-wrap:anywhere]", children: toast.preview })] }), _jsx("button", { type: "button", onClick: () => onDismiss(toast.uid), "aria-label": `Dismiss notification: ${toast.subject}`, className: "absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text focus-visible:outline-2 focus-visible:outline-primary", children: _jsx(HiOutlineXMark, { size: 16, "aria-hidden": "true" }) }), offer && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t border-border px-3 py-2 text-xs text-text-muted", children: [_jsx("span", { className: "basis-full", children: "Get a desktop notification when mail arrives while this tab is in the background." }), _jsx("button", { type: "button", onClick: offer.onEnable, className: "rounded-sm bg-primary px-2.5 py-1 text-xs font-semibold text-text-on-primary hover:bg-primary-dark", children: "Turn on desktop notifications" }), _jsx("button", { type: "button", onClick: offer.onDecline, className: "rounded-sm px-2 py-1 text-xs font-medium hover:bg-surface-alt hover:text-text", children: "Not now" })] }))] }));
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* The new-mail pop-ups, stacked at the top right (`fixed`, above everything). The container is always rendered - a live region
|
|
47
|
-
* has to exist before its content changes to be announced - and is a polite `status` region, so an arrival is read out without
|
|
48
|
-
* interrupting. Each pop-up shows the sender's name and address, the subject and a short preview, as text; clicking it opens the
|
|
49
|
-
* message; it has a dismiss button and goes by itself after a few seconds (see `ToastItem`). Motion is CSS-only and switched off
|
|
50
|
-
* by `prefers-reduced-motion` (`.rr-toast-in` in `app.css`).
|
|
51
|
-
*/
|
|
52
|
-
export default function NewMailToasts({ toasts, onDismiss, offerDesktop, onEnableDesktop, onDeclineDesktop }) {
|
|
53
|
-
const tabHidden = useTabHidden();
|
|
54
|
-
return (_jsx("div", { role: "status", "aria-live": "polite", "aria-label": "New mail", "data-testid": "new-mail-toasts", className: "pointer-events-none fixed right-4 top-4 z-50 flex w-[22rem] max-w-[calc(100vw-2rem)] flex-col gap-2", children: toasts.map((toast, index) => (_jsx(ToastItem, { toast: toast, tabHidden: tabHidden, onDismiss: onDismiss, offer: offerDesktop && index === 0 ? { onEnable: onEnableDesktop, onDecline: onDeclineDesktop } : undefined }, toast.uid))) }));
|
|
55
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
export interface SendFailureAlertProps {
|
|
3
|
-
/** The plain, human message - always shown. */
|
|
4
|
-
message: string;
|
|
5
|
-
/** The technical facts behind it (`describeSendFailure().lines`): per-recipient SMTP results, a transport error.
|
|
6
|
-
* With none, only the message is shown. */
|
|
7
|
-
lines: string[];
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* The banner a compose window shows when a send (or scheduled send) fails: the plain message, prominent, and - when the
|
|
11
|
-
* server said more - a collapsed "Technical details" block of monospace lines, one per fact, that can be selected or
|
|
12
|
-
* copied in one go to send to whoever runs the server. Collapsed, because most people only need the message.
|
|
13
|
-
*/
|
|
14
|
-
export default function SendFailureAlert({ message, lines }: SendFailureAlertProps): React.JSX.Element;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
3
|
-
import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
|
|
4
|
-
/**
|
|
5
|
-
* The banner a compose window shows when a send (or scheduled send) fails: the plain message, prominent, and - when the
|
|
6
|
-
* server said more - a collapsed "Technical details" block of monospace lines, one per fact, that can be selected or
|
|
7
|
-
* copied in one go to send to whoever runs the server. Collapsed, because most people only need the message.
|
|
8
|
-
*/
|
|
9
|
-
export default function SendFailureAlert({ message, lines }) {
|
|
10
|
-
return (_jsx(Alert, { children: _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "font-semibold break-words", children: message }), lines.length > 0 && (_jsxs("details", { className: "mt-2", children: [_jsx("summary", { className: "cursor-pointer text-xs font-semibold", children: "Technical details" }), _jsx("ul", { "aria-label": "Technical details", className: "mt-2 max-h-40 overflow-auto rounded-sm border border-border bg-surface p-2 text-xs font-mono text-text select-text", children: lines.map((line, index) => (_jsx("li", { className: "whitespace-pre-wrap break-words", children: line }, index))) }), _jsx("div", { className: "mt-2", children: _jsx(CopyButton, { value: lines.join("\n"), label: "Copy technical details" }) })] }))] }) }));
|
|
11
|
-
}
|