@rapidmx/web-client 0.15.1 → 0.17.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 (318) hide show
  1. package/README.md +381 -373
  2. package/apps/admin/branding/index.tsx +39 -39
  3. package/apps/admin/data-requests/index.tsx +482 -480
  4. package/apps/admin/distribution-lists/new/index.tsx +3 -1
  5. package/apps/admin/domains/[uid].tsx +166 -164
  6. package/apps/admin/domains/new/index.tsx +3 -1
  7. package/apps/admin/escrow-scopes/[uid].tsx +350 -348
  8. package/apps/admin/escrow-scopes/new/index.tsx +3 -1
  9. package/apps/admin/index.tsx +129 -124
  10. package/apps/admin/mailboxes/[uid].tsx +271 -224
  11. package/apps/admin/mailboxes/new/index.tsx +30 -28
  12. package/apps/admin/plugins/index.tsx +15 -15
  13. package/apps/admin/retention-policy/index.tsx +39 -39
  14. package/apps/admin/signing-certificates/index.tsx +343 -343
  15. package/apps/admin/transport-rules/new/index.tsx +3 -1
  16. package/apps/escrow/audit-log/index.tsx +196 -196
  17. package/apps/escrow/matters/[uid].tsx +621 -621
  18. package/apps/escrow/matters/new/index.tsx +3 -1
  19. package/apps/shared/auth/adminAccess.ts +99 -99
  20. package/apps/shared/components/admin/layout/AdminShell.tsx +381 -378
  21. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
  22. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
  23. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
  24. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  25. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  26. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +50 -3
  27. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  28. package/apps/shared/components/admin/settings/PluginsManager.tsx +1545 -1545
  29. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  30. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  31. package/apps/shared/components/admin/setup/SetupWizard.tsx +446 -444
  32. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  33. package/apps/shared/components/calendar/EventEditor.tsx +5 -10
  34. package/apps/shared/components/calendar/EventFormParts.tsx +16 -47
  35. package/apps/shared/components/calendar/EventModal.tsx +206 -206
  36. package/apps/shared/components/calendar/GuestInput.tsx +100 -0
  37. package/apps/shared/components/calendar/MonthView.tsx +185 -185
  38. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
  39. package/apps/shared/components/calendar/RequestChangeForm.tsx +23 -54
  40. package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
  41. package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
  42. package/apps/shared/components/calendar/allDay.ts +124 -124
  43. package/apps/shared/components/calendar/eventForm.ts +2 -2
  44. package/apps/shared/components/calendar/eventFormat.ts +49 -22
  45. package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
  46. package/apps/shared/components/contacts/ContactForm.tsx +383 -383
  47. package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
  48. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
  49. package/apps/shared/components/contacts/layout/ContactsShell.tsx +218 -216
  50. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  51. package/apps/shared/components/layout/AppShell.tsx +461 -480
  52. package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
  53. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  54. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
  55. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  56. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  57. package/apps/shared/components/layout/UserMenu.tsx +439 -424
  58. package/apps/shared/components/mail/ConversationList.tsx +323 -323
  59. package/apps/shared/components/mail/ConversationThreadPane.tsx +613 -491
  60. package/apps/shared/components/mail/EmptyFolderBar.tsx +41 -0
  61. package/apps/shared/components/mail/MailSelectionBar.tsx +240 -227
  62. package/apps/shared/components/mail/MenuButton.tsx +31 -11
  63. package/apps/shared/components/mail/MessageDetailPane.tsx +1757 -1559
  64. package/apps/shared/components/mail/PermanentDeleteDialog.tsx +47 -0
  65. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
  66. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  67. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1677
  68. package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
  69. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  70. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  71. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  72. package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
  73. package/apps/shared/components/mail/layout/MailShell.tsx +161 -134
  74. package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
  75. package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -0
  76. package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -0
  77. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
  78. package/apps/shared/components/mail/reading/messageExport.ts +59 -0
  79. package/apps/shared/components/mail/reading/printMessage.ts +99 -0
  80. package/apps/shared/components/mail/reading/useMessageActions.ts +443 -0
  81. package/apps/shared/components/mail/verificationSeals.ts +125 -125
  82. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  83. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  84. package/apps/shared/components/settings/layout/SettingsShell.tsx +257 -254
  85. package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
  86. package/apps/shared/components/tasks/layout/TasksShell.tsx +219 -217
  87. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  88. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  89. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  90. package/apps/shared/keyboard/dispatch.ts +124 -124
  91. package/apps/shared/keyboard/format.ts +89 -89
  92. package/apps/shared/keyboard/keymap.ts +114 -114
  93. package/apps/shared/keyboard/registry.ts +65 -65
  94. package/apps/shared/keyboard/targets.ts +79 -79
  95. package/apps/shared/mail/folderCounts.ts +17 -0
  96. package/apps/shared/mail/folderOfType.ts +49 -0
  97. package/apps/shared/mail/folderTree.ts +143 -143
  98. package/apps/shared/mail/listAllPages.ts +39 -39
  99. package/apps/shared/mail/newMailNotifications.ts +171 -171
  100. package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
  101. package/apps/shared/mail/outbox/sendJob.ts +19 -1
  102. package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
  103. package/apps/shared/mail/permanentDelete.ts +160 -0
  104. package/apps/shared/mail/primaryMailbox.ts +71 -0
  105. package/apps/shared/mail/reportNotices.ts +66 -0
  106. package/apps/shared/mail/senderBlocking.ts +141 -0
  107. package/apps/shared/mail/useCollapsedSections.ts +146 -0
  108. package/apps/shared/mail/useMailConnection.ts +205 -203
  109. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  110. package/apps/shared/mail/useMailboxUpdateAccess.ts +60 -0
  111. package/apps/shared/mail/useMarkMessageRead.ts +47 -47
  112. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  113. package/apps/shared/mail/usePermanentDelete.tsx +160 -0
  114. package/apps/shared/navigation/frameContext.tsx +1 -1
  115. package/apps/shared/navigation/index.ts +43 -0
  116. package/apps/shared/navigation/pageTitle.ts +19 -0
  117. package/apps/shared/notifications/store.ts +560 -560
  118. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
  119. package/apps/shared/search/crossMailboxSearch.ts +169 -0
  120. package/apps/shared/search/localIndexBuilder.ts +481 -481
  121. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  122. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  123. package/apps/shared/signing/enrollmentView.ts +251 -251
  124. package/apps/shared/signing/useNow.ts +19 -19
  125. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
  126. package/apps/shared/styles/app.css +396 -386
  127. package/apps/www/_shell.tsx +82 -0
  128. package/apps/www/calendar/index.tsx +581 -571
  129. package/apps/www/contacts/[uid].tsx +112 -109
  130. package/apps/www/contacts/index.tsx +14 -3
  131. package/apps/www/index.tsx +2962 -2479
  132. package/apps/www/messages/[uid].tsx +139 -135
  133. package/apps/www/settings/appearance/index.tsx +5 -2
  134. package/apps/www/settings/auto-reply/index.tsx +136 -133
  135. package/apps/www/settings/blocked-senders/index.tsx +303 -0
  136. package/apps/www/settings/encryption/index.tsx +1290 -1287
  137. package/apps/www/settings/filters/[uid].tsx +179 -176
  138. package/apps/www/settings/filters/index.tsx +105 -102
  139. package/apps/www/settings/filters/new/index.tsx +165 -140
  140. package/apps/www/settings/labels/index.tsx +207 -204
  141. package/apps/www/settings/privacy/index.tsx +495 -492
  142. package/apps/www/settings/profile/index.tsx +251 -248
  143. package/apps/www/settings/read-receipts/index.tsx +150 -147
  144. package/apps/www/settings/sharing/index.tsx +259 -256
  145. package/apps/www/settings/signatures/[uid].tsx +175 -172
  146. package/apps/www/settings/signatures/index.tsx +91 -88
  147. package/apps/www/settings/signatures/new/index.tsx +138 -135
  148. package/apps/www/tasks/index.tsx +654 -649
  149. package/dist/apps/admin/data-requests/index.js +1 -1
  150. package/dist/apps/admin/distribution-lists/new/index.js +3 -1
  151. package/dist/apps/admin/domains/[uid].js +3 -1
  152. package/dist/apps/admin/domains/new/index.js +3 -1
  153. package/dist/apps/admin/escrow-scopes/[uid].js +3 -1
  154. package/dist/apps/admin/escrow-scopes/new/index.js +3 -1
  155. package/dist/apps/admin/index.js +5 -2
  156. package/dist/apps/admin/mailboxes/[uid].js +15 -3
  157. package/dist/apps/admin/mailboxes/new/index.js +3 -1
  158. package/dist/apps/admin/transport-rules/new/index.js +3 -1
  159. package/dist/apps/escrow/matters/new/index.js +3 -1
  160. package/dist/apps/shared/components/admin/layout/AdminShell.js +6 -3
  161. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
  162. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
  163. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
  164. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
  165. package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
  166. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +25 -3
  167. package/dist/apps/shared/components/admin/setup/SetupWizard.js +3 -1
  168. package/dist/apps/shared/components/calendar/EventEditor.js +5 -10
  169. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -2
  170. package/dist/apps/shared/components/calendar/EventFormParts.js +5 -16
  171. package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
  172. package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
  173. package/dist/apps/shared/components/calendar/RequestChangeForm.js +11 -22
  174. package/dist/apps/shared/components/calendar/eventForm.d.ts +2 -2
  175. package/dist/apps/shared/components/calendar/eventFormat.d.ts +21 -8
  176. package/dist/apps/shared/components/calendar/eventFormat.js +50 -23
  177. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +5 -4
  178. package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
  179. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  180. package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
  181. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +7 -5
  182. package/dist/apps/shared/components/layout/AppShell.d.ts +8 -11
  183. package/dist/apps/shared/components/layout/AppShell.js +24 -33
  184. package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
  185. package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
  186. package/dist/apps/shared/components/layout/UserMenu.d.ts +4 -1
  187. package/dist/apps/shared/components/layout/UserMenu.js +6 -5
  188. package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
  189. package/dist/apps/shared/components/mail/EmptyFolderBar.d.ts +18 -0
  190. package/dist/apps/shared/components/mail/EmptyFolderBar.js +10 -0
  191. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +14 -4
  192. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -13
  193. package/dist/apps/shared/components/mail/MenuButton.d.ts +9 -2
  194. package/dist/apps/shared/components/mail/MenuButton.js +15 -11
  195. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +5 -0
  196. package/dist/apps/shared/components/mail/MessageDetailPane.js +147 -7
  197. package/dist/apps/shared/components/mail/PermanentDeleteDialog.d.ts +19 -0
  198. package/dist/apps/shared/components/mail/PermanentDeleteDialog.js +11 -0
  199. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +8 -4
  200. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
  201. package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
  202. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
  203. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
  204. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +16 -7
  205. package/dist/apps/shared/components/mail/layout/MailShell.js +78 -65
  206. package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
  207. package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
  208. package/dist/apps/shared/components/mail/reading/MessageMoreMenu.d.ts +56 -0
  209. package/dist/apps/shared/components/mail/reading/MessageMoreMenu.js +153 -0
  210. package/dist/apps/shared/components/mail/reading/MessageSourceDialog.d.ts +27 -0
  211. package/dist/apps/shared/components/mail/reading/MessageSourceDialog.js +27 -0
  212. package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
  213. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
  214. package/dist/apps/shared/components/mail/reading/messageExport.d.ts +19 -0
  215. package/dist/apps/shared/components/mail/reading/messageExport.js +51 -0
  216. package/dist/apps/shared/components/mail/reading/printMessage.d.ts +30 -0
  217. package/dist/apps/shared/components/mail/reading/printMessage.js +70 -0
  218. package/dist/apps/shared/components/mail/reading/useMessageActions.d.ts +57 -0
  219. package/dist/apps/shared/components/mail/reading/useMessageActions.js +366 -0
  220. package/dist/apps/shared/components/settings/layout/SettingsShell.js +10 -7
  221. package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
  222. package/dist/apps/shared/components/tasks/layout/TasksShell.js +7 -5
  223. package/dist/apps/shared/keyboard/GlobalShortcuts.js +1 -1
  224. package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
  225. package/dist/apps/shared/mail/folderCounts.js +12 -0
  226. package/dist/apps/shared/mail/folderOfType.d.ts +13 -0
  227. package/dist/apps/shared/mail/folderOfType.js +40 -0
  228. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
  229. package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
  230. package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
  231. package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
  232. package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
  233. package/dist/apps/shared/mail/permanentDelete.d.ts +62 -0
  234. package/dist/apps/shared/mail/permanentDelete.js +127 -0
  235. package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
  236. package/dist/apps/shared/mail/primaryMailbox.js +61 -0
  237. package/dist/apps/shared/mail/reportNotices.d.ts +21 -0
  238. package/dist/apps/shared/mail/reportNotices.js +47 -0
  239. package/dist/apps/shared/mail/senderBlocking.d.ts +26 -0
  240. package/dist/apps/shared/mail/senderBlocking.js +92 -0
  241. package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
  242. package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
  243. package/dist/apps/shared/mail/useMailConnection.js +3 -1
  244. package/dist/apps/shared/mail/useMailboxUpdateAccess.d.ts +11 -0
  245. package/dist/apps/shared/mail/useMailboxUpdateAccess.js +52 -0
  246. package/dist/apps/shared/mail/usePermanentDelete.d.ts +35 -0
  247. package/dist/apps/shared/mail/usePermanentDelete.js +96 -0
  248. package/dist/apps/shared/navigation/frameContext.d.ts +1 -1
  249. package/dist/apps/shared/navigation/index.d.ts +16 -0
  250. package/dist/apps/shared/navigation/index.js +34 -0
  251. package/dist/apps/shared/navigation/pageTitle.d.ts +11 -0
  252. package/dist/apps/shared/navigation/pageTitle.js +12 -0
  253. package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
  254. package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
  255. package/dist/apps/shared/styles/app.css +396 -386
  256. package/dist/apps/www/_shell.d.ts +26 -0
  257. package/dist/apps/www/_shell.js +31 -0
  258. package/dist/apps/www/calendar/index.d.ts +7 -2
  259. package/dist/apps/www/calendar/index.js +7 -4
  260. package/dist/apps/www/contacts/[uid].d.ts +14 -3
  261. package/dist/apps/www/contacts/[uid].js +5 -3
  262. package/dist/apps/www/contacts/index.d.ts +7 -2
  263. package/dist/apps/www/contacts/index.js +8 -4
  264. package/dist/apps/www/index.d.ts +7 -2
  265. package/dist/apps/www/index.js +542 -207
  266. package/dist/apps/www/messages/[uid].d.ts +14 -3
  267. package/dist/apps/www/messages/[uid].js +7 -5
  268. package/dist/apps/www/settings/appearance/index.d.ts +8 -2
  269. package/dist/apps/www/settings/appearance/index.js +4 -2
  270. package/dist/apps/www/settings/auto-reply/index.d.ts +7 -2
  271. package/dist/apps/www/settings/auto-reply/index.js +4 -2
  272. package/dist/apps/www/settings/blocked-senders/index.d.ts +9 -0
  273. package/dist/apps/www/settings/blocked-senders/index.js +134 -0
  274. package/dist/apps/www/settings/encryption/index.d.ts +7 -2
  275. package/dist/apps/www/settings/encryption/index.js +4 -2
  276. package/dist/apps/www/settings/filters/[uid].d.ts +7 -2
  277. package/dist/apps/www/settings/filters/[uid].js +4 -2
  278. package/dist/apps/www/settings/filters/index.d.ts +7 -2
  279. package/dist/apps/www/settings/filters/index.js +4 -2
  280. package/dist/apps/www/settings/filters/new/index.d.ts +7 -2
  281. package/dist/apps/www/settings/filters/new/index.js +28 -5
  282. package/dist/apps/www/settings/labels/index.d.ts +7 -2
  283. package/dist/apps/www/settings/labels/index.js +4 -2
  284. package/dist/apps/www/settings/privacy/index.d.ts +7 -2
  285. package/dist/apps/www/settings/privacy/index.js +4 -2
  286. package/dist/apps/www/settings/profile/index.d.ts +7 -2
  287. package/dist/apps/www/settings/profile/index.js +4 -2
  288. package/dist/apps/www/settings/read-receipts/index.d.ts +7 -2
  289. package/dist/apps/www/settings/read-receipts/index.js +4 -2
  290. package/dist/apps/www/settings/sharing/index.d.ts +7 -2
  291. package/dist/apps/www/settings/sharing/index.js +4 -2
  292. package/dist/apps/www/settings/signatures/[uid].d.ts +7 -2
  293. package/dist/apps/www/settings/signatures/[uid].js +4 -2
  294. package/dist/apps/www/settings/signatures/index.d.ts +7 -2
  295. package/dist/apps/www/settings/signatures/index.js +4 -2
  296. package/dist/apps/www/settings/signatures/new/index.d.ts +7 -2
  297. package/dist/apps/www/settings/signatures/new/index.js +5 -3
  298. package/dist/apps/www/tasks/index.d.ts +7 -2
  299. package/dist/apps/www/tasks/index.js +5 -3
  300. package/package.json +4 -2
  301. package/apps/shared/navigation/AppRouter.tsx +0 -300
  302. package/apps/shared/navigation/idle.ts +0 -45
  303. package/apps/shared/navigation/routerContext.tsx +0 -83
  304. package/apps/shared/navigation/routes.ts +0 -72
  305. package/apps/www/_routedPage.tsx +0 -24
  306. package/apps/www/_routes.ts +0 -37
  307. package/dist/apps/shared/navigation/AppRouter.d.ts +0 -52
  308. package/dist/apps/shared/navigation/AppRouter.js +0 -242
  309. package/dist/apps/shared/navigation/idle.d.ts +0 -14
  310. package/dist/apps/shared/navigation/idle.js +0 -43
  311. package/dist/apps/shared/navigation/routerContext.d.ts +0 -37
  312. package/dist/apps/shared/navigation/routerContext.js +0 -56
  313. package/dist/apps/shared/navigation/routes.d.ts +0 -32
  314. package/dist/apps/shared/navigation/routes.js +0 -37
  315. package/dist/apps/www/_routedPage.d.ts +0 -12
  316. package/dist/apps/www/_routedPage.js +0 -19
  317. package/dist/apps/www/_routes.d.ts +0 -11
  318. package/dist/apps/www/_routes.js +0 -31
