@rapidmx/web-client 0.14.1 → 0.16.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 (314) hide show
  1. package/README.md +373 -373
  2. package/apps/admin/branding/index.tsx +39 -39
  3. package/apps/admin/data-requests/index.tsx +482 -480
  4. package/apps/admin/domains/[uid].tsx +164 -163
  5. package/apps/admin/escrow-scopes/[uid].tsx +348 -348
  6. package/apps/admin/index.tsx +127 -124
  7. package/apps/admin/mailboxes/[uid].tsx +269 -224
  8. package/apps/admin/mailboxes/new/index.tsx +28 -28
  9. package/apps/admin/plugins/index.tsx +15 -15
  10. package/apps/admin/retention-policy/index.tsx +39 -39
  11. package/apps/admin/signing-certificates/index.tsx +343 -343
  12. package/apps/escrow/audit-log/index.tsx +196 -196
  13. package/apps/escrow/matters/[uid].tsx +621 -621
  14. package/apps/shared/appearance/AppearanceForm.tsx +9 -1
  15. package/apps/shared/auth/adminAccess.ts +99 -99
  16. package/apps/shared/calendar/bookingPlugin.ts +100 -0
  17. package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
  18. package/apps/shared/components/admin/layout/AdminShell.tsx +378 -346
  19. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
  20. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
  21. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
  22. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  23. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  24. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +53 -4
  25. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +64 -10
  27. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  28. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  29. package/apps/shared/components/admin/setup/SetupWizard.tsx +444 -444
  30. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  31. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
  32. package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
  33. package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
  34. package/apps/shared/components/calendar/EventDetails.tsx +401 -0
  35. package/apps/shared/components/calendar/EventEditor.tsx +635 -0
  36. package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
  37. package/apps/shared/components/calendar/EventFormParts.tsx +591 -0
  38. package/apps/shared/components/calendar/EventModal.tsx +206 -946
  39. package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
  40. package/apps/shared/components/calendar/EventShell.tsx +202 -0
  41. package/apps/shared/components/calendar/FindATime.tsx +379 -0
  42. package/apps/shared/components/calendar/GuestInput.tsx +100 -0
  43. package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
  44. package/apps/shared/components/calendar/MonthView.tsx +185 -173
  45. package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
  46. package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
  47. package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
  48. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -222
  49. package/apps/shared/components/calendar/RequestChangeForm.tsx +240 -0
  50. package/apps/shared/components/calendar/SplitDayView.tsx +144 -143
  51. package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
  52. package/apps/shared/components/calendar/TimeGridView.tsx +246 -244
  53. package/apps/shared/components/calendar/allDay.ts +124 -124
  54. package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
  55. package/apps/shared/components/calendar/eventForm.ts +108 -0
  56. package/apps/shared/components/calendar/eventFormat.ts +210 -0
  57. package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
  58. package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
  59. package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
  60. package/apps/shared/components/contacts/ContactForm.tsx +383 -383
  61. package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
  62. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
  63. package/apps/shared/components/contacts/layout/ContactsShell.tsx +217 -216
  64. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  65. package/apps/shared/components/layout/AppShell.tsx +480 -480
  66. package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
  67. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  68. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
  69. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  70. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  71. package/apps/shared/components/layout/UserMenu.tsx +439 -372
  72. package/apps/shared/components/mail/ConversationList.tsx +323 -271
  73. package/apps/shared/components/mail/ConversationThreadPane.tsx +130 -5
  74. package/apps/shared/components/mail/InviteCard.tsx +387 -0
  75. package/apps/shared/components/mail/MailSelectionBar.tsx +236 -227
  76. package/apps/shared/components/mail/MessageDetailPane.tsx +1559 -1537
  77. package/apps/shared/components/mail/SwipeRow.tsx +120 -0
  78. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
  79. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  80. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1673
  81. package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
  82. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  83. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  84. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  85. package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
  86. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
  87. package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
  88. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
  89. package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
  90. package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
  91. package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
  92. package/apps/shared/components/mail/layout/MailShell.tsx +546 -490
  93. package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
  94. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
  95. package/apps/shared/components/mail/verificationSeals.ts +125 -125
  96. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  97. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +255 -253
  99. package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +218 -217
  101. package/apps/shared/gestures/useEnterSlide.ts +54 -0
  102. package/apps/shared/gestures/useSwipe.ts +114 -0
  103. package/apps/shared/gestures/useSwipeSlide.ts +113 -0
  104. package/apps/shared/gestures/useWheelPaging.ts +107 -0
  105. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  106. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  107. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  108. package/apps/shared/keyboard/dispatch.ts +124 -124
  109. package/apps/shared/keyboard/format.ts +89 -89
  110. package/apps/shared/keyboard/keymap.ts +114 -114
  111. package/apps/shared/keyboard/registry.ts +65 -65
  112. package/apps/shared/keyboard/targets.ts +79 -79
  113. package/apps/shared/mail/folderCounts.ts +17 -0
  114. package/apps/shared/mail/folderTree.ts +143 -143
  115. package/apps/shared/mail/listAllPages.ts +39 -39
  116. package/apps/shared/mail/newMailNotifications.ts +3 -15
  117. package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
  118. package/apps/shared/mail/outbox/sendJob.ts +19 -1
  119. package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
  120. package/apps/shared/mail/primaryMailbox.ts +71 -0
  121. package/apps/shared/mail/useCollapsedSections.ts +146 -0
  122. package/apps/shared/mail/useMailConnection.ts +205 -203
  123. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  124. package/apps/shared/mail/useMarkMessageRead.ts +47 -47
  125. package/apps/shared/mail/useNewMailNotifications.ts +2 -2
  126. package/apps/shared/navigation/AppRouter.tsx +300 -300
  127. package/apps/shared/navigation/routerContext.tsx +83 -83
  128. package/apps/shared/notifications/preferences.ts +36 -0
  129. package/apps/shared/notifications/store.ts +9 -0
  130. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
  131. package/apps/shared/search/crossMailboxSearch.ts +169 -0
  132. package/apps/shared/search/localIndexBuilder.ts +481 -481
  133. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  134. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  135. package/apps/shared/signing/enrollmentView.ts +251 -251
  136. package/apps/shared/signing/useNow.ts +19 -19
  137. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
  138. package/apps/shared/styles/app.css +396 -386
  139. package/apps/www/_routes.ts +1 -0
  140. package/apps/www/calendar/index.tsx +578 -469
  141. package/apps/www/contacts/[uid].tsx +109 -109
  142. package/apps/www/contacts/index.tsx +6 -0
  143. package/apps/www/index.tsx +2847 -2332
  144. package/apps/www/messages/[uid].tsx +135 -108
  145. package/apps/www/settings/auto-reply/index.tsx +133 -133
  146. package/apps/www/settings/encryption/index.tsx +1287 -1287
  147. package/apps/www/settings/filters/[uid].tsx +176 -176
  148. package/apps/www/settings/filters/index.tsx +102 -102
  149. package/apps/www/settings/filters/new/index.tsx +140 -140
  150. package/apps/www/settings/labels/index.tsx +204 -204
  151. package/apps/www/settings/privacy/index.tsx +492 -492
  152. package/apps/www/settings/profile/index.tsx +248 -0
  153. package/apps/www/settings/read-receipts/index.tsx +147 -147
  154. package/apps/www/settings/sharing/index.tsx +256 -256
  155. package/apps/www/settings/signatures/[uid].tsx +172 -172
  156. package/apps/www/settings/signatures/index.tsx +88 -88
  157. package/apps/www/settings/signatures/new/index.tsx +135 -135
  158. package/apps/www/tasks/index.tsx +649 -649
  159. package/dist/apps/admin/data-requests/index.js +1 -1
  160. package/dist/apps/admin/domains/[uid].js +4 -2
  161. package/dist/apps/admin/index.js +2 -1
  162. package/dist/apps/admin/mailboxes/[uid].js +12 -2
  163. package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
  164. package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
  165. package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
  166. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
  167. package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
  168. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  169. package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
  170. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
  171. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
  172. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
  173. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
  174. package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +28 -4
  176. package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
  177. package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
  178. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
  179. package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
  180. package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
  181. package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
  182. package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
  183. package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
  184. package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
  185. package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
  186. package/dist/apps/shared/components/calendar/EventEditor.js +544 -0
  187. package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
  188. package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
  189. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +70 -0
  190. package/dist/apps/shared/components/calendar/EventFormParts.js +191 -0
  191. package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
  192. package/dist/apps/shared/components/calendar/EventModal.js +57 -459
  193. package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
  194. package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
  195. package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
  196. package/dist/apps/shared/components/calendar/EventShell.js +134 -0
  197. package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
  198. package/dist/apps/shared/components/calendar/FindATime.js +190 -0
  199. package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
  200. package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
  201. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
  202. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
  203. package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
  204. package/dist/apps/shared/components/calendar/MonthView.js +11 -5
  205. package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
  206. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
  207. package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
  208. package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
  209. package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
  210. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
  211. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  212. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
  213. package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
  214. package/dist/apps/shared/components/calendar/RequestChangeForm.js +127 -0
  215. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
  216. package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
  217. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
  218. package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
  219. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
  220. package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
  221. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
  222. package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
  223. package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
  224. package/dist/apps/shared/components/calendar/eventForm.js +1 -0
  225. package/dist/apps/shared/components/calendar/eventFormat.d.ts +80 -0
  226. package/dist/apps/shared/components/calendar/eventFormat.js +190 -0
  227. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
  228. package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
  229. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -2
  230. package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
  231. package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
  232. package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
  233. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  234. package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
  235. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -3
  236. package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
  237. package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
  238. package/dist/apps/shared/components/layout/UserMenu.d.ts +14 -9
  239. package/dist/apps/shared/components/layout/UserMenu.js +74 -26
  240. package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
  241. package/dist/apps/shared/components/mail/ConversationList.js +21 -4
  242. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
  243. package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
  244. package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
  245. package/dist/apps/shared/components/mail/InviteCard.js +150 -0
  246. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -1
  247. package/dist/apps/shared/components/mail/MailSelectionBar.js +3 -3
  248. package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
  249. package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
  250. package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
  251. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +16 -7
  252. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
  253. package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
  254. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
  255. package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
  256. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
  257. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
  258. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
  259. package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
  260. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
  261. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
  262. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
  263. package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
  264. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
  265. package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
  266. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
  267. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
  268. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +10 -0
  269. package/dist/apps/shared/components/mail/layout/MailShell.js +76 -46
  270. package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
  271. package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
  272. package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
  273. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
  274. package/dist/apps/shared/components/settings/layout/SettingsShell.js +7 -5
  275. package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
  276. package/dist/apps/shared/components/tasks/layout/TasksShell.js +4 -3
  277. package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
  278. package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
  279. package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
  280. package/dist/apps/shared/gestures/useSwipe.js +90 -0
  281. package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
  282. package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
  283. package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
  284. package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
  285. package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
  286. package/dist/apps/shared/mail/folderCounts.js +12 -0
  287. package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
  288. package/dist/apps/shared/mail/newMailNotifications.js +3 -12
  289. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
  290. package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
  291. package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
  292. package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
  293. package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
  294. package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
  295. package/dist/apps/shared/mail/primaryMailbox.js +61 -0
  296. package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
  297. package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
  298. package/dist/apps/shared/mail/useMailConnection.js +3 -1
  299. package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
  300. package/dist/apps/shared/notifications/preferences.d.ts +13 -0
  301. package/dist/apps/shared/notifications/preferences.js +36 -0
  302. package/dist/apps/shared/notifications/store.d.ts +1 -0
  303. package/dist/apps/shared/notifications/store.js +7 -23
  304. package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
  305. package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
  306. package/dist/apps/shared/styles/app.css +396 -386
  307. package/dist/apps/www/_routes.js +1 -0
  308. package/dist/apps/www/calendar/index.js +78 -7
  309. package/dist/apps/www/contacts/index.js +3 -1
  310. package/dist/apps/www/index.js +548 -210
  311. package/dist/apps/www/messages/[uid].js +13 -5
  312. package/dist/apps/www/settings/profile/index.d.ts +4 -0
  313. package/dist/apps/www/settings/profile/index.js +124 -0
  314. package/package.json +2 -2
