@rapidmx/web-client 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (443) hide show
  1. package/README.md +373 -88
  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/quarantine/index.tsx +2 -1
  7. package/apps/admin/signing-certificates/index.tsx +343 -0
  8. package/apps/escrow/_layout.tsx +8 -2
  9. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  10. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  11. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  12. package/apps/shared/appearance/ColorField.tsx +107 -0
  13. package/apps/shared/appearance/appearanceCache.ts +106 -0
  14. package/apps/shared/appearance/appearanceContext.ts +77 -0
  15. package/apps/shared/appearance/bootScript.ts +35 -0
  16. package/apps/shared/appearance/color.ts +169 -0
  17. package/apps/shared/appearance/photo.ts +88 -0
  18. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  19. package/apps/shared/appearance/theme.ts +355 -0
  20. package/apps/shared/auth/accountUrl.ts +9 -0
  21. package/apps/shared/auth/adminAccess.ts +99 -0
  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 +2 -1
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  27. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  28. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  29. package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
  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 +98 -115
  33. package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
  34. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  35. package/apps/shared/components/layout/AppShell.tsx +477 -288
  36. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  37. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
  38. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
  39. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  40. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  41. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  42. package/apps/shared/components/layout/UserMenu.tsx +372 -185
  43. package/apps/shared/components/mail/ConversationList.tsx +271 -262
  44. package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
  45. package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
  46. package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
  47. package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
  48. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  49. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  50. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -160
  51. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
  52. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
  53. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
  54. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
  55. package/apps/shared/components/mail/compose/composePerf.ts +46 -0
  56. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  57. package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
  58. package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
  59. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  60. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  61. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  62. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  63. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  64. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  65. package/apps/shared/components/mail/reading/color.ts +245 -0
  66. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  67. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  68. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  69. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  70. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  71. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  72. package/apps/shared/components/mail/unreadStyle.tsx +75 -0
  73. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  74. package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
  75. package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
  76. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  77. package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
  78. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
  79. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
  80. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
  81. package/apps/shared/keyboard/dispatch.ts +124 -0
  82. package/apps/shared/keyboard/format.ts +89 -0
  83. package/apps/shared/keyboard/keymap.ts +114 -0
  84. package/apps/shared/keyboard/match.ts +44 -0
  85. package/apps/shared/keyboard/parse.ts +136 -0
  86. package/apps/shared/keyboard/platform.ts +34 -0
  87. package/apps/shared/keyboard/registry.ts +65 -0
  88. package/apps/shared/keyboard/targets.ts +79 -0
  89. package/apps/shared/keyboard/useShortcut.ts +50 -0
  90. package/apps/shared/keyboard/useShortcutProps.ts +17 -0
  91. package/apps/shared/mail/folderCounts.ts +308 -0
  92. package/apps/shared/mail/folderTree.ts +143 -0
  93. package/apps/shared/mail/listSnapshots.ts +87 -0
  94. package/apps/shared/mail/messageReadState.ts +85 -0
  95. package/apps/shared/mail/newMailNotifications.ts +183 -0
  96. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  97. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  98. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  99. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  100. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  101. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  102. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  103. package/apps/shared/mail/outbox/sendState.ts +35 -0
  104. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  105. package/apps/shared/mail/useMailConnection.ts +203 -0
  106. package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
  107. package/apps/shared/mail/useMarkMessageRead.ts +47 -0
  108. package/apps/shared/mail/useNewMailNotifications.ts +178 -0
  109. package/apps/shared/mail/useUnreadTitle.ts +42 -0
  110. package/apps/shared/navigation/AppRouter.tsx +300 -0
  111. package/apps/shared/navigation/appHrefs.ts +23 -0
  112. package/apps/shared/navigation/frameContext.tsx +35 -0
  113. package/apps/shared/navigation/idle.ts +45 -0
  114. package/apps/shared/navigation/routerContext.tsx +83 -0
  115. package/apps/shared/navigation/routes.ts +72 -0
  116. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  117. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  118. package/apps/shared/notifications/apiErrors.ts +98 -0
  119. package/apps/shared/notifications/headerOffset.ts +35 -0
  120. package/apps/shared/notifications/pushStatus.ts +56 -0
  121. package/apps/shared/notifications/store.ts +549 -0
  122. package/apps/shared/notifications/systemErrors.ts +63 -0
  123. package/apps/shared/notifications/useNotifications.ts +41 -0
  124. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
  125. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  126. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  127. package/apps/shared/signing/enrollmentView.ts +251 -0
  128. package/apps/shared/signing/signingInfo.ts +58 -0
  129. package/apps/shared/signing/useNow.ts +19 -0
  130. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  131. package/apps/shared/styles/app.css +108 -10
  132. package/apps/www/_layout.tsx +8 -2
  133. package/apps/www/_routedPage.tsx +24 -0
  134. package/apps/www/_routes.ts +36 -0
  135. package/apps/www/calendar/index.tsx +64 -33
  136. package/apps/www/contacts/[uid].tsx +109 -107
  137. package/apps/www/contacts/index.tsx +62 -42
  138. package/apps/www/index.tsx +2291 -1917
  139. package/apps/www/messages/[uid].tsx +108 -101
  140. package/apps/www/settings/appearance/index.tsx +21 -0
  141. package/apps/www/settings/auto-reply/index.tsx +7 -8
  142. package/apps/www/settings/encryption/index.tsx +1287 -1249
  143. package/apps/www/settings/filters/[uid].tsx +7 -2
  144. package/apps/www/settings/filters/index.tsx +102 -99
  145. package/apps/www/settings/filters/new/index.tsx +140 -133
  146. package/apps/www/settings/labels/index.tsx +204 -201
  147. package/apps/www/settings/privacy/index.tsx +7 -9
  148. package/apps/www/settings/read-receipts/index.tsx +7 -8
  149. package/apps/www/settings/sharing/index.tsx +256 -274
  150. package/apps/www/settings/signatures/[uid].tsx +172 -167
  151. package/apps/www/settings/signatures/index.tsx +88 -85
  152. package/apps/www/settings/signatures/new/index.tsx +135 -129
  153. package/apps/www/tasks/index.tsx +29 -18
  154. package/dist/apps/admin/_layout.d.ts +5 -1
  155. package/dist/apps/admin/_layout.js +3 -2
  156. package/dist/apps/admin/index.js +3 -2
  157. package/dist/apps/admin/ingest-queue/index.js +2 -1
  158. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  159. package/dist/apps/admin/quarantine/index.js +2 -1
  160. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  161. package/dist/apps/admin/signing-certificates/index.js +161 -0
  162. package/dist/apps/escrow/_layout.d.ts +5 -1
  163. package/dist/apps/escrow/_layout.js +3 -2
  164. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  165. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  166. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  167. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  168. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  169. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  170. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  171. package/dist/apps/shared/appearance/ColorField.js +40 -0
  172. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  173. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  174. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  175. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  176. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  177. package/dist/apps/shared/appearance/bootScript.js +33 -0
  178. package/dist/apps/shared/appearance/color.d.ts +65 -0
  179. package/dist/apps/shared/appearance/color.js +132 -0
  180. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  181. package/dist/apps/shared/appearance/photo.js +79 -0
  182. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  183. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  184. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  185. package/dist/apps/shared/appearance/theme.js +251 -0
  186. package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
  187. package/dist/apps/shared/auth/accountUrl.js +8 -0
  188. package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
  189. package/dist/apps/shared/auth/adminAccess.js +89 -0
  190. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  191. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  192. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  193. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  194. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  195. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  196. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +1 -1
  197. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  198. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  199. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  201. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
  202. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  203. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  204. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +6 -2
  205. package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
  206. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  207. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
  208. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  209. package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
  210. package/dist/apps/shared/components/layout/AppShell.js +122 -17
  211. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  212. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  213. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
  214. package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
  215. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  216. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  217. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  218. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  219. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  220. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  221. package/dist/apps/shared/components/layout/UserMenu.d.ts +25 -3
  222. package/dist/apps/shared/components/layout/UserMenu.js +88 -7
  223. package/dist/apps/shared/components/mail/ConversationList.js +5 -11
  224. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
  225. package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
  226. package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
  227. package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
  228. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
  229. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
  230. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
  231. package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
  232. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  233. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  234. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  235. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  236. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +38 -0
  237. package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
  238. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
  239. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
  240. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
  241. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
  242. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
  243. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
  244. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
  245. package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
  246. package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
  247. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  248. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  249. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
  250. package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
  251. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
  252. package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
  253. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  254. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  255. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  256. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  257. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  258. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  259. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  260. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  261. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  262. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  263. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  264. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  265. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  266. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  267. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  268. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  269. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  270. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  271. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  272. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  273. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  274. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  275. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  276. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  277. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  278. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  279. package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
  280. package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
  281. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  282. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  283. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  284. package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
  285. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
  286. package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
  287. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  288. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  289. package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
  290. package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
  291. package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
  292. package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
  293. package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
  294. package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
  295. package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
  296. package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
  297. package/dist/apps/shared/keyboard/dispatch.js +109 -0
  298. package/dist/apps/shared/keyboard/format.d.ts +12 -0
  299. package/dist/apps/shared/keyboard/format.js +74 -0
  300. package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
  301. package/dist/apps/shared/keyboard/keymap.js +84 -0
  302. package/dist/apps/shared/keyboard/match.d.ts +20 -0
  303. package/dist/apps/shared/keyboard/match.js +31 -0
  304. package/dist/apps/shared/keyboard/parse.d.ts +31 -0
  305. package/dist/apps/shared/keyboard/parse.js +107 -0
  306. package/dist/apps/shared/keyboard/platform.d.ts +15 -0
  307. package/dist/apps/shared/keyboard/platform.js +22 -0
  308. package/dist/apps/shared/keyboard/registry.d.ts +39 -0
  309. package/dist/apps/shared/keyboard/registry.js +33 -0
  310. package/dist/apps/shared/keyboard/targets.d.ts +14 -0
  311. package/dist/apps/shared/keyboard/targets.js +67 -0
  312. package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
  313. package/dist/apps/shared/keyboard/useShortcut.js +35 -0
  314. package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
  315. package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
  316. package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
  317. package/dist/apps/shared/mail/folderCounts.js +218 -0
  318. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  319. package/dist/apps/shared/mail/folderTree.js +116 -0
  320. package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
  321. package/dist/apps/shared/mail/listSnapshots.js +43 -0
  322. package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
  323. package/dist/apps/shared/mail/messageReadState.js +63 -0
  324. package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
  325. package/dist/apps/shared/mail/newMailNotifications.js +138 -0
  326. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  327. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  328. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  329. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  330. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  331. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  332. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  333. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  334. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  335. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  336. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  337. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  338. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  339. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  340. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  341. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  342. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  343. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  344. package/dist/apps/shared/mail/useMailConnection.d.ts +56 -0
  345. package/dist/apps/shared/mail/useMailConnection.js +138 -0
  346. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
  347. package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
  348. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
  349. package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
  350. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
  351. package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
  352. package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
  353. package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
  354. package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
  355. package/dist/apps/shared/navigation/AppRouter.js +242 -0
  356. package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
  357. package/dist/apps/shared/navigation/appHrefs.js +20 -0
  358. package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
  359. package/dist/apps/shared/navigation/frameContext.js +24 -0
  360. package/dist/apps/shared/navigation/idle.d.ts +14 -0
  361. package/dist/apps/shared/navigation/idle.js +43 -0
  362. package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
  363. package/dist/apps/shared/navigation/routerContext.js +56 -0
  364. package/dist/apps/shared/navigation/routes.d.ts +32 -0
  365. package/dist/apps/shared/navigation/routes.js +37 -0
  366. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  367. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  368. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  369. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  370. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  371. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  372. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  373. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  374. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  375. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  376. package/dist/apps/shared/notifications/store.d.ts +136 -0
  377. package/dist/apps/shared/notifications/store.js +416 -0
  378. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  379. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  380. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  381. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  382. package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
  383. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  384. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  385. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  386. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  387. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  388. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  389. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  390. package/dist/apps/shared/signing/signingInfo.js +48 -0
  391. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  392. package/dist/apps/shared/signing/useNow.js +18 -0
  393. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  394. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  395. package/dist/apps/shared/styles/app.css +108 -10
  396. package/dist/apps/www/_layout.d.ts +5 -1
  397. package/dist/apps/www/_layout.js +3 -2
  398. package/dist/apps/www/_routedPage.d.ts +12 -0
  399. package/dist/apps/www/_routedPage.js +19 -0
  400. package/dist/apps/www/_routes.d.ts +11 -0
  401. package/dist/apps/www/_routes.js +30 -0
  402. package/dist/apps/www/calendar/index.d.ts +2 -2
  403. package/dist/apps/www/calendar/index.js +41 -11
  404. package/dist/apps/www/contacts/[uid].d.ts +3 -9
  405. package/dist/apps/www/contacts/[uid].js +11 -9
  406. package/dist/apps/www/contacts/index.d.ts +2 -2
  407. package/dist/apps/www/contacts/index.js +30 -24
  408. package/dist/apps/www/index.d.ts +2 -2
  409. package/dist/apps/www/index.js +332 -48
  410. package/dist/apps/www/messages/[uid].d.ts +3 -7
  411. package/dist/apps/www/messages/[uid].js +10 -5
  412. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  413. package/dist/apps/www/settings/appearance/index.js +13 -0
  414. package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
  415. package/dist/apps/www/settings/auto-reply/index.js +6 -7
  416. package/dist/apps/www/settings/encryption/index.d.ts +2 -2
  417. package/dist/apps/www/settings/encryption/index.js +106 -96
  418. package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
  419. package/dist/apps/www/settings/filters/[uid].js +6 -2
  420. package/dist/apps/www/settings/filters/index.d.ts +2 -2
  421. package/dist/apps/www/settings/filters/index.js +3 -1
  422. package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
  423. package/dist/apps/www/settings/filters/new/index.js +9 -3
  424. package/dist/apps/www/settings/labels/index.d.ts +2 -2
  425. package/dist/apps/www/settings/labels/index.js +3 -1
  426. package/dist/apps/www/settings/privacy/index.d.ts +2 -2
  427. package/dist/apps/www/settings/privacy/index.js +8 -9
  428. package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
  429. package/dist/apps/www/settings/read-receipts/index.js +6 -7
  430. package/dist/apps/www/settings/sharing/index.d.ts +2 -2
  431. package/dist/apps/www/settings/sharing/index.js +22 -34
  432. package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
  433. package/dist/apps/www/settings/signatures/[uid].js +6 -2
  434. package/dist/apps/www/settings/signatures/index.d.ts +2 -2
  435. package/dist/apps/www/settings/signatures/index.js +3 -1
  436. package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
  437. package/dist/apps/www/settings/signatures/new/index.js +9 -4
  438. package/dist/apps/www/tasks/index.d.ts +2 -2
  439. package/dist/apps/www/tasks/index.js +22 -16
  440. package/package.json +2 -2
  441. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  442. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  443. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -1,262 +1,271 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React, { useState } from "react";