@@ -3,13 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
- import { createContext, useContext, useEffect, useMemo, useState } from "react";
6
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
7
7
  import { HiOutlineBars3, HiOutlinePencilSquare } from "react-icons/hi2";
8
8
  import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
9
9
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
10
10
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
11
11
  import AppShell from "../../layout/AppShell.js";
12
- import { useLocationSearch, useNavigate } from "../../../navigation/AppRouter.js";
12
+ import FloatingActionButton from "../../layout/FloatingActionButton.js";
13
+ import { useLocation } from "@rapidrest/react/client";
14
+ import { useNavigate } from "../../../navigation/index.js";
13
15
  import KeyEnrollmentGate from "../../layout/KeyEnrollmentGate.js";
14
16
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
15
17
  import { prefetchComposeWindow, useCompose } from "../compose/ComposeContext.js";
@@ -17,7 +19,11 @@ import LocalIndexLifecycle from "../../../search/LocalIndexLifecycle.js";
17
19
  import { NO_LIVE_UPDATES } from "../../../mail/useMailLiveUpdates.js";
18
20
  import { MAILBOX_LIST_LIMIT, MailConnectionContext, useMailConnection } from "../../../mail/useMailConnection.js";
19
21
  import { folderRows } from "../../../mail/folderTree.js";