@@ -0,0 +1,120 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { validDate } from "./inviteFormat.js";
3
+ /** How tall one hour is drawn, in px. */
4
+ const HOUR_HEIGHT = 36;
5
+ /** The room the hour labels take at the left, as a CSS length. */
6
+ const LABEL_WIDTH = "3.25rem";
7
+ /** The hours of context shown before the invitation and after it, and the least the day view is tall (in hours). */
8
+ const CONTEXT_HOURS = 2;
9
+ const MIN_HOURS = 5;
10
+ const MINUTE_MS = 60000;
11
+ /**
12
+ * Lays overlapping blocks side by side: blocks that overlap (directly or through a chain of others) form a cluster, and each block takes the first
13
+ * column of the cluster that is free where it starts. `blocks` must be sorted by start; the result is in the same order.
14
+ */
15
+ export function placeBlocks(blocks) {
16
+ const placed = [];
17
+ let cluster = [];
18
+ let columnEnds = [];
19
+ let clusterEnd = -Infinity;
20
+ const close = () => {
21
+ for (const member of cluster) {
22
+ placed[member.index] = { column: member.column, columns: columnEnds.length };
23
+ }
24
+ cluster = [];
25
+ columnEnds = [];
26
+ };
27
+ blocks.forEach((block, index) => {
28
+ if (block.start >= clusterEnd) {
29
+ close();
30
+ }
31
+ let column = columnEnds.findIndex((end) => end <= block.start);
32
+ if (column === -1) {
33
+ column = columnEnds.length;
34
+ }
35
+ columnEnds[column] = block.end;
36
+ cluster.push({ index, column });
37
+ clusterEnd = Math.max(clusterEnd, block.end);
38
+ });
39
+ close();
40
+ return placed;
41
+ }
42
+ /** The hours the day view spans: the invitation's own, with `CONTEXT_HOURS` either side (at least `MIN_HOURS`), kept inside the invitation's day. */
43
+ function hourWindow(start, end) {
44
+ const sameDay = start.toDateString() === end.toDateString();
45
+ const endHour = sameDay ? Math.ceil(end.getHours() + end.getMinutes() / 60) : 24;
46
+ let from = Math.max(0, start.getHours() - CONTEXT_HOURS);
47
+ let to = Math.min(24, endHour + CONTEXT_HOURS);
48
+ if (to - from < MIN_HOURS) {
49
+ to = Math.min(24, from + MIN_HOURS);
50
+ from = Math.max(0, to - MIN_HOURS);
51
+ }
52
+ return { from, to };
53
+ }
54
+ const BLOCK_CLASS = {
55
+ invite: "bg-primary/20 border-primary text-text font-semibold",
56
+ conflict: "bg-danger-bg border-danger text-danger",
57
+ busy: "bg-surface border-border text-text",
58
+ free: "bg-transparent border-border text-text-muted",
59
+ };
60
+ const CLOCK = new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" });
61
+ const HOUR = new Intl.DateTimeFormat(undefined, { hour: "numeric" });
62
+ /**
63
+ * A small day agenda around an invitation, as Outlook draws it in its RSVP card: hour rows for the invitation's day (from a couple of hours before it
64
+ * to a couple after), the invitation itself as a highlighted block, and the reader's other events as blocks - the ones that overlap it marked in red
65
+ * (and named as conflicts to a screen reader). Overlapping blocks stand side by side. All-day events are listed above the hours rather than drawn.
66
+ * Draws nothing when the invitation has no usable time.
67
+ */
68
+ export default function InviteTimeline({ invite }) {
69
+ const start = validDate(invite.startDate);
70
+ const end = validDate(invite.endDate);
71
+ if (!start || !end || end <= start) {
72
+ return null;
73
+ }
74
+ const { from, to } = hourWindow(start, end);
75
+ const windowStart = new Date(start.getFullYear(), start.getMonth(), start.getDate(), from).getTime();
76
+ const windowEnd = new Date(start.getFullYear(), start.getMonth(), start.getDate(), to).getTime();
77
+ const conflictUids = new Set(invite.conflicts.map((entry) => entry.uid));
78
+ // A conflict the schedule doesn't list is still drawn.
79
+ const entries = [...invite.schedule, ...invite.conflicts.filter((c) => !invite.schedule.some((s) => s.uid === c.uid))];
80
+ const allDay = entries.filter((entry) => entry.allDay);
81
+ const blocks = [{ key: "invite", title: invite.summary?.trim() || "(no title)", start: start.getTime(), end: end.getTime(), kind: "invite", tentative: false }];
82
+ for (const entry of entries) {
83
+ const entryStart = validDate(entry.startDate)?.getTime();
84
+ const entryEnd = validDate(entry.endDate)?.getTime();
85
+ if (entry.allDay || entryStart === undefined || entryEnd === undefined) {
86
+ continue;
87
+ }
88
+ blocks.push({
89
+ key: entry.uid,
90
+ title: entry.title || "(no title)",
91
+ start: entryStart,
92
+ end: entryEnd,
93
+ kind: conflictUids.has(entry.uid) ? "conflict" : entry.busy ? "busy" : "free",
94
+ tentative: entry.tentative,
95
+ });
96
+ }
97
+ // Clipped to the hours shown; a block entirely outside them isn't drawn.
98
+ const visible = blocks
99
+ .map((block) => ({ ...block, start: Math.max(block.start, windowStart), end: Math.min(block.end, windowEnd), fullStart: block.start, fullEnd: block.end }))
100
+ .filter((block) => block.end > block.start)
101
+ .sort((a, b) => a.start - b.start || a.end - b.end);
102
+ const placed = placeBlocks(visible);
103
+ const hours = Array.from({ length: to - from }, (_, i) => from + i);
104
+ return (_jsxs("div", { "aria-label": "Your schedule around this meeting", role: "group", className: "text-xs", children: [allDay.length > 0 && (_jsx("ul", { "aria-label": "All-day events", className: "mb-1 flex flex-col gap-0.5", children: allDay.map((entry) => (_jsxs("li", { className: "px-2 py-0.5 rounded-sm border border-border bg-surface text-text-muted truncate", children: ["All day: ", entry.title || "(no title)"] }, entry.uid))) })), _jsxs("div", { className: "relative border-t border-border", style: { height: hours.length * HOUR_HEIGHT }, children: [hours.map((hour) => (_jsx("div", { "aria-hidden": "true", className: "absolute inset-x-0 border-b border-border text-text-muted", style: { top: (hour - from) * HOUR_HEIGHT, height: HOUR_HEIGHT }, children: _jsx("span", { className: "absolute left-0 top-0 pr-1 leading-none pt-0.5", style: { width: LABEL_WIDTH }, children: HOUR.format(new Date(2000, 0, 1, hour)) }) }, hour))), _jsx("ul", { "aria-label": "Events", children: visible.map((block, index) => {
105
+ const { column, columns } = placed[index];
106
+ const top = ((block.start - windowStart) / MINUTE_MS / 60) * HOUR_HEIGHT;
107
+ const height = Math.max(18, ((block.end - block.start) / MINUTE_MS / 60) * HOUR_HEIGHT - 1);
108
+ const range = `${CLOCK.format(block.fullStart)} to ${CLOCK.format(block.fullEnd)}`;
109
+ return (_jsxs("li", { title: `${block.title}, ${range}`, "data-kind": block.kind, className: [
110
+ "absolute overflow-hidden rounded-sm border px-1.5 py-0.5 leading-tight",
111
+ BLOCK_CLASS[block.kind],
112
+ block.tentative ? "border-dashed" : "",
113
+ ].join(" "), style: {
114
+ top,
115
+ height,
116
+ left: `calc(${LABEL_WIDTH} + (100% - ${LABEL_WIDTH}) * ${column} / ${columns})`,
117
+ width: `calc((100% - ${LABEL_WIDTH}) / ${columns} - 2px)`,
118
+ }, children: [_jsx("span", { className: "block truncate", children: block.title }), _jsxs("span", { className: "sr-only", children: [", ", range, block.kind === "invite" ? ", this meeting" : "", block.kind === "conflict" ? ", conflicts with this meeting" : "", block.tentative ? ", tentative" : ""] })] }, block.key));
119
+ }) })] })] }));
120
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import type { MessageInvite, ProposedTime } from "@rapidmx/react-shared/calendar/inviteApi.js";
3
+ export interface ProposeTimeFormProps {
4
+ /** The invitation being answered: its own time (in the reader's zone) is what the fields start out as. */
5
+ invite: Pick<MessageInvite, "startDate" | "endDate">;
6
+ /** A proposal is on its way: the form is off. */
7
+ busy?: boolean;
8
+ /** Called with the proposed instants once the fields are valid. The caller reports a failure and closes the form on success. */
9
+ onSubmit: (proposal: ProposedTime) => void;
10
+ onCancel: () => void;
11
+ }
12
+ /**
13
+ * The small form behind "Propose new time": a date and a start and end time - in the reader's own time zone, prefilled with the invitation's own
14
+ * time - and an optional note to the organizer. It validates what it can (all three fields, and an end after the start) before anything is sent.
15
+ * The end is on the start's day: a proposal spans one day, as Outlook's does from a list.
16
+ */
17
+ export default function ProposeTimeForm({ invite, busy, onSubmit, onCancel }: ProposeTimeFormProps): React.JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ ///////////////////////////////////////////////////////////////////////////////
3
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
4
+ // SPDX-License-Identifier: MPL-2.0
5
+ ///////////////////////////////////////////////////////////////////////////////
6
+ import { useId, useState } from "react";
7
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
8
+ import { isoDay, validDate } from "./inviteFormat.js";
9
+ const INPUT_CLASS = "w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface text-text";
10
+ /** `HH:MM` of an instant in the reader's zone, the value of a time input. */
11
+ function clock(date) {
12
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
13
+ }
14
+ /**
15
+ * The small form behind "Propose new time": a date and a start and end time - in the reader's own time zone, prefilled with the invitation's own
16
+ * time - and an optional note to the organizer. It validates what it can (all three fields, and an end after the start) before anything is sent.
17
+ * The end is on the start's day: a proposal spans one day, as Outlook's does from a list.
18
+ */
19
+ export default function ProposeTimeForm({ invite, busy = false, onSubmit, onCancel }) {
20
+ const start = validDate(invite.startDate);
21
+ const end = validDate(invite.endDate);
22
+ const [date, setDate] = useState(start ? isoDay(start) : "");
23
+ const [startTime, setStartTime] = useState(start ? clock(start) : "");
24
+ const [endTime, setEndTime] = useState(end ? clock(end) : "");
25
+ const [comment, setComment] = useState("");
26
+ const [error, setError] = useState(null);
27
+ const id = useId();
28
+ function submit(event) {
29
+ event.preventDefault();
30
+ if (!date || !startTime || !endTime) {
31
+ setError("Choose a date and a start and end time.");
32
+ return;
33
+ }
34
+ const proposedStart = new Date(`${date}T${startTime}:00`);
35
+ const proposedEnd = new Date(`${date}T${endTime}:00`);
36
+ if (proposedEnd <= proposedStart) {
37
+ setError("The end time must be after the start time.");
38
+ return;
39
+ }
40
+ setError(null);
41
+ const note = comment.trim();
42
+ onSubmit({ startDate: proposedStart.toISOString(), endDate: proposedEnd.toISOString(), ...(note ? { comment: note } : {}) });
43
+ }
44
+ return (_jsxs("form", { "aria-label": "Propose a new time", onSubmit: submit, noValidate: true, className: "flex flex-col gap-2 text-sm", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs("div", { className: "flex-1 min-w-[9rem]", children: [_jsx("label", { htmlFor: `${id}-date`, className: "block text-xs text-text-muted mb-0.5", children: "Date" }), _jsx("input", { id: `${id}-date`, type: "date", value: date, disabled: busy, onChange: (e) => setDate(e.target.value), className: INPUT_CLASS })] }), _jsxs("div", { className: "flex-1 min-w-[6rem]", children: [_jsx("label", { htmlFor: `${id}-start`, className: "block text-xs text-text-muted mb-0.5", children: "Start" }), _jsx("input", { id: `${id}-start`, type: "time", value: startTime, disabled: busy, onChange: (e) => setStartTime(e.target.value), className: INPUT_CLASS })] }), _jsxs("div", { className: "flex-1 min-w-[6rem]", children: [_jsx("label", { htmlFor: `${id}-end`, className: "block text-xs text-text-muted mb-0.5", children: "End" }), _jsx("input", { id: `${id}-end`, type: "time", value: endTime, disabled: busy, onChange: (e) => setEndTime(e.target.value), className: INPUT_CLASS })] })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: `${id}-comment`, className: "block text-xs text-text-muted mb-0.5", children: "Comment (optional)" }), _jsx("textarea", { id: `${id}-comment`, rows: 2, value: comment, disabled: busy, onChange: (e) => setComment(e.target.value), className: `${INPUT_CLASS} resize-y` })] }), error && (_jsx("p", { role: "alert", className: "text-danger", children: error })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "submit", className: "!w-auto", loading: busy, disabled: busy, "aria-busy": busy || undefined, children: "Send proposal" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: busy, onClick: onCancel, children: "Cancel" })] })] }));
45
+ }
@@ -0,0 +1,28 @@
1
+ import type { Attachment } from "@rapidmx/react-shared/mail/mailApi.js";
2
+ import type { InviteResponse, MessageInvite } from "@rapidmx/react-shared/calendar/inviteApi.js";
3
+ /** Whether an attachment is a calendar file - what tells the pane a message may carry an invitation worth asking the server about. By type
4
+ * or by name, since mail clients disagree about which one they set (`text/calendar`, `application/ics`, or a bare `.ics` as octet-stream). */
5
+ export declare function isCalendarAttachment(attachment: Pick<Attachment, "filename" | "mimeType">): boolean;
6
+ /** `zone` if the browser knows it, else UTC - an all-day date is a calendar day, and needs a fixed zone to stay the same day for every reader. */
7
+ export declare function allDayZone(zone: string | undefined): string;
8
+ /** The instant an ISO string names, or `undefined` when it is missing or unparseable. */
9
+ export declare function validDate(iso: string | undefined): Date | undefined;
10
+ /**
11
+ * When the meeting is, in the reader's own locale and time zone: one date and a time range for a same-day event, both ends in full for
12
+ * one that spans days, and dates only for an all-day event (drawn in the organizer's zone, since a calendar day is not an instant). `undefined`
13
+ * when the invitation names no usable start. A missing or unusable end shows the start alone.
14
+ */
15
+ export declare function formatInviteWhen(invite: Pick<MessageInvite, "startDate" | "endDate" | "allDay" | "timezone">): string | undefined;
16
+ /** The meeting's start the way a list row writes it - `Thu 9/24/2026 1:00 PM` (dates only for an all-day event), in the reader's zone. */
17
+ export declare function formatRowStart(invite: Pick<MessageInvite, "startDate" | "allDay" | "timezone">): string | undefined;
18
+ /** `YYYY-MM-DD` of an instant in the reader's zone, or in `timeZone` when given (`en-CA` writes a date that way). */
19
+ export declare function isoDay(date: Date, timeZone?: string): string;
20
+ /** Where "Open in Calendar" goes: the day view of the day the meeting starts (the `date`/`view` the calendar page reads), or the calendar itself
21
+ * when the invitation names no usable start. An all-day event's day is the organizer's; any other is the reader's. */
22
+ export declare function calendarHref(invite: Pick<MessageInvite, "startDate" | "allDay" | "timezone">): string;
23
+ /** The reader's answer as a list row says it. */
24
+ export declare const ANSWER_LABEL: Record<InviteResponse, string>;
25
+ /** The titles of the events an invitation conflicts with, as "A, B and 2 more". */
26
+ export declare function conflictSummary(conflicts: {
27
+ title: string;
28
+ }[]): string;
@@ -0,0 +1,94 @@
1
+ import { APP_HREFS } from "../../../navigation/appHrefs.js";
2
+ /** Whether an attachment is a calendar file - what tells the pane a message may carry an invitation worth asking the server about. By type
3
+ * or by name, since mail clients disagree about which one they set (`text/calendar`, `application/ics`, or a bare `.ics` as octet-stream). */
4
+ export function isCalendarAttachment(attachment) {
5
+ const type = (attachment.mimeType ?? "").split(";")[0].trim().toLowerCase();
6
+ return type === "text/calendar" || type === "application/ics" || (attachment.filename ?? "").trim().toLowerCase().endsWith(".ics");
7
+ }
8
+ const DAY_FORMAT = { weekday: "short", year: "numeric", month: "short", day: "numeric" };
9
+ const CLOCK_FORMAT = { hour: "numeric", minute: "2-digit" };
10
+ /** `zone` if the browser knows it, else UTC - an all-day date is a calendar day, and needs a fixed zone to stay the same day for every reader. */
11
+ export function allDayZone(zone) {
12
+ try {
13
+ new Intl.DateTimeFormat(undefined, { timeZone: zone });
14
+ return zone ?? "UTC";
15
+ }
16
+ catch {
17
+ return "UTC";
18
+ }
19
+ }
20
+ /** The instant an ISO string names, or `undefined` when it is missing or unparseable. */
21
+ export function validDate(iso) {
22
+ const date = iso ? new Date(iso) : undefined;
23
+ return date && !isNaN(date.getTime()) ? date : undefined;
24
+ }
25
+ /**
26
+ * When the meeting is, in the reader's own locale and time zone: one date and a time range for a same-day event, both ends in full for
27
+ * one that spans days, and dates only for an all-day event (drawn in the organizer's zone, since a calendar day is not an instant). `undefined`
28
+ * when the invitation names no usable start. A missing or unusable end shows the start alone.
29
+ */
30
+ export function formatInviteWhen(invite) {
31
+ const start = validDate(invite.startDate);
32
+ if (!start) {
33
+ return undefined;
34
+ }
35
+ const parsedEnd = validDate(invite.endDate);
36
+ const end = parsedEnd && parsedEnd > start ? parsedEnd : undefined;
37
+ if (invite.allDay) {
38
+ const day = new Intl.DateTimeFormat(undefined, { ...DAY_FORMAT, timeZone: allDayZone(invite.timezone) });
39
+ const first = day.format(start);
40
+ // An all-day event's end is the midnight after its last day (iCalendar's DTEND is exclusive), so the last day is a moment before it.
41
+ const last = end ? day.format(new Date(end.getTime() - 1)) : first;
42
+ return last === first ? first : `${first} - ${last}`;
43
+ }
44
+ const date = new Intl.DateTimeFormat(undefined, DAY_FORMAT);
45
+ const time = new Intl.DateTimeFormat(undefined, CLOCK_FORMAT);
46
+ // The zone is named once, after the last time shown: the times are the reader's own, not the organizer's.
47
+ const timeAndZone = new Intl.DateTimeFormat(undefined, { ...CLOCK_FORMAT, timeZoneName: "short" });
48
+ const startDay = date.format(start);
49
+ if (!end) {
50
+ return `${startDay}, ${timeAndZone.format(start)}`;
51
+ }
52
+ return startDay === date.format(end)
53
+ ? `${startDay}, ${time.format(start)} - ${timeAndZone.format(end)}`
54
+ : `${startDay}, ${time.format(start)} - ${date.format(end)}, ${timeAndZone.format(end)}`;
55
+ }
56
+ /** The meeting's start the way a list row writes it - `Thu 9/24/2026 1:00 PM` (dates only for an all-day event), in the reader's zone. */
57
+ export function formatRowStart(invite) {
58
+ const start = validDate(invite.startDate);
59
+ if (!start) {
60
+ return undefined;
61
+ }
62
+ const zone = invite.allDay ? allDayZone(invite.timezone) : undefined;
63
+ const weekday = new Intl.DateTimeFormat(undefined, { weekday: "short", timeZone: zone }).format(start);
64
+ const date = new Intl.DateTimeFormat(undefined, { year: "numeric", month: "numeric", day: "numeric", timeZone: zone }).format(start);
65
+ return invite.allDay ? `${weekday} ${date}` : `${weekday} ${date} ${new Intl.DateTimeFormat(undefined, CLOCK_FORMAT).format(start)}`;
66
+ }
67
+ /** `YYYY-MM-DD` of an instant in the reader's zone, or in `timeZone` when given (`en-CA` writes a date that way). */
68
+ export function isoDay(date, timeZone) {
69
+ return new Intl.DateTimeFormat("en-CA", { year: "numeric", month: "2-digit", day: "2-digit", timeZone }).format(date);
70
+ }
71
+ /** Where "Open in Calendar" goes: the day view of the day the meeting starts (the `date`/`view` the calendar page reads), or the calendar itself
72
+ * when the invitation names no usable start. An all-day event's day is the organizer's; any other is the reader's. */
73
+ export function calendarHref(invite) {
74
+ const start = validDate(invite.startDate);
75
+ if (!start) {
76
+ return APP_HREFS.calendar;
77
+ }
78
+ return `${APP_HREFS.calendar}?date=${isoDay(start, invite.allDay ? allDayZone(invite.timezone) : undefined)}&view=day`;
79
+ }
80
+ /** The reader's answer as a list row says it. */
81
+ export const ANSWER_LABEL = {
82
+ accepted: "Accepted",
83
+ tentative: "Tentative",
84
+ declined: "Declined",
85
+ };
86
+ /** The most conflicting events that are named; more are counted. */
87
+ const MAX_NAMED_CONFLICTS = 3;
88
+ /** The titles of the events an invitation conflicts with, as "A, B and 2 more". */
89
+ export function conflictSummary(conflicts) {
90
+ const titles = conflicts.map((c) => c.title.trim() || "(no title)");
91
+ return titles.length <= MAX_NAMED_CONFLICTS
92
+ ? titles.join(", ")
93
+ : `${titles.slice(0, MAX_NAMED_CONFLICTS).join(", ")} and ${titles.length - MAX_NAMED_CONFLICTS} more`;
94
+ }
@@ -0,0 +1,28 @@
1
+ import { MessageInvite } from "@rapidmx/react-shared/calendar/inviteApi.js";
2
+ /**
3
+ * A per-message cache of the calendar invitation the server reads out of a message, shared by everything that draws one: the reading pane's
4
+ * card, the list row's RSVP chip and its popover. They ask for the same message a moment apart (the row's chip, then the pane once it is
5
+ * opened), and an answer given in one has to show in the others, so a lookup is made once and every answer goes through `storeInvite()`.
6
+ *
7
+ * A message with no invitation (`null`, the server's 404) is remembered too. A lookup that fails is not - nothing is cached and the next
8
+ * thing to ask (a remount, another row) tries again - but nothing retries on its own. An entry goes stale after `INVITE_TTL_MS`, because the
9
+ * calendar it describes can change elsewhere (the meeting deleted, another device answering); it is refetched when next asked for.
10
+ */
11
+ /** How long a looked-up invitation is trusted before the next ask reads it again. */
12
+ export declare const INVITE_TTL_MS = 30000;
13
+ /** Reads a message's invitation, from the cache when it is fresh. Rejects when the lookup fails. */
14
+ export declare function loadInvite(uid: string): Promise<MessageInvite | null>;
15
+ /** Records the invitation as it now stands after an answer, so the pane, the chip and the popover all show it. */
16
+ export declare function storeInvite(uid: string, invite: MessageInvite): void;
17
+ /** Forgets everything - for tests, and for a sign-out. */
18
+ export declare function clearInviteCache(): void;
19
+ /**
20
+ * A message's invitation, looked up once (shared with every other user of the same message) when `enabled`. `invite` is `null` until it is
21
+ * known, and stays `null` for a message with none or a lookup that failed - nothing to draw either way. `settled` says the lookup is over.
22
+ * Nothing is asked while disabled, and a failure is never retried from here.
23
+ */
24
+ export declare function useMessageInvite(uid: string, enabled?: boolean): {
25
+ invite: MessageInvite | null;
26
+ settled: boolean;
27
+ setInvite: (invite: MessageInvite) => void;
28
+ };
@@ -0,0 +1,95 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useCallback, useEffect, useReducer } from "react";
6
+ import { getMessageInvite } from "@rapidmx/react-shared/calendar/inviteApi.js";
7
+ /**
8
+ * A per-message cache of the calendar invitation the server reads out of a message, shared by everything that draws one: the reading pane's
9
+ * card, the list row's RSVP chip and its popover. They ask for the same message a moment apart (the row's chip, then the pane once it is
10
+ * opened), and an answer given in one has to show in the others, so a lookup is made once and every answer goes through `storeInvite()`.
11
+ *
12
+ * A message with no invitation (`null`, the server's 404) is remembered too. A lookup that fails is not - nothing is cached and the next
13
+ * thing to ask (a remount, another row) tries again - but nothing retries on its own. An entry goes stale after `INVITE_TTL_MS`, because the
14
+ * calendar it describes can change elsewhere (the meeting deleted, another device answering); it is refetched when next asked for.
15
+ */
16
+ /** How long a looked-up invitation is trusted before the next ask reads it again. */
17
+ export const INVITE_TTL_MS = 30000;
18
+ const entries = new Map();
19
+ const listeners = new Map();
20
+ function notify(uid) {
21
+ listeners.get(uid)?.forEach((listener) => listener());
22
+ }
23
+ /** Older servers, or a partial answer, may leave a list out: a card must never fail to draw over one. */
24
+ function normalize(invite) {
25
+ if (!invite) {
26
+ return null;
27
+ }
28
+ const partial = invite;
29
+ return { ...invite, attendees: partial.attendees ?? [], conflicts: partial.conflicts ?? [], schedule: partial.schedule ?? [] };
30
+ }
31
+ /** Reads a message's invitation, from the cache when it is fresh. Rejects when the lookup fails. */
32
+ export function loadInvite(uid) {
33
+ const existing = entries.get(uid);
34
+ if (existing && (existing.value === undefined || Date.now() - existing.at < INVITE_TTL_MS)) {
35
+ return existing.promise;
36
+ }
37
+ const entry = {
38
+ at: Date.now(),
39
+ promise: getMessageInvite(uid).then((invite) => {
40
+ entry.value = normalize(invite);
41
+ entry.at = Date.now();
42
+ notify(uid);
43
+ return entry.value;
44
+ }, (err) => {
45
+ if (entries.get(uid) === entry) {
46
+ entries.delete(uid);
47
+ }
48
+ throw err;
49
+ }),
50
+ };
51
+ entries.set(uid, entry);
52
+ return entry.promise;
53
+ }
54
+ /** Records the invitation as it now stands after an answer, so the pane, the chip and the popover all show it. */
55
+ export function storeInvite(uid, invite) {
56
+ const value = normalize(invite);
57
+ entries.set(uid, { promise: Promise.resolve(value), value, at: Date.now() });
58
+ notify(uid);
59
+ }
60
+ /** Forgets everything - for tests, and for a sign-out. */
61
+ export function clearInviteCache() {
62
+ entries.clear();
63
+ }
64
+ /**
65
+ * A message's invitation, looked up once (shared with every other user of the same message) when `enabled`. `invite` is `null` until it is
66
+ * known, and stays `null` for a message with none or a lookup that failed - nothing to draw either way. `settled` says the lookup is over.
67
+ * Nothing is asked while disabled, and a failure is never retried from here.
68
+ */
69
+ export function useMessageInvite(uid, enabled = true) {
70
+ const [, rerender] = useReducer((n) => n + 1, 0);
71
+ useEffect(() => {
72
+ if (!enabled) {
73
+ return;
74
+ }
75
+ let cancelled = false;
76
+ const update = () => {
77
+ if (!cancelled) {
78
+ rerender();
79
+ }
80
+ };
81
+ const set = listeners.get(uid) ?? new Set();
82
+ set.add(update);
83
+ listeners.set(uid, set);
84
+ loadInvite(uid).then(update, () => {
85
+ // Nothing to show, and nothing worth interrupting the reader with: the message reads the same without it.
86
+ });
87
+ return () => {
88
+ cancelled = true;
89
+ set.delete(update);
90
+ };
91
+ }, [uid, enabled]);
92
+ const setInvite = useCallback((invite) => storeInvite(uid, invite), [uid]);
93
+ const value = enabled ? entries.get(uid)?.value : undefined;
94
+ return { invite: value ?? null, settled: value !== undefined, setInvite };
95
+ }
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import { FolderBadge } from "../../../mail/folderCounts.js";
3
+ /** A folder's badge: an accent pill with the unread count, or - for the folders that show how many they hold - plain muted text. */
4
+ export default function FolderBadgeChip({ badge }: {
5
+ badge: FolderBadge;
6
+ }): React.JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { badgeLabel } from "../../../mail/folderCounts.js";
3
+ /** A folder's badge: an accent pill with the unread count, or - for the folders that show how many they hold - plain muted text. */
4
+ export default function FolderBadgeChip({ badge }) {
5
+ return (_jsxs("span", { className: [
6
+ "text-xs rounded-pill py-0.5 px-1.5",
7
+ badge.kind === "unread" ? "font-bold bg-primary/15 text-primary-dark" : "font-medium text-text-muted",
8
+ ].join(" "), children: [_jsx("span", { "aria-hidden": "true", children: badge.value }), _jsxs("span", { className: "sr-only", children: [" ", badgeLabel(badge)] })] }));
9
+ }
@@ -59,6 +59,11 @@ export interface MailShellContextValue {
59
59
  * context value, which is only ever read outside a real shell.
60
60
  */
61
61
  trackMessageChange: (previous: Message, next: Message | null) => CountTracker;
62
+ /**
63
+ * The phone layout's header row beside the folders button, for the page to put its search box in (`createPortal()`): the shell
64
+ * owns the row, the page owns the search state. `null` until the row has rendered, and on the default context value.
65
+ */
66
+ mobileSearchSlot: HTMLElement | null;
62
67
  }
63
68
  /** Reads the mailbox/folder a page is currently showing, as resolved by the enclosing `MailShell`. */
64
69
  export declare function useMailShell(): MailShellContextValue;
@@ -78,5 +83,10 @@ export { MAILBOX_LIST_LIMIT };
78
83
  * behavior change from before this, and a multi-mailbox user sees every mailbox's folders (and the new
79
84
  * aggregate section) simultaneously, matching the shared-mailbox feature's own "display as a separate set
80
85
  * of folders" requirement.
86
+ *
87
+ * An address that names nothing opens "All Mailboxes > Inbox" when there is more than one mailbox (that is when the section exists), and
88
+ * the one mailbox's own Inbox otherwise; a `?mailboxUid=`, `?folderUid=` or `?aggregate=` always wins. Nothing is selected until the address
89
+ * has been read. What needs a single mailbox while an all-mailboxes view is open (Compose, the new-message shortcut, key unlocking) uses the
90
+ * primary mailbox.
81
91
  */
82
92
  export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, pluginNav, children, }: PropsWithChildren<MailShellProps>): React.JSX.Element;