6
- import { HiChevronDown, HiChevronRight, HiOutlineFlag, HiOutlinePaperClip } from "react-icons/hi2";
7
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
- import { Message } from "@rapidmx/react-shared/mail/mailApi.js";
9
- import { formatMailAddress } from "@rapidmx/react-shared/mail/mailAddress.js";
10
- import MailAddress from "./MailAddress.js";
11
- import { ConversationSummary, listConversationMessages } from "@rapidmx/react-shared/mail/conversationsApi.js";
12
-
13
- export interface ConversationListProps {
14
- conversations: ConversationSummary[];
15
- /** The mailbox the conversations were listed from - `listConversationMessages()` is mailbox-scoped. */
16
- mailboxUid: string;
17
- /** The message currently open in the reading pane, so the row standing for it can be marked. */
18
- selectedUid: string | null;
19
- /**
20
- * Opens a conversation in the reading pane, positioned at one of its messages: the one a child row
21
- * stands for, or the conversation's own `latestMessageUid` for a parent row. The conversation goes
22
- * with the uid because the pane shows the whole thread, not just that message.
23
- */
24
- onOpenMessage: (conversation: ConversationSummary, uid: string) => void;
25
- /** Newer copies of messages this list already fetched - the reading pane marks the message it opens as
26
- * read, which this list would otherwise keep showing as unread until the thread is collapsed and
27
- * expanded again. */
28
- messageOverrides?: Record<string, Message>;
29
- /** Turns each parent row into a checkbox row: select mode here ticks whole conversations, since a
30
- * conversation is what this list's rows are. A child row stays a plain "open this message" button - a
31
- * mixed conversation/message selection has no sensible bulk semantics (see `MailListToolbar`). */
32
- selectMode?: boolean;
33
- /** The `conversationId`s currently ticked. */
34
- selectedConversationIds?: Set<string>;
35
- onToggleSelected?: (conversation: ConversationSummary) => void;
36
- /**
37
- * Whether a conversation's own messages read newest first, matching the order sense the rows themselves
38
- * are in ("Newest on top"). `listConversationMessages()` always answers oldest first - the order a
39
- * thread is read in - so this reverses that copy for display rather than asking for it differently.
40
- */
41
- newestFirst?: boolean;
42
- }
43
-
44
- /** Every participant as `Name <address>`, for the tooltip and the accessible text of the "+N" a crowded row folds them into. */
45
- function participantList(conversation: ConversationSummary): string {
46
- return conversation.participants.map((participant) => formatMailAddress(participant)).join(", ");
47
- }
48
-
49
- /**
50
- * The nested, Outlook-style conversation list: one parent row per conversation - its participants, subject,
51
- * message count, unread count, flag/attachment hints and the latest message's preview - expanding via its
52
- * own chevron into the conversation's individual messages as child rows, fetched on first expand with
53
- * `listConversationMessages()` (one request for the whole thread, across folders, oldest first).
54
- *
55
- * Opening a row opens the whole conversation in `ConversationThreadPane`, positioned at the message the row
56
- * stands for: the latest one for a parent row, that one for a child row.
57
- *
58
- * The chevron is a button of its own beside the row's own button rather than inside it (a button can't
59
- * nest inside a button), so expanding and opening are separately reachable by keyboard and each carries
60
- * its own name.
61
- */
62
- export default function ConversationList({
63
- conversations,
64
- mailboxUid,
65
- selectedUid,
66
- onOpenMessage,
67
- messageOverrides,
68
- selectMode,
69
- selectedConversationIds,
70
- onToggleSelected,
71
- newestFirst,
72
- }: ConversationListProps) {
73
- const [expanded, setExpanded] = useState<Set<string>>(new Set());
74
- const [messagesById, setMessagesById] = useState<Record<string, Message[]>>({});
75
- const [loadingIds, setLoadingIds] = useState<Set<string>>(new Set());
76
- const [errorsById, setErrorsById] = useState<Record<string, string>>({});
77
-
78
- function toggle(conversation: ConversationSummary) {
79
- const id = conversation.conversationId;
80
- setExpanded((prev) => {
81
- const next = new Set(prev);
82
- if (next.has(id)) {
83
- next.delete(id);
84
- } else {
85
- next.add(id);
86
- }
87
- return next;
88
- });
89
- if (expanded.has(id) || messagesById[id] || loadingIds.has(id)) {
90
- return;
91
- }
92
- setLoadingIds((prev) => new Set(prev).add(id));
93
- setErrorsById((prev) => {
94
- const next = { ...prev };
95
- delete next[id];
96
- return next;
97
- });
98
- listConversationMessages(mailboxUid, id)
99
- .then((loaded) => setMessagesById((prev) => ({ ...prev, [id]: loaded })))
100
- .catch((err) =>
101
- setErrorsById((prev) => ({
102
- ...prev,
103
- [id]: err instanceof ApiRequestError ? err.message : "Could not load this conversation's messages.",
104
- }))
105
- )
106
- .finally(() =>
107
- setLoadingIds((prev) => {
108
- const next = new Set(prev);
109
- next.delete(id);
110
- return next;
111
- })
112
- );
113
- }
114
-
115
- if (conversations.length === 0) {
116
- return <p className="p-4 text-sm text-text-muted">No conversations in this folder.</p>;
117
- }
118
-
119
- return (
120
- <ul>
121
- {conversations.map((conversation) => {
122
- const id = conversation.conversationId;
123
- const isExpanded = expanded.has(id);
124
- const unread = conversation.unreadCount > 0;
125
- const loaded = messagesById[id];
126
- // `listConversationMessages()` answers oldest first; the rows read in whichever sense the
127
- // list itself is arranged in. Copied before reversing - the fetched array is cached.
128
- const children = loaded && newestFirst ? [...loaded].reverse() : loaded;
129
- const panelId = `conversation-messages-${id}`;
130
- const ticked = selectedConversationIds?.has(id) ?? false;
131
- return (
132
- <li key={id}>
133
- <div
134
- className={[
135
- "flex items-stretch border-b border-border",
136
- conversation.latestMessageUid === selectedUid || ticked
137
- ? "bg-primary/10"
138
- : "hover:bg-surface-alt",
139
- ].join(" ")}
140
- >
141
- {selectMode && (
142
- <span className="shrink-0 flex items-center pl-3">
143
- <input
144
- type="checkbox"
145
- checked={ticked}
146
- onChange={() => onToggleSelected?.(conversation)}
147
- aria-label={`Select conversation: ${conversation.subject || "(no subject)"}`}
148
- className="w-4 h-4 accent-primary"
149
- />
150
- </span>
151
- )}
152
- <button
153
- type="button"
154
- onClick={() => toggle(conversation)}
155
- aria-expanded={isExpanded}
156
- aria-controls={panelId}
157
- aria-label={`${isExpanded ? "Collapse" : "Expand"} conversation: ${conversation.subject || "(no subject)"}`}
158
- className="shrink-0 px-2 text-text-muted hover:text-text"
159
- >
160
- {isExpanded ? (
161
- <HiChevronDown size={16} aria-hidden="true" />
162
- ) : (
163
- <HiChevronRight size={16} aria-hidden="true" />
164
- )}
165
- </button>
166
- <button
167
- type="button"
168
- onClick={() => onOpenMessage(conversation, conversation.latestMessageUid)}
169
- className={["flex-1 min-w-0 text-left pr-4 py-3", unread ? "font-semibold" : ""].join(
170
- " "
171
- )}
172
- >
173
- <div className="flex items-center justify-between gap-2 text-sm">
174
- {/* The first participant in full - name and address - and the rest as a count, with everyone's
175
- address in its tooltip; the latest sender when the summary lists none. */}
176
- <MailAddress recipient={conversation.participants[0] ?? conversation.latestFrom} className="flex-1" />
177
- {conversation.participants.length > 1 && (
178
- <span className="text-xs text-text-muted shrink-0 font-normal" title={participantList(conversation)}>
179
- +{conversation.participants.length - 1}
180
- <span className="sr-only"> more: {participantList(conversation)}</span>
181
- </span>
182
- )}
183
- <span className="text-xs text-text-muted shrink-0">
184
- {new Date(conversation.latestDate).toLocaleDateString()}
185
- </span>
186
- </div>
187
- <div className="text-sm truncate">{conversation.subject || "(no subject)"}</div>
188
- <div className="text-xs text-text-muted truncate font-normal">
189
- {conversation.latestPreview}
190
- </div>
191
- <div className="flex items-center gap-2 mt-1 text-xs text-text-muted font-normal">
192
- {conversation.messageCount > 1 && <span>{conversation.messageCount} messages</span>}
193
- {/* A one-message conversation's unread state is already the row's own
194
- bolding - counting it "1 unread" on every such row is just noise. */}
195
- {unread && conversation.messageCount > 1 && (
196
- <span className="py-0.5 px-2 rounded-pill bg-primary/10 text-primary-dark font-semibold">
197
- {conversation.unreadCount} unread
198
- </span>
199
- )}
200
- {conversation.hasAttachments && (
201
- <HiOutlinePaperClip size={13} aria-label="Has attachments" />
202
- )}
203
- {conversation.flagged && (
204
- <HiOutlineFlag size={13} aria-label="Flagged" className="text-danger" />
205
- )}
206
- </div>
207
- </button>
208
- </div>
209
- <ul id={panelId} hidden={!isExpanded}>
210
- {errorsById[id] && (
211
- <li role="alert" className="pl-8 pr-4 py-2 text-xs text-danger border-b border-border">
212
- {errorsById[id]}
213
- </li>
214
- )}
215
- {loadingIds.has(id) && (
216
- <li className="pl-8 pr-4 py-2 text-xs text-text-muted border-b border-border">
217
- Loading messages&hellip;
218
- </li>
219
- )}
220
- {children?.map((fetched) => {
221
- const message = messageOverrides?.[fetched.uid] ?? fetched;
222
- return (
223
- <li key={message.uid}>
224
- <button
225
- type="button"
226
- onClick={() => onOpenMessage(conversation, message.uid)}
227
- className={[
228
- "w-full text-left pl-8 pr-4 py-2 border-b border-border",
229
- message.uid === selectedUid ? "bg-primary/10" : "hover:bg-surface-alt",
230
- message.flags.read ? "" : "font-semibold",
231
- ].join(" ")}
232
- >
233
- <div className="flex items-center justify-between gap-2 text-sm">
234
- <MailAddress recipient={message.from} />
235
- <span className="text-xs text-text-muted shrink-0">
236
- {new Date(message.receivedDate).toLocaleDateString()}
237
- </span>
238
- </div>
239
- <div className="flex items-center gap-2 text-xs text-text-muted font-normal">
240
- <span className="truncate">{message.bodyPreview}</span>
241
- {message.hasAttachments && (
242
- <HiOutlinePaperClip size={12} aria-label="Has attachments" />
243
- )}
244
- {message.flags.flagged && (
245
- <HiOutlineFlag
246
- size={12}
247
- aria-label="Flagged"
248
- className="text-danger"
249
- />
250
- )}
251
- </div>
252
- </button>
253
- </li>
254
- );
255
- })}
256
- </ul>
257
- </li>
258
- );
259
- })}
260
- </ul>
261
- );
262
- }
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { useState } from "react";
6
+ import { HiChevronDown, HiChevronRight, HiOutlineFlag, HiOutlinePaperClip } from "react-icons/hi2";
7
+ import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
8
+ import { Message } from "@rapidmx/react-shared/mail/mailApi.js";
9
+ import { formatMailAddress } from "@rapidmx/react-shared/mail/mailAddress.js";
10
+ import MailAddress from "./MailAddress.js";
11
+ import { EncryptedPreview, conversationLooksEncrypted } from "./reading/EncryptedPreview.js";
12
+ import { ConversationSummary, listConversationMessages } from "@rapidmx/react-shared/mail/conversationsApi.js";
13
+ import { ROW_FOCUS_CLASS, UnreadBar, UnreadLabel, dateClass, isUnread, rowClass, senderClass, subjectClass } from "./unreadStyle.js";
14
+
15
+ export interface ConversationListProps {
16
+ conversations: ConversationSummary[];
17
+ /** The mailbox the conversations were listed from - `listConversationMessages()` is mailbox-scoped. */
18
+ mailboxUid: string;
19
+ /** The message currently open in the reading pane, so the row standing for it can be marked. */
20
+ selectedUid: string | null;
21
+ /**
22
+ * Opens a conversation in the reading pane, positioned at one of its messages: the one a child row
23
+ * stands for, or the conversation's own `latestMessageUid` for a parent row. The conversation goes
24
+ * with the uid because the pane shows the whole thread, not just that message.
25
+ */
26
+ onOpenMessage: (conversation: ConversationSummary, uid: string) => void;
27
+ /** Newer copies of messages this list already fetched - the reading pane marks the message it opens as
28
+ * read, which this list would otherwise keep showing as unread until the thread is collapsed and
29
+ * expanded again. */
30
+ messageOverrides?: Record<string, Message>;
31
+ /** Turns each parent row into a checkbox row: select mode here ticks whole conversations, since a
32
+ * conversation is what this list's rows are. A child row stays a plain "open this message" button - a
33
+ * mixed conversation/message selection has no sensible bulk semantics (see `MailListToolbar`). */
34
+ selectMode?: boolean;
35
+ /** The `conversationId`s currently ticked. */
36
+ selectedConversationIds?: Set<string>;
37
+ onToggleSelected?: (conversation: ConversationSummary) => void;
38
+ /**
39
+ * Whether a conversation's own messages read newest first, matching the order sense the rows themselves
40
+ * are in ("Newest on top"). `listConversationMessages()` always answers oldest first - the order a
41
+ * thread is read in - so this reverses that copy for display rather than asking for it differently.
42
+ */
43
+ newestFirst?: boolean;
44
+ }
45
+
46
+ /** Every participant as `Name <address>`, for the tooltip and the accessible text of the "+N" a crowded row folds them into. */
47
+ function participantList(conversation: ConversationSummary): string {
48
+ return conversation.participants.map((participant) => formatMailAddress(participant)).join(", ");
49
+ }
50
+
51
+ /**
52
+ * The nested, Outlook-style conversation list: one parent row per conversation - its participants, subject,
53
+ * message count, unread count, flag/attachment hints and the latest message's preview - expanding via its
54
+ * own chevron into the conversation's individual messages as child rows, fetched on first expand with
55
+ * `listConversationMessages()` (one request for the whole thread, across folders, oldest first).
56
+ *
57
+ * Opening a row opens the whole conversation in `ConversationThreadPane`, positioned at the message the row
58
+ * stands for: the latest one for a parent row, that one for a child row.
59
+ *
60
+ * The chevron is a button of its own beside the row's own button rather than inside it (a button can't
61
+ * nest inside a button), so expanding and opening are separately reachable by keyboard and each carries
62
+ * its own name.
63
+ */
64
+ export default function ConversationList({
65
+ conversations,
66
+ mailboxUid,
67
+ selectedUid,
68
+ onOpenMessage,
69
+ messageOverrides,
70
+ selectMode,
71
+ selectedConversationIds,
72
+ onToggleSelected,
73
+ newestFirst,
74
+ }: ConversationListProps) {
75
+ const [expanded, setExpanded] = useState<Set<string>>(new Set());
76
+ const [messagesById, setMessagesById] = useState<Record<string, Message[]>>({});
77
+ const [loadingIds, setLoadingIds] = useState<Set<string>>(new Set());
78
+ const [errorsById, setErrorsById] = useState<Record<string, string>>({});
79
+
80
+ function toggle(conversation: ConversationSummary) {
81
+ const id = conversation.conversationId;
82
+ setExpanded((prev) => {
83
+ const next = new Set(prev);
84
+ if (next.has(id)) {
85
+ next.delete(id);
86
+ } else {
87
+ next.add(id);
88
+ }
89
+ return next;
90
+ });
91
+ if (expanded.has(id) || messagesById[id] || loadingIds.has(id)) {
92
+ return;
93
+ }
94
+ setLoadingIds((prev) => new Set(prev).add(id));
95
+ setErrorsById((prev) => {
96
+ const next = { ...prev };
97
+ delete next[id];
98
+ return next;
99
+ });
100
+ listConversationMessages(mailboxUid, id)
101
+ .then((loaded) => setMessagesById((prev) => ({ ...prev, [id]: loaded })))
102
+ .catch((err) =>
103
+ setErrorsById((prev) => ({
104
+ ...prev,
105
+ [id]: err instanceof ApiRequestError ? err.message : "Could not load this conversation's messages.",
106
+ }))
107
+ )
108
+ .finally(() =>
109
+ setLoadingIds((prev) => {
110
+ const next = new Set(prev);
111
+ next.delete(id);
112
+ return next;
113
+ })
114
+ );
115
+ }
116
+
117
+ if (conversations.length === 0) {
118
+ return <p className="p-4 text-sm text-text-muted">No conversations in this folder.</p>;
119
+ }
120
+
121
+ return (
122
+ <ul>
123
+ {conversations.map((conversation) => {
124
+ const id = conversation.conversationId;
125
+ const isExpanded = expanded.has(id);
126
+ const unread = conversation.unreadCount > 0;
127
+ const loaded = messagesById[id];
128
+ // `listConversationMessages()` answers oldest first; the rows read in whichever sense the
129
+ // list itself is arranged in. Copied before reversing - the fetched array is cached.
130
+ const children = loaded && newestFirst ? [...loaded].reverse() : loaded;
131
+ const panelId = `conversation-messages-${id}`;
132
+ const ticked = selectedConversationIds?.has(id) ?? false;
133
+ return (
134
+ <li key={id}>
135
+ <div
136
+ data-message-uid={conversation.latestMessageUid}
137
+ data-unread={unread ? "true" : undefined}
138
+ className={rowClass({ unread, selected: conversation.latestMessageUid === selectedUid || ticked }, "flex items-stretch")}
139
+ >
140
+ <UnreadBar unread={unread} />
141
+ {selectMode && (
142
+ <span className="shrink-0 flex items-center pl-3">
143
+ <input
144
+ type="checkbox"
145
+ checked={ticked}
146
+ onChange={() => onToggleSelected?.(conversation)}
147
+ aria-label={`Select conversation: ${conversation.subject || "(no subject)"}`}
148
+ className="w-4 h-4 accent-primary"
149
+ />
150
+ </span>
151
+ )}
152
+ <button
153
+ type="button"
154
+ onClick={() => toggle(conversation)}
155
+ aria-expanded={isExpanded}
156
+ aria-controls={panelId}
157
+ aria-label={`${isExpanded ? "Collapse" : "Expand"} conversation: ${conversation.subject || "(no subject)"}`}
158
+ className={["shrink-0 px-2 text-text-muted hover:text-text", ROW_FOCUS_CLASS].join(" ")}
159
+ >
160
+ {isExpanded ? (
161
+ <HiChevronDown size={16} aria-hidden="true" />
162
+ ) : (
163
+ <HiChevronRight size={16} aria-hidden="true" />
164
+ )}
165
+ </button>
166
+ <button
167
+ type="button"
168
+ data-row-open
169
+ onClick={() => onOpenMessage(conversation, conversation.latestMessageUid)}
170
+ className={["flex-1 min-w-0 text-left pr-4 py-3", ROW_FOCUS_CLASS].join(" ")}
171
+ >
172
+ <UnreadLabel unread={unread} />
173
+ <div className="flex items-center justify-between gap-2 text-sm">
174
+ {/* The first participant in full - name and address - and the rest as a count, with everyone's
175
+ address in its tooltip; the latest sender when the summary lists none. */}
176
+ <MailAddress
177
+ recipient={conversation.participants[0] ?? conversation.latestFrom}
178
+ className={["flex-1", senderClass(unread)].join(" ")}
179
+ />
180
+ {conversation.participants.length > 1 && (
181
+ <span className="text-xs text-text-muted shrink-0 font-normal" title={participantList(conversation)}>
182
+ +{conversation.participants.length - 1}
183
+ <span className="sr-only"> more: {participantList(conversation)}</span>
184
+ </span>
185
+ )}
186
+ <span className={["text-xs shrink-0", dateClass(unread)].join(" ")}>
187
+ {new Date(conversation.latestDate).toLocaleDateString()}
188
+ </span>
189
+ </div>
190
+ <div className={["text-sm truncate", subjectClass(unread)].join(" ")}>{conversation.subject || "(no subject)"}</div>
191
+ <div className="text-xs text-text-muted truncate font-normal">
192
+ {/* An encrypted latest message has no preview (the server never had its plaintext): say so, with a lock, not nothing. */}
193
+ {conversationLooksEncrypted(conversation) ? <EncryptedPreview /> : conversation.latestPreview}
194
+ </div>
195
+ <div className="flex items-center gap-2 mt-1 text-xs text-text-muted font-normal">
196
+ {conversation.messageCount > 1 && <span>{conversation.messageCount} messages</span>}
197
+ {/* A one-message conversation's unread state is already the row's own
198
+ bolding - counting it "1 unread" on every such row is just noise. */}
199
+ {unread && conversation.messageCount > 1 && (
200
+ <span className="py-0.5 px-2 rounded-pill bg-primary/15 text-primary-dark font-bold">
201
+ {conversation.unreadCount} unread
202
+ </span>
203
+ )}
204
+ {conversation.hasAttachments && (
205
+ <HiOutlinePaperClip size={13} aria-label="Has attachments" />
206
+ )}
207
+ {conversation.flagged && (
208
+ <HiOutlineFlag size={13} aria-label="Flagged" className="text-danger" />
209
+ )}
210
+ </div>
211
+ </button>
212
+ </div>
213
+ <ul id={panelId} hidden={!isExpanded}>
214
+ {errorsById[id] && (
215
+ <li role="alert" className="pl-8 pr-4 py-2 text-xs text-danger border-b border-border">
216
+ {errorsById[id]}
217
+ </li>
218
+ )}
219
+ {loadingIds.has(id) && (
220
+ <li className="pl-8 pr-4 py-2 text-xs text-text-muted border-b border-border">
221
+ Loading messages&hellip;
222
+ </li>
223
+ )}
224
+ {children?.map((fetched) => {
225
+ const message = messageOverrides?.[fetched.uid] ?? fetched;
226
+ const messageUnread = isUnread(message);
227
+ return (
228
+ <li
229
+ key={message.uid}
230
+ data-message-uid={message.uid}
231
+ data-unread={messageUnread ? "true" : undefined}
232
+ className={rowClass({ unread: messageUnread, selected: message.uid === selectedUid })}
233
+ >
234
+ <UnreadBar unread={messageUnread} />
235
+ <button
236
+ type="button"
237
+ data-row-open
238
+ onClick={() => onOpenMessage(conversation, message.uid)}
239
+ className={["w-full text-left pl-8 pr-4 py-2", ROW_FOCUS_CLASS].join(" ")}
240
+ >
241
+ <UnreadLabel unread={messageUnread} />
242
+ <div className="flex items-center justify-between gap-2 text-sm">
243
+ <MailAddress recipient={message.from} className={senderClass(messageUnread)} />
244
+ <span className={["text-xs shrink-0", dateClass(messageUnread)].join(" ")}>
245
+ {new Date(message.receivedDate).toLocaleDateString()}
246
+ </span>
247
+ </div>
248
+ <div className="flex items-center gap-2 text-xs text-text-muted font-normal">
249
+ <span className="truncate">{message.bodyPreview || (message.encrypted ? <EncryptedPreview /> : null)}</span>
250
+ {message.hasAttachments && (
251
+ <HiOutlinePaperClip size={12} aria-label="Has attachments" />
252
+ )}
253
+ {message.flags.flagged && (
254
+ <HiOutlineFlag
255
+ size={12}
256
+ aria-label="Flagged"
257
+ className="text-danger"
258
+ />
259
+ )}
260
+ </div>
261
+ </button>
262
+ </li>
263
+ );
264
+ })}
265
+ </ul>
266
+ </li>
267
+ );
268
+ })}
269
+ </ul>
270
+ );
271
+ }