20
- import { badgeFor, badgeLabel, countOfFolder, inboxUnreadTotal, } from "../../../mail/folderCounts.js";
22
+ import { primaryMailboxUid } from "../../../mail/primaryMailbox.js";
23
+ import { badgeFor, countOfFolder, inboxUnreadTotal, unreadBadgeTotal, } from "../../../mail/folderCounts.js";
24
+ import { ALL_MAILBOXES_SECTION, useSidebarSections } from "../../../mail/useCollapsedSections.js";
25
+ import FolderBadgeChip from "./FolderBadgeChip.js";
26
+ import SidebarSection from "./SidebarSection.js";
21
27
  import { useUnreadTitle } from "../../../mail/useUnreadTitle.js";
22
28
  import { pendingCountFor, usePendingSends } from "../../../mail/outbox/pendingSends.js";
23
29
  import OutboxBadge from "../OutboxBadge.js";
@@ -42,6 +48,8 @@ const MailShellContext = createContext({
42
48
  noteFolderUids: () => undefined,
43
49
  live: NO_LIVE_UPDATES,
44
50
  trackMessageChange: () => NO_TRACKER,
51
+ refreshFolderCounts: () => undefined,
52
+ folderCountOf: (folder) => ({ unread: folder.unreadCount, total: folder.totalCount }),
45
53
  mobileSearchSlot: null,
46
54
  });
47
55
  /** Reads the mailbox/folder a page is currently showing, as resolved by the enclosing `MailShell`. */
@@ -66,13 +74,6 @@ function aggregateBadge(mailboxFolders, type, counts) {
66
74
  }, { unread: 0, total: 0 });
67
75
  return badgeFor(type, sum);
68
76
  }
69
- /** A folder's badge: an accent pill with the unread count, or - for the folders that show how many they hold - plain muted text. */
70
- function FolderBadgeChip({ badge }) {
71
- return (_jsxs("span", { className: [
72
- "text-xs rounded-pill py-0.5 px-1.5",
73
- badge.kind === "unread" ? "font-bold bg-primary/15 text-primary-dark" : "font-medium text-text-muted",
74
- ].join(" "), children: [_jsx("span", { "aria-hidden": "true", children: badge.value }), _jsxs("span", { className: "sr-only", children: [" ", badgeLabel(badge)] })] }));
75
- }
76
77
  export { MAILBOX_LIST_LIMIT };
77
78
  /**
78
79
  * A separate component (not inlined into `MailShell`'s own render) so `useCompose()` resolves against
@@ -98,7 +99,7 @@ function ComposeButton({ mailboxUid }) {
98
99
  */
99
100
  function MobileComposeButton({ mailboxUid }) {
100
101
  const { openCompose } = useCompose();
101
- return (_jsx("button", { type: "button", "aria-label": "New message", onClick: () => openCompose({ mailboxUid }), onPointerEnter: prefetchComposeWindow, onFocus: prefetchComposeWindow, className: "md:hidden fixed right-4 bottom-[4.5rem] z-30 w-12 h-12 flex items-center justify-center rounded-full bg-primary text-white shadow-lg hover:bg-primary-dark", children: _jsx(HiOutlinePencilSquare, { size: 22, "aria-hidden": "true" }) }));
102
+ return (_jsx(FloatingActionButton, { label: "New message", icon: HiOutlinePencilSquare, onClick: () => openCompose({ mailboxUid }), onPointerEnter: prefetchComposeWindow, onFocus: prefetchComposeWindow }));
102
103
  }
