@rapidmx/web-client 0.10.0 → 0.12.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.
Files changed (324) hide show
  1. package/README.md +183 -2
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/mailboxes/new/index.tsx +3 -3
  7. package/apps/admin/quarantine/index.tsx +2 -1
  8. package/apps/admin/signing-certificates/index.tsx +343 -0
  9. package/apps/escrow/_layout.tsx +8 -2
  10. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  11. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  12. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  13. package/apps/shared/appearance/ColorField.tsx +107 -0
  14. package/apps/shared/appearance/appearanceCache.ts +106 -0
  15. package/apps/shared/appearance/appearanceContext.ts +77 -0
  16. package/apps/shared/appearance/bootScript.ts +35 -0
  17. package/apps/shared/appearance/color.ts +169 -0
  18. package/apps/shared/appearance/photo.ts +88 -0
  19. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  20. package/apps/shared/appearance/theme.ts +355 -0
  21. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +4 -4
  22. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  23. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  24. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  25. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +60 -14
  26. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +65 -16
  27. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  28. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  29. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  30. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  31. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  32. package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
  33. package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
  34. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  35. package/apps/shared/components/layout/AppShell.tsx +98 -32
  36. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  37. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  38. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  39. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  40. package/apps/shared/components/layout/UserMenu.tsx +75 -6
  41. package/apps/shared/components/mail/ConversationList.tsx +4 -2
  42. package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
  43. package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
  44. package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
  45. package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
  46. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  47. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  48. package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
  49. package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
  50. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  51. package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
  52. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  53. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  54. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  55. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  56. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  57. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  58. package/apps/shared/components/mail/reading/color.ts +245 -0
  59. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  60. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  61. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  62. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  63. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  64. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  65. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  66. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  67. package/apps/shared/components/sharing/PrincipalListField.tsx +82 -0
  68. package/apps/shared/components/sharing/PrincipalPicker.tsx +76 -0
  69. package/apps/shared/components/sharing/PrincipalResolver.tsx +164 -0
  70. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  71. package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
  72. package/apps/shared/mail/folderCounts.ts +9 -3
  73. package/apps/shared/mail/folderTree.ts +143 -0
  74. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  75. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  76. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  77. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  78. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  79. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  80. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  81. package/apps/shared/mail/outbox/sendState.ts +35 -0
  82. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  83. package/apps/shared/mail/useMailConnection.ts +81 -28
  84. package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
  85. package/apps/shared/mail/useNewMailNotifications.ts +47 -32
  86. package/apps/shared/navigation/AppRouter.tsx +2 -2
  87. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  88. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  89. package/apps/shared/notifications/apiErrors.ts +98 -0
  90. package/apps/shared/notifications/headerOffset.ts +35 -0
  91. package/apps/shared/notifications/pushStatus.ts +56 -0
  92. package/apps/shared/notifications/store.ts +549 -0
  93. package/apps/shared/notifications/systemErrors.ts +63 -0
  94. package/apps/shared/notifications/useNotifications.ts +41 -0
  95. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  96. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  97. package/apps/shared/signing/enrollmentView.ts +251 -0
  98. package/apps/shared/signing/signingInfo.ts +58 -0
  99. package/apps/shared/signing/useNow.ts +19 -0
  100. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  101. package/apps/shared/styles/app.css +80 -0
  102. package/apps/www/_layout.tsx +8 -2
  103. package/apps/www/_routes.ts +1 -0
  104. package/apps/www/calendar/index.tsx +9 -14
  105. package/apps/www/contacts/[uid].tsx +5 -8
  106. package/apps/www/contacts/index.tsx +587 -584
  107. package/apps/www/index.tsx +38 -27
  108. package/apps/www/messages/[uid].tsx +3 -1
  109. package/apps/www/settings/appearance/index.tsx +21 -0
  110. package/apps/www/settings/auto-reply/index.tsx +3 -7
  111. package/apps/www/settings/encryption/index.tsx +151 -116
  112. package/apps/www/settings/filters/[uid].tsx +3 -1
  113. package/apps/www/settings/filters/new/index.tsx +3 -1
  114. package/apps/www/settings/privacy/index.tsx +3 -8
  115. package/apps/www/settings/read-receipts/index.tsx +3 -7
  116. package/apps/www/settings/sharing/index.tsx +44 -65
  117. package/apps/www/settings/signatures/[uid].tsx +3 -1
  118. package/apps/www/settings/signatures/new/index.tsx +3 -2
  119. package/apps/www/tasks/index.tsx +11 -16
  120. package/dist/apps/admin/_layout.d.ts +5 -1
  121. package/dist/apps/admin/_layout.js +3 -2
  122. package/dist/apps/admin/index.js +3 -2
  123. package/dist/apps/admin/ingest-queue/index.js +2 -1
  124. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  125. package/dist/apps/admin/mailboxes/new/index.js +1 -1
  126. package/dist/apps/admin/quarantine/index.js +2 -1
  127. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  128. package/dist/apps/admin/signing-certificates/index.js +161 -0
  129. package/dist/apps/escrow/_layout.d.ts +5 -1
  130. package/dist/apps/escrow/_layout.js +3 -2
  131. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  132. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  133. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  134. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  135. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  136. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  137. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  138. package/dist/apps/shared/appearance/ColorField.js +40 -0
  139. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  140. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  141. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  142. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  143. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  144. package/dist/apps/shared/appearance/bootScript.js +33 -0
  145. package/dist/apps/shared/appearance/color.d.ts +65 -0
  146. package/dist/apps/shared/appearance/color.js +132 -0
  147. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  148. package/dist/apps/shared/appearance/photo.js +79 -0
  149. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  150. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  151. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  152. package/dist/apps/shared/appearance/theme.js +251 -0
  153. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -2
  154. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  155. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  156. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  157. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  158. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  159. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  160. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +25 -5
  161. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +18 -7
  162. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  163. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  164. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  165. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  166. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  167. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  168. package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
  169. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
  170. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  171. package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
  172. package/dist/apps/shared/components/layout/AppShell.js +59 -13
  173. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  174. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  175. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  176. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  177. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  178. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  179. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  180. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  181. package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
  182. package/dist/apps/shared/components/layout/UserMenu.js +41 -7
  183. package/dist/apps/shared/components/mail/ConversationList.js +3 -2
  184. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
  185. package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
  186. package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
  187. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
  188. package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
  189. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
  190. package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
  191. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  192. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  193. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  194. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  195. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
  196. package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
  197. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
  198. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
  199. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  200. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  201. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
  202. package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
  203. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  204. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  205. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  206. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  207. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  208. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  209. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  210. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  211. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  212. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  213. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  214. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  215. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  216. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  217. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  218. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  219. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  220. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  221. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  222. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  223. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  224. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  225. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  226. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  227. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  228. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  229. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  230. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  231. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  232. package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +24 -0
  233. package/dist/apps/shared/components/sharing/PrincipalListField.js +23 -0
  234. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +28 -0
  235. package/dist/apps/shared/components/sharing/PrincipalPicker.js +24 -0
  236. package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +44 -0
  237. package/dist/apps/shared/components/sharing/PrincipalResolver.js +92 -0
  238. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  239. package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
  240. package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
  241. package/dist/apps/shared/mail/folderCounts.js +9 -3
  242. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  243. package/dist/apps/shared/mail/folderTree.js +116 -0
  244. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  245. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  246. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  247. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  248. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  249. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  250. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  251. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  252. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  253. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  254. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  255. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  256. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  257. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  258. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  259. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  260. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  261. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  262. package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
  263. package/dist/apps/shared/mail/useMailConnection.js +64 -22
  264. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
  265. package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
  266. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
  267. package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
  268. package/dist/apps/shared/navigation/AppRouter.js +2 -2
  269. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  270. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  271. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  272. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  273. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  274. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  275. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  276. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  277. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  278. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  279. package/dist/apps/shared/notifications/store.d.ts +136 -0
  280. package/dist/apps/shared/notifications/store.js +416 -0
  281. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  282. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  283. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  284. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  285. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  286. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  287. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  288. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  289. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  290. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  291. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  292. package/dist/apps/shared/signing/signingInfo.js +48 -0
  293. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  294. package/dist/apps/shared/signing/useNow.js +18 -0
  295. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  296. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  297. package/dist/apps/shared/styles/app.css +80 -0
  298. package/dist/apps/www/_layout.d.ts +5 -1
  299. package/dist/apps/www/_layout.js +3 -2
  300. package/dist/apps/www/_routes.js +1 -0
  301. package/dist/apps/www/calendar/index.js +3 -4
  302. package/dist/apps/www/contacts/[uid].js +5 -7
  303. package/dist/apps/www/contacts/index.js +14 -21
  304. package/dist/apps/www/index.js +37 -19
  305. package/dist/apps/www/messages/[uid].js +3 -1
  306. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  307. package/dist/apps/www/settings/appearance/index.js +13 -0
  308. package/dist/apps/www/settings/auto-reply/index.js +3 -6
  309. package/dist/apps/www/settings/encryption/index.js +103 -95
  310. package/dist/apps/www/settings/filters/[uid].js +3 -1
  311. package/dist/apps/www/settings/filters/new/index.js +3 -1
  312. package/dist/apps/www/settings/privacy/index.js +5 -8
  313. package/dist/apps/www/settings/read-receipts/index.js +3 -6
  314. package/dist/apps/www/settings/sharing/index.js +19 -33
  315. package/dist/apps/www/settings/signatures/[uid].js +3 -1
  316. package/dist/apps/www/settings/signatures/new/index.js +3 -2
  317. package/dist/apps/www/tasks/index.js +8 -14
  318. package/package.json +2 -2
  319. package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
  320. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  321. package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
  322. package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
  323. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  324. 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
- setError(err instanceof ApiRequestError ? err.message : "Could not save this signature.");
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
- setError(err instanceof ApiRequestError ? err.message : "Could not create the signature.");
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
- setError(err instanceof ApiRequestError ? err.message : "Could not update this task.");
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
- setError(err instanceof ApiRequestError ? err.message : "Could not delete this task.");
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
- bulkError = err instanceof ApiRequestError ? err.message : "Could not update one or more tasks.";
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
- bulkError = err instanceof ApiRequestError ? err.message : "Could not update one or more tasks.";
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
- bulkError = err instanceof ApiRequestError ? err.message : "Could not delete one or more tasks.";
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 flex min-h-0", 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 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} 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`,
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.10.0",
3
+ "version": "0.12.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.10.0",
42
+ "@rapidmx/react-shared": "^0.12.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">&lt;{toast.senderAddress}&gt;</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
- }