103
104
  /**
104
105
  * Mail's keyboard shortcuts that don't belong to a list or a message: "New message". A component of its own for the reason `ComposeButton`
@@ -113,18 +114,20 @@ function MailShortcuts({ mailboxUid }) {
113
114
  /**
114
115
  * Mail's own contextual sidebar (every accessible mailbox's own folder tree, plus a merged "All Mailboxes"
115
116
  * aggregate section) + content area, rendered inside the shared `AppShell` chrome (icon rail, header,
116
- * impersonation banner — see that component). There is no client-side router in this framework (see
117
- * `ReactRoute`'s file-convention resolver) — the selected mailbox/folder (or aggregate pseudo-folder) live
118
- * in the URL's `?mailboxUid=`/`?folderUid=`/`?aggregate=` query params, read once on mount (never during
119
- * the initial render itself, matching every other query-param reader in this codebase — e.g.
120
- * `apps/admin/quarantine`'s `readMailboxUid()` — so the server-rendered and just-hydrated client markup
121
- * match).
117
+ * impersonation banner — see that component). The selected mailbox/folder (or aggregate pseudo-folder) live
118
+ * in the URL's `?mailboxUid=`/`?folderUid=`/`?aggregate=` query params, read from the router's location
119
+ * (`@rapidrest/react`), which is the request's on the server and while hydrating and follows a shallow
120
+ * navigation (a folder link) without remounting the page.
122
121
  *
123
122
  * Every accessible mailbox's folder tree renders at once - there is no "switch mailbox" affordance
124
123
  * anymore (the mailbox `<select>` this shell used to have is gone) - so a single-mailbox user sees zero
125
124
  * behavior change from before this, and a multi-mailbox user sees every mailbox's folders (and the new
126
125
  * aggregate section) simultaneously, matching the shared-mailbox feature's own "display as a separate set
127
126
  * of folders" requirement.
127
+ *
128
+ * An address that names nothing opens "All Mailboxes > Inbox" when there is more than one mailbox (that is when the section exists), and
129
+ * the one mailbox's own Inbox otherwise; a `?mailboxUid=`, `?folderUid=` or `?aggregate=` always wins. What needs a single mailbox while an all-mailboxes view is open (Compose, the new-message shortcut, key unlocking) uses the
130
+ * primary mailbox.
128
131
  */
129
132
  export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, pluginNav, children, }) {
130
133
  // The mailboxes, their folders and the live connection come from the persistent app frame when there is one (`AppChrome` owns them, so
@@ -136,51 +139,60 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
136
139
  const { status, error, mailboxes, mailboxFolders, foldersLoading, onFolderCreated, noteFolderUids, live, folderCounts, outbox: outboxStatuses } = hosted ?? own;
137
140
  // The messages this tab is still handing to the server: the Outbox pill counts them at once.
138
141
  const pendingSends = usePendingSends();
139
- const [requestedMailboxUid, setRequestedMailboxUid] = useState(null);
140
- const [requestedFolderUid, setRequestedFolderUid] = useState(null);
141
- const [requestedAggregateType, setRequestedAggregateType] = useState(null);
142
142
  const [drawerOpen, setDrawerOpen] = useState(false);
143
- // The selection lives in the URL, and the router (`AppRouter`) changes the URL without a page load when a folder link is
144
- // clicked - so it is read from the router's location, which updates, rather than once from `window.location`. Still read
145
- // in an effect, never during the first render, so the server render and the hydrating render agree.
146
- const search = useLocationSearch();
147
- useEffect(() => {
143
+ // The selection lives in the URL, and the router (`@rapidrest/react`) changes the URL without a page load when a folder link is
144
+ // clicked - a shallow navigation, which keeps this page and its state - so it is read from the router's location, which updates,
145
+ // rather than once from `window.location`. The location is the request's on the server and while hydrating (its `#fragment` is not
146
+ // sent, and no one here reads it), so the server render and the hydrating render agree and the selection is known from the first render.
147
+ const { search } = useLocation();
148
+ const { requestedMailboxUid, requestedFolderUid, requestedAggregateType } = useMemo(() => {
148
149
  const params = new URLSearchParams(search);
149
- setRequestedMailboxUid(params.get("mailboxUid"));
150
- setRequestedFolderUid(params.get("folderUid"));
151
- setRequestedAggregateType(params.get("aggregate"));
150
+ return { requestedMailboxUid: params.get("mailboxUid"), requestedFolderUid: params.get("folderUid"), requestedAggregateType: params.get("aggregate") };
152
151
  }, [search]);
153
152
  // Choosing a folder (or mailbox) changes the URL without a page load now, so the drawer that held the choice - which used to go
154
153
  // with the page - is closed here.
155
154
  useEffect(() => {
156
155
  setDrawerOpen(false);
157
156
  }, [search]);
158
- const aggregateFolderType = isAggregateFolderType(requestedAggregateType)
159
- ? requestedAggregateType
160
- : undefined;
161
- const mailboxUid = aggregateFolderType
162
- ? undefined
163
- : (requestedMailboxUid && mailboxes.some((mb) => mb.uid === requestedMailboxUid) ? requestedMailboxUid : undefined) ??
164
- mailboxes[0]?.uid;
157
+ // What the address asks for.
158
+ const requestedAggregate = isAggregateFolderType(requestedAggregateType) ? requestedAggregateType : undefined;
159
+ const requestedMailbox = requestedMailboxUid && mailboxes.some((mb) => mb.uid === requestedMailboxUid) ? requestedMailboxUid : undefined;
160
+ // The mailbox the caller has not chosen: their own, never a shared one that merely sorts first. It is what compose, the new-message
161
+ // shortcut and everything else that needs a single mailbox use while an all-mailboxes view (which has none) is open.
162
+ const defaultMailboxUid = primaryMailboxUid(mailboxes, userUid);
163
+ // With several mailboxes the address that names nothing opens "All Mailboxes > Inbox", the merged Inbox; with one there is no such
164
+ // section, so it opens that mailbox's own Inbox. A mailbox, a folder or an all-mailboxes view in the address wins over either.
165
+ const aggregateByDefault = !requestedAggregate && !requestedMailbox && requestedFolderUid === null && mailboxes.length > 1;
166
+ const aggregateFolderType = requestedAggregate ?? (aggregateByDefault ? "inbox" : undefined);
167
+ const mailboxUid = aggregateFolderType ? undefined : (requestedMailbox ?? defaultMailboxUid);
165
168
  const selectedMailboxFolders = mailboxFolders.find((mf) => mf.mailbox.uid === mailboxUid)?.folders ?? [];
166
169
  const folderUid = aggregateFolderType
167
170
  ? undefined
168
171
  : (requestedFolderUid && selectedMailboxFolders.some((f) => f.uid === requestedFolderUid) ? requestedFolderUid : undefined) ??
169
172
  selectedMailboxFolders.find((f) => f.type === "inbox")?.uid;
170
173
  // The mailbox `KeyEnrollmentGate`/`LocalIndexLifecycle`/`ComposeButton` treat as "the" mailbox when
171
- // there's no single selected one to use (aggregate mode) - the caller's own owned mailbox if they
172
- // have one, else whichever accessible mailbox happens to be first. See MailShell's own doc comment on
174
+ // there's no single selected one to use (aggregate mode) - the caller's primary mailbox (`primaryMailbox()`). See MailShell's own doc comment on
173
175
  // this being an accepted limitation: an aggregate-view message from a *different*, not-yet-visited
174
176
  // mailbox may still need that mailbox's own folder view opened directly to unlock/decrypt it.
175
- const defaultMailboxUid = mailboxes.find((mb) => mb.ownerUserUid === userUid)?.uid ?? mailboxes[0]?.uid;
176
177
  const activeMailboxUid = mailboxUid ?? defaultMailboxUid;
178
+ // Which sidebar sections are open: "All mailboxes" and the primary mailbox unless the reader chose otherwise. Any of them can be collapsed;
179
+ // a change of the open folder into another section opens that one (see `useSidebarSections()`); the first selection, once the address has been read
180
+ // and the mailboxes are here, is where a load lands, not a navigation. A lone mailbox has nothing to collapse, so its section is never a toggle.
181
+ const sections = useSidebarSections({
182
+ userUid,
183
+ primaryUid: defaultMailboxUid,
184
+ activeId: aggregateFolderType ? ALL_MAILBOXES_SECTION : mailboxUid,
185
+ });
186
+ const collapsible = mailboxes.length > 1;
177
187
  const counts = folderCounts.counts;
178
188
  const trackMessageChange = folderCounts.track;
189
+ const refreshFolderCounts = folderCounts.refresh;
190
+ const folderCountOf = useCallback((folder) => countOfFolder(folder, counts), [counts]);
179
191
  // `(3) Acme: Mail` in the tab strip while there is unread mail in an Inbox - kept by the frame when there is one.
180
192
  useUnreadTitle(inboxUnreadTotal(mailboxFolders, counts), { enabled: !hosted });
181
193
  // A state rather than a ref: the page reads it during its render, so it has to re-render once the row it names is in the document.
182
194
  const [mobileSearchSlot, setMobileSearchSlot] = useState(null);
183
- const contextValue = useMemo(() => ({ mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, noteFolderUids, live, trackMessageChange, mobileSearchSlot }), [mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, noteFolderUids, live, trackMessageChange, mobileSearchSlot]);
195
+ const contextValue = useMemo(() => ({ mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, noteFolderUids, live, trackMessageChange, refreshFolderCounts, folderCountOf, mobileSearchSlot }), [mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, noteFolderUids, live, trackMessageChange, refreshFolderCounts, folderCountOf, mobileSearchSlot]);
184
196
  // A full-screen takeover, not nested inside the rest of the app's chrome — there's nothing else for a
185
197
  // mailbox-less caller to do here yet, so the icon rail/header/folder tree don't render at all. Checks
186
198
  // `mailboxes.length` directly (not `!mailboxUid`) since `mailboxUid` is legitimately undefined in
@@ -201,32 +213,33 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
201
213
  // A function, not a plain JSX constant — it's rendered twice (desktop `<aside>` + mobile
202
214
  // `Drawer`), possibly *simultaneously* mounted (the aside is only CSS-hidden below `md`, not
203
215
  // unmounted).
204
- const sidebarContent = () => (_jsxs(_Fragment, { children: [_jsx("div", { className: "p-3", children: _jsx(ComposeButton, { mailboxUid: defaultMailboxUid }) }), foldersLoading ? (_jsx("div", { className: "flex-1 overflow-y-auto px-3 pb-3", children: _jsx(SkeletonList, { count: 6, className: "pt-1" }) })) : (_jsxs("nav", { className: "flex-1 overflow-y-auto px-3 pb-3 flex flex-col gap-3", children: [mailboxes.length > 1 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-1 px-2.5", children: "All Mailboxes" }), _jsx("div", { className: "flex flex-col gap-0.5", children: AGGREGATE_FOLDER_TYPES.map((type) => {
205
- const badge = aggregateBadge(mailboxFolders, type, counts);
206
- return (_jsxs("a", { href: `/?aggregate=${encodeURIComponent(type)}`, className: [
207
- "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5",
208
- aggregateFolderType === type
209
- ? "bg-primary/10 text-primary-dark font-semibold"
210
- : "text-text hover:bg-surface-alt",
211
- ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[type] }), badge && _jsx(FolderBadgeChip, { badge: badge })] }, type));
212
- }) })] })), mailboxFolders.map(({ mailbox, folders, error: mailboxError }) => (_jsxs("div", { children: [_jsxs("div", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-1 px-2.5 truncate", children: [mailbox.displayName, mailbox.ownerUserUid ? "" : " (shared)"] }), mailboxError && (_jsx("div", { className: "px-2.5 pb-1", children: _jsx(Alert, { children: mailboxError }) })), _jsx("div", { className: "flex flex-col gap-0.5", children: folderRows(folders, pendingCountFor(mailbox.uid, pendingSends)).map((row) => {
213
- if (row.kind === "placeholder") {
214
- // A folder the server has not made (or told us about) yet - the Outbox and Sent Items are made with the first message
215
- // sent - shown the moment a message is on its way. Nothing to open; the real folder replaces it when it arrives.
216
- const sending = pendingCountFor(mailbox.uid, pendingSends);
217
- return (_jsxs("div", { "data-folder-placeholder": row.type, className: "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5 text-text-muted", children: [_jsx("span", { children: FOLDER_LABELS[row.type] }), row.type === "outbox" && _jsx(OutboxBadge, { total: sending, pendingHere: sending })] }, `placeholder-${row.type}`));
218
- }
219
- const folder = row.folder;
220
- const badge = badgeFor(folder.type, countOfFolder(folder, counts));
221
- return (_jsxs("a", { href: `/?mailboxUid=${encodeURIComponent(mailbox.uid)}&folderUid=${encodeURIComponent(folder.uid)}`, className: [
222
- "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5",
223
- folder.uid === folderUid
224
- ? "bg-primary/10 text-primary-dark font-semibold"
225
- : "text-text hover:bg-surface-alt",
226
- ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[folder.type] ?? folder.name }), badge &&
227
- (folder.type === "outbox" ? (_jsx(OutboxBadge, { total: badge.value, status: outboxStatuses[folder.uid], pendingHere: pendingCountFor(mailbox.uid, pendingSends) })) : (_jsx(FolderBadgeChip, { badge: badge })))] }, folder.uid));
228
- }) })] }, mailbox.uid)))] }))] }));
229
- inner = (_jsxs(_Fragment, { children: [_jsx(MailShortcuts, { mailboxUid: defaultMailboxUid }), _jsx("aside", { className: "hidden md:flex w-64 shrink-0 bg-surface border-r border-border flex-col", children: sidebarContent() }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Folders", children: _jsx("div", { className: "flex flex-col", children: sidebarContent() }) }), _jsxs("main", { className: "flex-1 min-w-0 overflow-y-auto", children: [_jsxs("div", { className: "md:hidden flex items-center gap-2 p-3", children: [_jsx("button", { type: "button", className: "shrink-0 w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": "Open folders", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx("div", { ref: setMobileSearchSlot, className: "flex-1 min-w-0" })] }), _jsx(MobileComposeButton, { mailboxUid: defaultMailboxUid }), _jsx(MailShellContext.Provider, { value: contextValue, children: children })] })] }));
216
+ // `idPrefix` keeps each section's folder list's id (which its heading's `aria-controls` names) unique between the two.
217
+ const sidebarContent = (idPrefix) => (_jsxs(_Fragment, { children: [_jsx("div", { className: "p-3", children: _jsx(ComposeButton, { mailboxUid: defaultMailboxUid }) }), foldersLoading ? (_jsx("div", { className: "flex-1 overflow-y-auto px-3 pb-3", children: _jsx(SkeletonList, { count: 6, className: "pt-1" }) })) : (_jsxs("nav", { className: "flex-1 overflow-y-auto px-3 pb-3 flex flex-col gap-3", children: [mailboxes.length > 1 && (_jsx(SidebarSection, { domId: `${idPrefix}-mail-section-${ALL_MAILBOXES_SECTION}`, label: "All Mailboxes", collapsible: collapsible, expanded: sections.isExpanded(ALL_MAILBOXES_SECTION), onToggle: () => sections.toggle(ALL_MAILBOXES_SECTION), unread: unreadBadgeTotal(mailboxFolders.flatMap((mf) => mf.folders), counts), children: AGGREGATE_FOLDER_TYPES.map((type) => {
218
+ const badge = aggregateBadge(mailboxFolders, type, counts);
219
+ return (_jsxs("a", { href: `/?aggregate=${encodeURIComponent(type)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: [
220
+ "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5",
221
+ aggregateFolderType === type
222
+ ? "bg-primary/10 text-primary-dark font-semibold"
223
+ : "text-text hover:bg-surface-alt",
224
+ ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[type] }), badge && _jsx(FolderBadgeChip, { badge: badge })] }, type));
225
+ }) })), mailboxFolders.map(({ mailbox, folders, error: mailboxError }) => (_jsx(SidebarSection, { domId: `${idPrefix}-mail-section-${mailbox.uid}`, label: _jsxs(_Fragment, { children: [mailbox.displayName, mailbox.ownerUserUid ? "" : " (shared)"] }), collapsible: collapsible, expanded: sections.isExpanded(mailbox.uid), onToggle: () => sections.toggle(mailbox.uid), unread: unreadBadgeTotal(folders, counts), notice: mailboxError && (_jsx("div", { className: "px-2.5 pb-1", children: _jsx(Alert, { children: mailboxError }) })), children: folderRows(folders, pendingCountFor(mailbox.uid, pendingSends)).map((row) => {
226
+ if (row.kind === "placeholder") {
227
+ // A folder the server has not made (or told us about) yet - the Outbox and Sent Items are made with the first message
228
+ // sent - shown the moment a message is on its way. Nothing to open; the real folder replaces it when it arrives.
229
+ const sending = pendingCountFor(mailbox.uid, pendingSends);
230
+ return (_jsxs("div", { "data-folder-placeholder": row.type, className: "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5 text-text-muted", children: [_jsx("span", { children: FOLDER_LABELS[row.type] }), row.type === "outbox" && _jsx(OutboxBadge, { total: sending, pendingHere: sending })] }, `placeholder-${row.type}`));
231
+ }
232
+ const folder = row.folder;
233
+ const badge = badgeFor(folder.type, countOfFolder(folder, counts));
234
+ return (_jsxs("a", { href: `/?mailboxUid=${encodeURIComponent(mailbox.uid)}&folderUid=${encodeURIComponent(folder.uid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: [
235
+ "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5",
236
+ folder.uid === folderUid
237
+ ? "bg-primary/10 text-primary-dark font-semibold"
238
+ : "text-text hover:bg-surface-alt",
239
+ ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[folder.type] ?? folder.name }), badge &&
240
+ (folder.type === "outbox" ? (_jsx(OutboxBadge, { total: badge.value, status: outboxStatuses[folder.uid], pendingHere: pendingCountFor(mailbox.uid, pendingSends) })) : (_jsx(FolderBadgeChip, { badge: badge })))] }, folder.uid));
241
+ }) }, mailbox.uid)))] }))] }));
242
+ inner = (_jsxs(_Fragment, { children: [_jsx(MailShortcuts, { mailboxUid: defaultMailboxUid }), _jsx("aside", { className: "hidden md:flex w-64 shrink-0 bg-surface border-r border-border flex-col", children: sidebarContent("desktop") }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Folders", fullScreen: true, children: _jsx("div", { className: "flex flex-col", children: sidebarContent("mobile") }) }), _jsxs("main", { className: "flex-1 min-w-0 overflow-y-auto", children: [_jsxs("div", { className: "md:hidden flex items-center gap-2 p-3", children: [_jsx("button", { type: "button", className: "shrink-0 w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": "Open folders", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx("div", { ref: setMobileSearchSlot, className: "flex-1 min-w-0" })] }), _jsx(MobileComposeButton, { mailboxUid: defaultMailboxUid }), _jsx(MailShellContext.Provider, { value: contextValue, children: children })] })] }));
230
243
  }
231
244
  // Wraps AppShell unconditionally, at a stable tree position regardless of whether mailboxUid has
232
245
  // resolved yet - KeyEnrollmentGate itself passes `children` through untouched until a real
@@ -0,0 +1,23 @@
1
+ import React, { ReactNode } from "react";
2
+ export interface SidebarSectionProps {
3
+ /** Unique per rendering of the sidebar (it is on screen twice on a phone: beside the list and in the drawer): names the folder list the heading controls. */
4
+ domId: string;
5
+ /** The heading's text. */
6
+ label: ReactNode;
7
+ /** Whether the heading is a toggle at all. Not for a lone mailbox, whose section has nothing to be hidden for. */
8
+ collapsible: boolean;
9
+ expanded: boolean;
10
+ onToggle: () => void;
11
+ /** The unread mail in the section's folders that carry an unread count (see `unreadBadgeTotal()`), shown on the heading while it is collapsed (and only when there is some) so new mail in a hidden section is still noticed. */
12
+ unread?: number;
13
+ /** Shown between the heading and the folders, collapsed or not: a failure to load the section's folders is not something to hide. */
14
+ notice?: ReactNode;
15
+ /** The section's folder rows. */
16
+ children: ReactNode;
17
+ }
18
+ /**
19
+ * One section of Mail's sidebar ("All mailboxes", or one mailbox): a heading over a list of folders. When `collapsible` the heading is a
20
+ * disclosure button (`aria-expanded`, `aria-controls` the list, a chevron that turns) and the list is `hidden` while it is collapsed -
21
+ * kept in the document, so the button's `aria-controls` always names something and nothing is fetched or rebuilt on opening it.
22
+ */
23
+ export default function SidebarSection({ domId, label, collapsible, expanded, onToggle, unread, notice, children }: SidebarSectionProps): React.JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HiChevronDown } from "react-icons/hi2";
3
+ import FolderBadgeChip from "./FolderBadgeChip.js";
4
+ /**
5
+ * One section of Mail's sidebar ("All mailboxes", or one mailbox): a heading over a list of folders. When `collapsible` the heading is a
6
+ * disclosure button (`aria-expanded`, `aria-controls` the list, a chevron that turns) and the list is `hidden` while it is collapsed -
7
+ * kept in the document, so the button's `aria-controls` always names something and nothing is fetched or rebuilt on opening it.
8
+ */
9
+ export default function SidebarSection({ domId, label, collapsible, expanded, onToggle, unread = 0, notice, children }) {
10
+ const shown = !collapsible || expanded;
11
+ return (_jsxs("div", { children: [collapsible ? (_jsx("h2", { className: "mb-1", children: _jsxs("button", { type: "button", "aria-expanded": expanded, "aria-controls": domId, onClick: onToggle, className: [
12
+ "flex w-full items-center gap-1 rounded-sm px-2.5 py-0.5 text-left text-xs font-bold uppercase tracking-wide text-text-muted",
13
+ "focus-visible:outline-2 focus-visible:outline-primary",
14
+ "hover:bg-surface-alt hover:text-text",
15
+ ].join(" "), children: [_jsx(HiChevronDown, { size: 14, "aria-hidden": "true", className: ["shrink-0 transition-transform", expanded ? "" : "-rotate-90"].join(" ") }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), !expanded && unread > 0 && _jsx(FolderBadgeChip, { badge: { kind: "unread", value: unread } })] }) })) : (_jsx("div", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-1 px-2.5 truncate", children: label })), notice, _jsx("div", { id: domId, hidden: !shown, className: shown ? "flex flex-col gap-0.5" : "hidden", children: children })] }));
16
+ }
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import { MenuSectionSpec } from "../MenuButton.js";
3
+ /** What each row of the menu does - the reading pane's own handlers. */
4
+ export interface MessageMenuActions {
5
+ replyAll: () => void;
6
+ forward: () => void;
7
+ deleteMessage: () => void;
8
+ toggleRead: () => void;
9
+ toggleFlag: () => void;
10
+ reportJunk: () => void;
11
+ reportPhishing: () => void;
12
+ notJunk: () => void;
13
+ notJunkAndTrust: () => void;
14
+ blockSender: () => void;
15
+ neverBlockSender: () => void;
16
+ print: () => void;
17
+ viewSource: () => void;
18
+ viewDetails: () => void;
19
+ saveAsEml: () => void;
20
+ createRule: () => void;
21
+ }
22
+ export interface MessageMoreMenuProps {
23
+ actions: MessageMenuActions;
24
+ /** The sender's address the Block rows name. */
25
+ senderAddress: string;
26
+ read: boolean;
27
+ flagged: boolean;
28
+ /** The reader may change this mailbox's messages and filters (it is not a view-only share). */
29
+ writable: boolean;
30
+ /** Something is already being done to the message: its rows wait. */
31
+ busy: boolean;
32
+ inJunk: boolean;
33
+ /** The message is in Deleted Items: the Delete row reads "Delete permanently". */
34
+ inDeletedItems: boolean;
35
+ /** Mail the reader sent: it is not junk, and its sender is the reader. */
36
+ sent: boolean;
37
+ /** The sender is one of the reader's own addresses, which is not blocked. */
38
+ ownSender: boolean;
39
+ /** The reader may add a sender to this mailbox's Safe Senders: they have full access, or the server has not said which access they have (it then refuses, with its reason). */
40
+ canTrustSender: boolean;
41
+ /** Why the message cannot be printed right now (an encrypted one that is locked or still opening), or `undefined`. */
42
+ printReason: string | undefined;
43
+ /** The compose window is opening for a reply or forward: Reply all and Forward wait for it. */
44
+ composing: boolean;
45
+ /** The classes of the round "..." button - the reading pane's own icon-button look. */
46
+ triggerClassName: string;
47
+ }
48
+ /** The sections of the menu - Outlook's "More actions", cut to what this product really does. */
49
+ export declare function buildMessageMenu(props: MessageMoreMenuProps): MenuSectionSpec[];
50
+ /**
51
+ * The "..." at the end of a message card's icon row: Outlook's More actions menu, holding the actions there is no room for beside the
52
+ * message and the ones that are less often wanted. An ARIA menu (`MenuButton`): arrows, Home and End move, Right or Enter opens a submenu, Left or
53
+ * Escape leaves it (Escape at the top closes the menu and the focus goes back to this button), and a click outside closes it. A row that cannot be used right now
54
+ * is disabled and says why under its label.
55
+ */
56
+ export default function MessageMoreMenu(props: MessageMoreMenuProps): React.JSX.Element;
@@ -0,0 +1,153 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { HiOutlineArrowDownTray, HiOutlineArrowUturnLeft, HiOutlineArrowUturnRight, HiOutlineCodeBracket, HiOutlineCog6Tooth, HiOutlineDocumentText, HiOutlineEllipsisHorizontal, HiOutlineEnvelope, HiOutlineEnvelopeOpen, HiOutlineEye, HiOutlineFlag, HiOutlineFunnel, HiOutlineHandRaised, HiOutlineInboxArrowDown, HiOutlineInformationCircle, HiOutlineNoSymbol, HiOutlinePrinter, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineTrash, } from "react-icons/hi2";
3
+ import MenuButton from "../MenuButton.js";
4
+ const VIEW_ONLY = "View-only mailbox";
5
+ /** A row, disabled with `reason` (shown under the label, where a keyboard or phone user sees it as well) when there is one. */
6
+ function row(key, label, icon, onSelect, reason, extra = {}) {
7
+ return { key, label, icon, onSelect, disabled: reason !== undefined, description: reason, ...extra };
8
+ }
9
+ /** The sections of the menu - Outlook's "More actions", cut to what this product really does. */
10
+ export function buildMessageMenu(props) {
11
+ const { actions, read, flagged, writable, busy, inJunk, inDeletedItems, sent, ownSender, printReason, composing } = props;
12
+ // The reason a row that changes the message is unavailable, if it is: the first that applies wins.
13
+ const changeReason = !writable ? VIEW_ONLY : undefined;
14
+ const junkReason = changeReason ?? (sent ? "Not available for mail you sent" : inJunk ? "Already in Junk Email" : undefined);
15
+ const blockReason = changeReason ?? (sent ? "Not available for mail you sent" : ownSender ? "This is your own address" : undefined);
16
+ const waiting = (reason) => reason ?? (busy ? "Working on this message" : undefined);
17
+ const trustLabel = `Not junk, and always trust ${props.senderAddress}`;
18
+ const blockLabel = `Block ${props.senderAddress}`;
19
+ const neverBlockLabel = `Never block ${props.senderAddress}`;
20
+ return [
21
+ {
22
+ key: "message",
23
+ items: [
24
+ {
25
+ key: "reply-actions",
26
+ label: "Other reply actions",
27
+ icon: _jsx(HiOutlineArrowUturnLeft, { size: 16 }),
28
+ submenu: [
29
+ {
30
+ key: "reply-actions-items",
31
+ items: [
32
+ row("reply-all", "Reply all", _jsx(HiOutlineArrowUturnLeft, { size: 16 }), actions.replyAll, composing ? "Opening the compose window" : undefined),
33
+ row("forward", "Forward", _jsx(HiOutlineArrowUturnRight, { size: 16 }), actions.forward, composing ? "Opening the compose window" : undefined),
34
+ ],
35
+ },
36
+ ],
37
+ },
38
+ row("delete",
39
+ // In Deleted Items there is nowhere further to move it to: Delete is the permanent one, and asks first.
40
+ inDeletedItems ? "Delete permanently" : "Delete", _jsx(HiOutlineTrash, { size: 16 }), actions.deleteMessage, waiting(changeReason)),
41
+ row("toggle-read", read ? "Mark as unread" : "Mark as read", read ? _jsx(HiOutlineEnvelope, { size: 16 }) : _jsx(HiOutlineEnvelopeOpen, { size: 16 }), actions.toggleRead, waiting(changeReason)),
42
+ row("toggle-flag", flagged ? "Unflag" : "Flag", _jsx(HiOutlineFlag, { size: 16 }), actions.toggleFlag, waiting(changeReason)),
43
+ ],
44
+ },
45
+ {
46
+ key: "sender",
47
+ items: [
48
+ {
49
+ key: "report",
50
+ label: "Report",
51
+ icon: _jsx(HiOutlineNoSymbol, { size: 16 }),
52
+ submenu: [
53
+ {
54
+ key: "report-items",
55
+ items: [
56
+ row("report-junk", "Report junk", _jsx(HiOutlineNoSymbol, { size: 16 }), actions.reportJunk, waiting(junkReason)),
57
+ row("report-phishing", "Report phishing", _jsx(HiOutlineShieldExclamation, { size: 16 }), actions.reportPhishing, waiting(junkReason)),
58
+ // In Junk Email the way back: to the Inbox, telling the spam filter it was wrong - and, when the reader may, trusting the sender.
59
+ ...(inJunk
60
+ ? [
61
+ row("not-junk", "Not junk", _jsx(HiOutlineInboxArrowDown, { size: 16 }), actions.notJunk, waiting(changeReason)),
62
+ ...(props.canTrustSender && !ownSender
63
+ ? [
64
+ row("not-junk-trust", trustLabel, _jsx(HiOutlineShieldCheck, { size: 16 }), actions.notJunkAndTrust, waiting(changeReason), {
65
+ title: trustLabel,
66
+ }),
67
+ ]
68
+ : []),
69
+ ]
70
+ : []),
71
+ ],
72
+ },
73
+ ],
74
+ },
75
+ {
76
+ key: "block",
77
+ label: "Block",
78
+ icon: _jsx(HiOutlineHandRaised, { size: 16 }),
79
+ submenu: [
80
+ {
81
+ key: "block-items",
82
+ items: [
83
+ row("block-sender", blockLabel, _jsx(HiOutlineHandRaised, { size: 16 }), actions.blockSender, waiting(blockReason), { title: blockLabel }),
84
+ row("never-block-sender", neverBlockLabel, _jsx(HiOutlineShieldCheck, { size: 16 }), actions.neverBlockSender, waiting(blockReason), { title: neverBlockLabel }),
85
+ ],
86
+ },
87
+ ],
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ key: "output",
93
+ items: [
94
+ row("print", "Print", _jsx(HiOutlinePrinter, { size: 16 }), actions.print, printReason),
95
+ {
96
+ key: "view",
97
+ label: "View",
98
+ icon: _jsx(HiOutlineEye, { size: 16 }),
99
+ submenu: [
100
+ {
101
+ key: "view-items",
102
+ items: [
103
+ row("view-source", "View message source", _jsx(HiOutlineCodeBracket, { size: 16 }), actions.viewSource),
104
+ row("view-details", "Message details", _jsx(HiOutlineInformationCircle, { size: 16 }), actions.viewDetails),
105
+ ],
106
+ },
107
+ ],
108
+ },
109
+ {
110
+ key: "save-as",
111
+ label: "Save as",
112
+ icon: _jsx(HiOutlineArrowDownTray, { size: 16 }),
113
+ submenu: [
114
+ {
115
+ key: "save-as-items",
116
+ items: [
117
+ row("save-eml", "Save as .eml", _jsx(HiOutlineEnvelope, { size: 16 }), actions.saveAsEml),
118
+ row("save-pdf", "Save as PDF", _jsx(HiOutlineDocumentText, { size: 16 }), actions.print, printReason, {
119
+ description: printReason ?? "Opens the print dialog: choose Save as PDF",
120
+ }),
121
+ ],
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+ },
127
+ {
128
+ key: "advanced",
129
+ items: [
130
+ {
131
+ key: "advanced-actions",
132
+ label: "Advanced actions",
133
+ icon: _jsx(HiOutlineCog6Tooth, { size: 16 }),
134
+ submenu: [
135
+ {
136
+ key: "advanced-items",
137
+ items: [row("create-rule", "Create rule", _jsx(HiOutlineFunnel, { size: 16 }), actions.createRule, changeReason)],
138
+ },
139
+ ],
140
+ },
141
+ ],
142
+ },
143
+ ];
144
+ }
145
+ /**
146
+ * The "..." at the end of a message card's icon row: Outlook's More actions menu, holding the actions there is no room for beside the
147
+ * message and the ones that are less often wanted. An ARIA menu (`MenuButton`): arrows, Home and End move, Right or Enter opens a submenu, Left or
148
+ * Escape leaves it (Escape at the top closes the menu and the focus goes back to this button), and a click outside closes it. A row that cannot be used right now
149
+ * is disabled and says why under its label.
150
+ */
151
+ export default function MessageMoreMenu(props) {
152
+ return (_jsx(MenuButton, { iconOnly: true, label: "More actions", "aria-label": "More actions", title: "More actions", icon: _jsx(HiOutlineEllipsisHorizontal, { size: 16, "aria-hidden": "true" }), className: props.triggerClassName, sections: buildMessageMenu(props), width: 288 }));
153
+ }
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ /** What the dialog is showing a message's source for: the whole of it, or only its header lines ("Message details"). */
3
+ export type SourceMode = "source" | "headers";
4
+ /** What has been fetched of the raw message: on its way, here, or refused. */
5
+ export type SourceState = {
6
+ status: "loading";
7
+ } | {
8
+ status: "ready";
9
+ raw: string;
10
+ } | {
11
+ status: "error";
12
+ message: string;
13
+ };
14
+ export interface MessageSourceDialogProps {
15
+ open: boolean;
16
+ onClose: () => void;
17
+ mode: SourceMode;
18
+ source: SourceState;
19
+ /** The message is end-to-end encrypted: what is shown is the ciphertext the server holds, and the dialog says so. */
20
+ encrypted: boolean;
21
+ }
22
+ /**
23
+ * A message's raw source (or just its headers) in a full-window dialog, in a monospace block that can be scrolled and selected, with a Copy
24
+ * button. It is the RFC 5322 text exactly as the server stores it - for an encrypted message that is the encrypted message, never a
25
+ * decrypted one, and the dialog says so. A very long source is cut for display (`MAX_SOURCE_DISPLAY_CHARS`); Copy has all of it.
26
+ */
27
+ export default function MessageSourceDialog({ open, onClose, mode, source, encrypted }: MessageSourceDialogProps): React.JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ ///////////////////////////////////////////////////////////////////////////////
3
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
4
+ // SPDX-License-Identifier: MPL-2.0
5
+ ///////////////////////////////////////////////////////////////////////////////
6
+ import { useMemo } from "react";
7
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
8
+ import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
9
+ import Drawer from "@rapidmx/react-shared/components/overlays/Drawer.js";
10
+ import { MAX_SOURCE_DISPLAY_CHARS, headerText, sourceText } from "./messageExport.js";
11
+ const TITLES = { source: "Message source", headers: "Message details" };
12
+ /**
13
+ * A message's raw source (or just its headers) in a full-window dialog, in a monospace block that can be scrolled and selected, with a Copy
14
+ * button. It is the RFC 5322 text exactly as the server stores it - for an encrypted message that is the encrypted message, never a
15
+ * decrypted one, and the dialog says so. A very long source is cut for display (`MAX_SOURCE_DISPLAY_CHARS`); Copy has all of it.
16
+ */
17
+ export default function MessageSourceDialog({ open, onClose, mode, source, encrypted }) {
18
+ const text = useMemo(() => {
19
+ if (source.status !== "ready") {
20
+ return "";
21
+ }
22
+ return mode === "headers" ? headerText(source.raw) : sourceText(source.raw);
23
+ }, [source, mode]);
24
+ const cut = text.length > MAX_SOURCE_DISPLAY_CHARS;
25
+ const title = TITLES[mode];
26
+ return (_jsx(Drawer, { open: open, onClose: onClose, title: title, fullScreen: true, children: _jsxs("div", { className: "flex flex-col gap-3 text-sm", children: [encrypted && (_jsx("p", { role: "note", className: "py-2 px-3 rounded-sm bg-surface-alt text-text", children: "This message is encrypted. This is the encrypted message exactly as the server stores it; it is not decrypted here." })), source.status === "loading" && (_jsx("p", { role: "status", className: "text-text-muted", children: "Loading the message\u2026" })), source.status === "error" && _jsx(Alert, { children: source.message }), source.status === "ready" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(CopyButton, { value: text, label: `Copy the ${mode === "headers" ? "message headers" : "message source"}` }), cut && (_jsxs("span", { className: "text-xs text-text-muted", children: ["Showing the first ", MAX_SOURCE_DISPLAY_CHARS.toLocaleString(), " of ", text.length.toLocaleString(), " characters. Copy and Save as .eml keep all of it."] }))] }), _jsx("pre", { tabIndex: 0, "aria-label": title, className: "m-0 max-h-[70vh] overflow-auto rounded-sm border border-border bg-surface-alt p-3 font-mono text-xs whitespace-pre-wrap break-all", children: cut ? text.slice(0, MAX_SOURCE_DISPLAY_CHARS) : text })] }))] }) }));
27
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import type { OutgoingReply } from "../../../mail/outbox/outgoingReplies.js";
3
+ /**
4
+ * A reply or forward that has just been sent, drawn where its message will be once the server has filed it: the sender, who it went to, its body as
5
+ * it was composed, and one line saying where it is - "Sending...", "Sent", or, when it did not go, "Not sent: <why>" with the same ways out the
6
+ * pop-up offers (Retry, Open draft ...). It reads like the message's own card, so the real message replaces it without the thread rearranging.
7
+ *
8
+ * The body goes through the reading pane's own sanitizing frame (`MessageBody`), exactly as a received message's does.
9
+ */
10
+ export default function PendingMessageCard({ reply, headerRef, }: {
11
+ reply: OutgoingReply;
12
+ /** Registered so the pane can put the focus here when the message first appears. Focusable, but not in the tab order. */
13
+ headerRef: (node: HTMLHeadingElement | null) => void;
14
+ }): React.JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
3
+ import MailAddress, { RecipientLine } from "../MailAddress.js";
4
+ import MessageBody from "./MessageBody.js";
5
+ import { BODY_FONT_STYLE, CardShell, SenderAvatar } from "./MessageCard.js";
6
+ /**
7
+ * A reply or forward that has just been sent, drawn where its message will be once the server has filed it: the sender, who it went to, its body as
8
+ * it was composed, and one line saying where it is - "Sending...", "Sent", or, when it did not go, "Not sent: <why>" with the same ways out the
9
+ * pop-up offers (Retry, Open draft ...). It reads like the message's own card, so the real message replaces it without the thread rearranging.
10
+ *
11
+ * The body goes through the reading pane's own sanitizing frame (`MessageBody`), exactly as a received message's does.
12
+ */
13
+ export default function PendingMessageCard({ reply, headerRef, }) {
14
+ const failed = reply.state === "failed";
15
+ return (_jsxs(CardShell, { children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [_jsx(SenderAvatar, { from: reply.sender }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-2 text-sm", children: [_jsx("h2", { ref: headerRef, tabIndex: -1, style: BODY_FONT_STYLE, className: "flex-1 min-w-[13rem] font-semibold outline-none focus-visible:outline-2 focus-visible:outline-primary", children: _jsx(MailAddress, { recipient: reply.sender }) }), _jsxs("div", { role: failed ? "alert" : "status", "data-testid": "pending-message-status", "data-state": reply.state, className: ["flex items-center gap-1.5 text-xs shrink-0", failed ? "font-semibold text-danger" : "text-text-muted"].join(" "), children: [reply.state === "sending" && _jsx("span", { "aria-hidden": "true", className: "rr-sending-dot h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }), _jsx("span", { children: failed ? `Not sent: ${reply.failure}` : reply.state === "sending" ? "Sending…" : "Sent" })] }, failed ? "failed" : "progress")] }), _jsx(RecipientLine, { label: "To", recipients: reply.to }), _jsx(RecipientLine, { label: "Cc", recipients: reply.cc }), _jsx(RecipientLine, { label: "Bcc", recipients: reply.bcc })] })] }), failed && (_jsx("div", { className: "flex flex-wrap gap-2 px-4 pb-3", children: reply.actions.map((action) => (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: action.onClick, children: action.label }, action.label))) })), _jsx("div", { className: "px-4 pb-4", children: _jsx(MessageBody, { messageUid: reply.uid, messageVersion: 0, title: `Message: ${reply.subject}`, content: { kind: "html", html: reply.html } }) })] }));
16
+ }