@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,90 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useRef } from "react";
6
+ /** The least distance that commits a swipe, and how far the finger must move before its direction is decided. */
7
+ export const MIN_SWIPE_DISTANCE = 80;
8
+ export const SWIPE_SLOP = 10;
9
+ /** A quicker flick than this (px per ms) commits after `FLING_MIN_DISTANCE` alone. */
10
+ export const FLING_VELOCITY = 0.5;
11
+ export const FLING_MIN_DISTANCE = 40;
12
+ /**
13
+ * A horizontal swipe by one finger, for the touch screens the app's phone layout is for: mouse and pen are left alone.
14
+ *
15
+ * Spread the handlers on the element and give it `touch-action: pan-y` (`style`), so the browser keeps scrolling vertically and leaves
16
+ * the horizontal movement to this. A gesture that starts out mostly vertical is a scroll and is never a swipe, however far it later
17
+ * strays sideways; one that starts out horizontal is a swipe, and stays one.
18
+ */
19
+ export function useSwipe({ enabled = true, directions = ["left", "right"], distance, onDrag, onSwipe }) {
20
+ const start = useRef(null);
21
+ const offset = useRef(0);
22
+ const latest = useRef({ directions, distance, onDrag, onSwipe });
23
+ latest.current = { directions, distance, onDrag, onSwipe };
24
+ function clamp(dx) {
25
+ const allowed = latest.current.directions;
26
+ return (dx < 0 && !allowed.includes("left")) || (dx > 0 && !allowed.includes("right")) ? 0 : dx;
27
+ }
28
+ function onTouchStart(event) {
29
+ if (!enabled || event.touches.length !== 1) {
30
+ start.current = null;
31
+ return;
32
+ }
33
+ const touch = event.touches[0];
34
+ start.current = { x: touch.clientX, y: touch.clientY, time: Date.now(), width: event.currentTarget.clientWidth };
35
+ offset.current = 0;
36
+ }
37
+ function onTouchMove(event) {
38
+ const gesture = start.current;
39
+ if (!gesture || gesture.mode === "scroll") {
40
+ return;
41
+ }
42
+ if (event.touches.length !== 1) {
43
+ cancel();
44
+ return;
45
+ }
46
+ const dx = event.touches[0].clientX - gesture.x;
47
+ const dy = event.touches[0].clientY - gesture.y;
48
+ if (!gesture.mode) {
49
+ if (Math.max(Math.abs(dx), Math.abs(dy)) < SWIPE_SLOP) {
50
+ return;
51
+ }
52
+ gesture.mode = Math.abs(dx) > Math.abs(dy) * 1.5 ? "swipe" : "scroll";
53
+ if (gesture.mode === "scroll") {
54
+ return;
55
+ }
56
+ }
57
+ offset.current = clamp(dx);
58
+ latest.current.onDrag?.(offset.current);
59
+ }
60
+ function onTouchEnd() {
61
+ const gesture = start.current;
62
+ start.current = null;
63
+ if (!gesture || gesture.mode !== "swipe") {
64
+ return;
65
+ }
66
+ const dx = offset.current;
67
+ offset.current = 0;
68
+ const travelled = Math.abs(dx);
69
+ const velocity = travelled / Math.max(1, Date.now() - gesture.time);
70
+ const needed = latest.current.distance ?? Math.max(MIN_SWIPE_DISTANCE, gesture.width / 3);
71
+ const committed = dx !== 0 && (travelled >= needed || (velocity >= FLING_VELOCITY && travelled >= FLING_MIN_DISTANCE));
72
+ if (committed) {
73
+ latest.current.onSwipe(dx < 0 ? "left" : "right");
74
+ }
75
+ latest.current.onDrag?.(0);
76
+ }
77
+ function cancel() {
78
+ const moved = start.current?.mode === "swipe";
79
+ start.current = null;
80
+ offset.current = 0;
81
+ if (moved) {
82
+ latest.current.onDrag?.(0);
83
+ }
84
+ }
85
+ return {
86
+ handlers: enabled ? { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel: cancel } : {},
87
+ /** Leaves vertical panning to the browser. */
88
+ style: enabled ? { touchAction: "pan-y" } : undefined,
89
+ };
90
+ }
@@ -0,0 +1,40 @@
1
+ import { type CSSProperties } from "react";
2
+ import { SwipeDirection } from "./useSwipe.js";
3
+ /** How long the current contents take to leave, and the new ones to arrive - or the current ones to snap back. */
4
+ export declare const SLIDE_MS = 200;
5
+ export interface UseSwipeSlideOptions {
6
+ /** Nothing is tracked while this is `false`. */
7
+ enabled: boolean;
8
+ /** Changes what is shown to the period the swipe went to - called once the old contents have left, while the new ones wait off-screen. */
9
+ onShift: (direction: SwipeDirection) => void;
10
+ }
11
+ /**
12
+ * A swipe between pages of content that visibly moves them, the way a phone calendar does: the contents follow the finger, and once
13
+ * the swipe commits they carry on out of view in the direction it went while the next ones - `onShift()` changes what is shown at
14
+ * that moment - slide in from the other side. A swipe that stops short slides back. The animation is timed here (`SLIDE_MS`) rather than
15
+ * left to `transitionend`, so it can't stall if the browser skips a transition.
16
+ *
17
+ * Put `handlers` and `style` on a clipping element (`overflow-hidden`, so the contents don't widen the page while they are off to
18
+ * the side), the contents inside it in an element of their own with `contentStyle`, and `ref` on the clipping element.
19
+ */
20
+ export declare function useSwipeSlide({ enabled, onShift }: UseSwipeSlideOptions): {
21
+ handlers: {
22
+ onTouchStart: (event: import("react").TouchEvent<HTMLElement>) => void;
23
+ onTouchMove: (event: import("react").TouchEvent<HTMLElement>) => void;
24
+ onTouchEnd: () => void;
25
+ onTouchCancel: () => void;
26
+ } | {
27
+ onTouchStart?: undefined;
28
+ onTouchMove?: undefined;
29
+ onTouchEnd?: undefined;
30
+ onTouchCancel?: undefined;
31
+ };
32
+ /** For the clipping element: `touch-action: pan-y`, and no overflow. */
33
+ style: {
34
+ readonly touchAction: "pan-y";
35
+ } | undefined;
36
+ contentStyle: CSSProperties;
37
+ ref: import("react").RefObject<HTMLDivElement | null>;
38
+ /** For a test or a style hook: `"idle"`, `"dragging"` or `"sliding"`. */
39
+ phase: string;
40
+ };
@@ -0,0 +1,95 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { useSwipe } from "./useSwipe.js";
7
+ /** How long the current contents take to leave, and the new ones to arrive - or the current ones to snap back. */
8
+ export const SLIDE_MS = 200;
9
+ /** The pause between the new contents being put off-screen and them starting to slide in, long enough for the browser to draw them there. */
10
+ const SETTLE_MS = 30;
11
+ /** How far a slide goes when the element's width can't be measured. */
12
+ const FALLBACK_WIDTH_PX = 320;
13
+ /** Whether the user asked their device for less motion, in which case the change is made at once instead of slid. */
14
+ function prefersReducedMotion() {
15
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
16
+ }
17
+ /**
18
+ * A swipe between pages of content that visibly moves them, the way a phone calendar does: the contents follow the finger, and once
19
+ * the swipe commits they carry on out of view in the direction it went while the next ones - `onShift()` changes what is shown at
20
+ * that moment - slide in from the other side. A swipe that stops short slides back. The animation is timed here (`SLIDE_MS`) rather than
21
+ * left to `transitionend`, so it can't stall if the browser skips a transition.
22
+ *
23
+ * Put `handlers` and `style` on a clipping element (`overflow-hidden`, so the contents don't widen the page while they are off to
24
+ * the side), the contents inside it in an element of their own with `contentStyle`, and `ref` on the clipping element.
25
+ */
26
+ export function useSwipeSlide({ enabled, onShift }) {
27
+ const [offset, setOffset] = useState(0);
28
+ /** Whether a change of `offset` is animated: not while the finger is moving it, yes for everything else. */
29
+ const [animated, setAnimated] = useState(false);
30
+ /** A committed slide is under way: further swipes are ignored until it ends. */
31
+ const [sliding, setSliding] = useState(false);
32
+ const slidingRef = useRef(false);
33
+ const ref = useRef(null);
34
+ const timers = useRef([]);
35
+ const latest = useRef({ onShift });
36
+ latest.current = { onShift };
37
+ useEffect(() => () => {
38
+ for (const timer of timers.current) {
39
+ window.clearTimeout(timer);
40
+ }
41
+ }, []);
42
+ function later(work, ms) {
43
+ timers.current.push(window.setTimeout(work, ms));
44
+ }
45
+ const swipe = useSwipe({
46
+ enabled: enabled && !sliding,
47
+ onDrag: (dx) => {
48
+ // `useSwipe()` reports the finger lifting (0) right after a swipe commits: that must not undo the slide out.
49
+ if (slidingRef.current) {
50
+ return;
51
+ }
52
+ setAnimated(dx === 0);
53
+ setOffset(dx);
54
+ },
55
+ onSwipe: (direction) => {
56
+ if (prefersReducedMotion()) {
57
+ latest.current.onShift(direction);
58
+ return;
59
+ }
60
+ const width = ref.current?.clientWidth || FALLBACK_WIDTH_PX;
61
+ // The finger moved `direction`, so the contents go that way and the next ones come from the opposite side.
62
+ const out = direction === "left" ? -width : width;
63
+ slidingRef.current = true;
64
+ setSliding(true);
65
+ setAnimated(true);
66
+ setOffset(out);
67
+ later(() => {
68
+ latest.current.onShift(direction);
69
+ setAnimated(false);
70
+ setOffset(-out);
71
+ later(() => {
72
+ setAnimated(true);
73
+ setOffset(0);
74
+ later(() => {
75
+ slidingRef.current = false;
76
+ setSliding(false);
77
+ }, SLIDE_MS);
78
+ }, SETTLE_MS);
79
+ }, SLIDE_MS);
80
+ },
81
+ });
82
+ const contentStyle = {
83
+ transform: offset === 0 ? undefined : `translate3d(${offset}px, 0, 0)`,
84
+ transition: animated ? `transform ${SLIDE_MS}ms ease-out` : "none",
85
+ };
86
+ return {
87
+ handlers: swipe.handlers,
88
+ /** For the clipping element: `touch-action: pan-y`, and no overflow. */
89
+ style: swipe.style,
90
+ contentStyle,
91
+ ref,
92
+ /** For a test or a style hook: `"idle"`, `"dragging"` or `"sliding"`. */
93
+ phase: sliding ? "sliding" : offset !== 0 && !animated ? "dragging" : "idle",
94
+ };
95
+ }
@@ -0,0 +1,32 @@
1
+ import { type RefObject } from "react";
2
+ /** Wheel movement, in pixels, that steps to the next or previous page when nothing inside scrolls: about one notch of a mouse wheel. */
3
+ export declare const WHEEL_STEP_PX = 100;
4
+ /** Wheel movement, in pixels, pushed against the top or bottom of what scrolls inside before it steps onto the next or previous page. */
5
+ export declare const WHEEL_EDGE_PX = 80;
6
+ /** Movement that isn't followed by more within this long starts from nothing again, so an old nudge doesn't add to a new one. */
7
+ export declare const WHEEL_IDLE_MS = 250;
8
+ /** After a step that starts at an edge, wheel events for this long are left to settle the new page, so one hard push isn't two steps. */
9
+ export declare const WHEEL_SETTLE_MS = 100;
10
+ /** Where the new page's scroll position goes: `"top"` when stepping forward past the bottom, `"bottom"` when stepping back past the top. */
11
+ export type WheelEdge = "top" | "bottom";
12
+ export interface UseWheelPagingOptions {
13
+ /** Nothing is handled while this is `false` (a phone, an event being dragged, a dialog open). */
14
+ enabled: boolean;
15
+ /** The element inside that scrolls vertically, if there is one now: the wheel scrolls it as usual and only steps past its ends.
16
+ * `null` when nothing inside scrolls (a month grid), and then every wheel movement counts towards a step. */
17
+ getScroller: () => HTMLElement | null;
18
+ /** Called for each step: `1` towards the next page (the wheel turned down), `-1` towards the previous. `edge` says where in the new
19
+ * page's scroller to start, when there is one. */
20
+ onStep: (direction: 1 | -1, edge: WheelEdge | null) => void;
21
+ }
22
+ /**
23
+ * Turns the mouse wheel over `ref`'s element into stepping through pages - the months, weeks or days of a calendar - without a limit:
24
+ * keep turning and it keeps stepping, and nothing waits for an animation to end.
25
+ *
26
+ * Where nothing inside scrolls, every `WHEEL_STEP_PX` of movement is a step (a bigger turn is several). Where something does, the
27
+ * wheel scrolls it as it always did and only what is pushed against its top or bottom counts (`WHEEL_EDGE_PX`), so scrolling on
28
+ * from the last hour of a day carries into the first hours of the next, and back the other way. The event is only cancelled when it
29
+ * counts towards a step, so the page and whatever scrolls inside keep their own wheel behaviour everywhere else. Zooming
30
+ * (Ctrl+wheel) and sideways movement are left alone.
31
+ */
32
+ export declare function useWheelPaging(ref: RefObject<HTMLElement | null>, { enabled, getScroller, onStep }: UseWheelPagingOptions): void;
@@ -0,0 +1,85 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useEffect, useRef } from "react";
6
+ /** Wheel movement, in pixels, that steps to the next or previous page when nothing inside scrolls: about one notch of a mouse wheel. */
7
+ export const WHEEL_STEP_PX = 100;
8
+ /** Wheel movement, in pixels, pushed against the top or bottom of what scrolls inside before it steps onto the next or previous page. */
9
+ export const WHEEL_EDGE_PX = 80;
10
+ /** Movement that isn't followed by more within this long starts from nothing again, so an old nudge doesn't add to a new one. */
11
+ export const WHEEL_IDLE_MS = 250;
12
+ /** After a step that starts at an edge, wheel events for this long are left to settle the new page, so one hard push isn't two steps. */
13
+ export const WHEEL_SETTLE_MS = 100;
14
+ /** A wheel event's vertical movement in pixels, whichever unit the browser reports it in. */
15
+ function pixelsOf(event, page) {
16
+ return event.deltaMode === 1 ? event.deltaY * 16 : event.deltaMode === 2 ? event.deltaY * page : event.deltaY;
17
+ }
18
+ /**
19
+ * Turns the mouse wheel over `ref`'s element into stepping through pages - the months, weeks or days of a calendar - without a limit:
20
+ * keep turning and it keeps stepping, and nothing waits for an animation to end.
21
+ *
22
+ * Where nothing inside scrolls, every `WHEEL_STEP_PX` of movement is a step (a bigger turn is several). Where something does, the
23
+ * wheel scrolls it as it always did and only what is pushed against its top or bottom counts (`WHEEL_EDGE_PX`), so scrolling on
24
+ * from the last hour of a day carries into the first hours of the next, and back the other way. The event is only cancelled when it
25
+ * counts towards a step, so the page and whatever scrolls inside keep their own wheel behaviour everywhere else. Zooming
26
+ * (Ctrl+wheel) and sideways movement are left alone.
27
+ */
28
+ export function useWheelPaging(ref, { enabled, getScroller, onStep }) {
29
+ const latest = useRef({ getScroller, onStep });
30
+ latest.current = { getScroller, onStep };
31
+ /** Movement collected towards the next step, and when it last grew. */
32
+ const collected = useRef({ pixels: 0, at: 0 });
33
+ const settledUntil = useRef(0);
34
+ useEffect(() => {
35
+ const element = ref.current;
36
+ if (!enabled || !element) {
37
+ return;
38
+ }
39
+ function onWheel(event) {
40
+ if (event.ctrlKey || event.defaultPrevented || Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
41
+ return;
42
+ }
43
+ const pixels = pixelsOf(event, element.clientHeight || 600);
44
+ if (pixels === 0) {
45
+ return;
46
+ }
47
+ const scroller = latest.current.getScroller();
48
+ if (scroller) {
49
+ const room = scroller.scrollHeight - scroller.clientHeight;
50
+ const canScroll = pixels > 0 ? scroller.scrollTop < room - 1 : scroller.scrollTop > 0;
51
+ if (room > 0 && canScroll) {
52
+ // Still something to scroll that way: the browser does it, and what was pushed against an end is forgotten.
53
+ collected.current = { pixels: 0, at: 0 };
54
+ return;
55
+ }
56
+ }
57
+ event.preventDefault();
58
+ const now = Date.now();
59
+ if (scroller && now < settledUntil.current) {
60
+ return;
61
+ }
62
+ const state = collected.current;
63
+ if (now - state.at > WHEEL_IDLE_MS || Math.sign(state.pixels) !== Math.sign(pixels)) {
64
+ state.pixels = 0;
65
+ }
66
+ state.at = now;
67
+ state.pixels += pixels;
68
+ const needed = scroller ? WHEEL_EDGE_PX : WHEEL_STEP_PX;
69
+ while (Math.abs(state.pixels) >= needed) {
70
+ const direction = state.pixels > 0 ? 1 : -1;
71
+ state.pixels -= direction * needed;
72
+ latest.current.onStep(direction, scroller ? (direction > 0 ? "top" : "bottom") : null);
73
+ if (scroller) {
74
+ // One push against an end is one step; the next turn of the wheel scrolls the page it arrived at.
75
+ state.pixels = 0;
76
+ settledUntil.current = now + WHEEL_SETTLE_MS;
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ // Not passive: the wheel's default (scrolling the page) has to be cancellable for the steps that replace it.
82
+ element.addEventListener("wheel", onWheel, { passive: false });
83
+ return () => element.removeEventListener("wheel", onWheel);
84
+ }, [enabled, ref]);
85
+ }
@@ -46,6 +46,16 @@ export declare function badgeLabel(badge: FolderBadge): string;
46
46
  export declare function inboxUnreadTotal(mailboxFolders: {
47
47
  folders: Folder[];
48
48
  }[], counts: Record<string, FolderCount>): number;
49
+ /**
50
+ * The folder types whose unread count is left out of a collapsed sidebar section's heading badge: Drafts and Outbox (their badges
51
+ * count messages held, not unread mail), and Sent Items, Deleted Items and Junk Email (an unread count there is noise, not mail
52
+ * to notice). It matches the folders `badgeFor()` shows no unread badge for; every other type - Inbox, Archive, the user's own
53
+ * folders - counts, so mail that a rule filed out of the Inbox is not overlooked in a hidden section. Change it here to change that.
54
+ */
55
+ export declare const UNREAD_BADGE_EXCLUDED_FOLDER_TYPES: readonly FolderType[];
56
+ /** The unread messages in the `folders` that carry an unread count (all but `UNREAD_BADGE_EXCLUDED_FOLDER_TYPES`) - what a collapsed
57
+ * sidebar section shows on its heading, so new mail in a hidden section is still noticed. */
58
+ export declare function unreadBadgeTotal(folders: readonly Folder[], counts: Record<string, FolderCount>): number;
49
59
  /** How long after the last change of this page's own has finished a server-published count is trusted again: one published
50
60
  * about an earlier state of the folder could otherwise arrive late and put back what the change just took away. */
51
61
  export declare const COUNT_QUIET_MS = 1500;
@@ -51,6 +51,18 @@ export function badgeLabel(badge) {
51
51
  export function inboxUnreadTotal(mailboxFolders, counts) {
52
52
  return mailboxFolders.reduce((total, entry) => total + entry.folders.filter((folder) => folder.type === "inbox").reduce((sum, folder) => sum + countOfFolder(folder, counts).unread, 0), 0);
53
53
  }
54
+ /**
55
+ * The folder types whose unread count is left out of a collapsed sidebar section's heading badge: Drafts and Outbox (their badges
56
+ * count messages held, not unread mail), and Sent Items, Deleted Items and Junk Email (an unread count there is noise, not mail
57
+ * to notice). It matches the folders `badgeFor()` shows no unread badge for; every other type - Inbox, Archive, the user's own
58
+ * folders - counts, so mail that a rule filed out of the Inbox is not overlooked in a hidden section. Change it here to change that.
59
+ */
60
+ export const UNREAD_BADGE_EXCLUDED_FOLDER_TYPES = ["drafts", "outbox", "sent_items", "deleted_items", "junk"];
61
+ /** The unread messages in the `folders` that carry an unread count (all but `UNREAD_BADGE_EXCLUDED_FOLDER_TYPES`) - what a collapsed
62
+ * sidebar section shows on its heading, so new mail in a hidden section is still noticed. */
63
+ export function unreadBadgeTotal(folders, counts) {
64
+ return folders.reduce((total, folder) => (UNREAD_BADGE_EXCLUDED_FOLDER_TYPES.includes(folder.type) ? total : total + countOfFolder(folder, counts).unread), 0);
65
+ }
54
66
  /** How long after the last change of this page's own has finished a server-published count is trusted again: one published
55
67
  * about an earlier state of the folder could otherwise arrive late and put back what the change just took away. */
56
68
  export const COUNT_QUIET_MS = 1500;
@@ -1,23 +1,18 @@
1
1
  import type { Folder, Mailbox, Message } from "@rapidmx/react-shared/mail/mailApi.js";
2
2
  /**
3
- * What decides whether a new message is announced, what the announcement says, and the two settings behind it (both per
4
- * browser, in `localStorage`): whether new-mail pop-ups are on at all, and what the user told the "turn on desktop
5
- * notifications" offer.
3
+ * What decides whether a new message is announced, what the announcement says, and the setting behind it (per browser, in
4
+ * `localStorage`): what the user told the "turn on desktop notifications" offer. Whether pop-ups are on at all is the
5
+ * notifications switch (`notifications/preferences.ts`).
6
6
  *
7
7
  * The announcement is built only from what the push event carries - the whole `Message`, including `bodyPreview` - and only
8
8
  * ever as text: React escapes it on screen and the Notifications API takes plain strings, so nothing here is HTML.
9
9
  */
10
- /** `localStorage` key of the pop-ups switch. Absent means on; `"off"` means the user turned them off. */
11
- export declare const NEW_MAIL_POPUPS_KEY = "rapidmx-new-mail-popups";
12
10
  /** `localStorage` key of the answer to the desktop-notifications offer: `"later"` (not now) or `"asked"` (they were asked). */
13
11
  export declare const DESKTOP_OFFER_KEY = "rapidmx-desktop-notifications-offer";
14
12
  /** How much of the body an announcement quotes. */
15
13
  export declare const PREVIEW_MAX_LENGTH = 140;
16
14
  /** A message received longer ago than this is not "new": mail imported or migrated in bulk arrives as `create` events too. */
17
15
  export declare const MAX_NEW_AGE_MS: number;
18
- /** Whether new-mail pop-ups are on (the default). */
19
- export declare function getNewMailPopupsEnabled(): boolean;
20
- export declare function setNewMailPopupsEnabled(enabled: boolean): void;
21
16
  /** Whether the "turn on desktop notifications" offer has been put away with "Not now" (or answered). */
22
17
  export declare function getDesktopOfferDismissed(): boolean;
23
18
  export declare function setDesktopOfferDismissed(dismissed: boolean): void;
@@ -1,14 +1,12 @@
1
1
  import { formatMailAddress, splitMailAddress } from "@rapidmx/react-shared/mail/mailAddress.js";
2
2
  /**
3
- * What decides whether a new message is announced, what the announcement says, and the two settings behind it (both per
4
- * browser, in `localStorage`): whether new-mail pop-ups are on at all, and what the user told the "turn on desktop
5
- * notifications" offer.
3
+ * What decides whether a new message is announced, what the announcement says, and the setting behind it (per browser, in
4
+ * `localStorage`): what the user told the "turn on desktop notifications" offer. Whether pop-ups are on at all is the
5
+ * notifications switch (`notifications/preferences.ts`).
6
6
  *
7
7
  * The announcement is built only from what the push event carries - the whole `Message`, including `bodyPreview` - and only
8
8
  * ever as text: React escapes it on screen and the Notifications API takes plain strings, so nothing here is HTML.
9
9
  */
10
- /** `localStorage` key of the pop-ups switch. Absent means on; `"off"` means the user turned them off. */
11
- export const NEW_MAIL_POPUPS_KEY = "rapidmx-new-mail-popups";
12
10
  /** `localStorage` key of the answer to the desktop-notifications offer: `"later"` (not now) or `"asked"` (they were asked). */
13
11
  export const DESKTOP_OFFER_KEY = "rapidmx-desktop-notifications-offer";
14
12
  /** How much of the body an announcement quotes. */
@@ -38,13 +36,6 @@ function writeStorage(key, value) {
38
36
  // Storage blocked or full: the choice lasts until the page is reloaded, no longer.
39
37
  }
40
38
  }
41
- /** Whether new-mail pop-ups are on (the default). */
42
- export function getNewMailPopupsEnabled() {
43
- return readStorage(NEW_MAIL_POPUPS_KEY) !== "off";
44
- }
45
- export function setNewMailPopupsEnabled(enabled) {
46
- writeStorage(NEW_MAIL_POPUPS_KEY, enabled ? null : "off");
47
- }
48
39
  /** Whether the "turn on desktop notifications" offer has been put away with "Not now" (or answered). */
49
40
  export function getDesktopOfferDismissed() {
50
41
  return readStorage(DESKTOP_OFFER_KEY) !== null;
@@ -0,0 +1,80 @@
1
+ import type { Message } from "@rapidmx/react-shared/mail/mailApi.js";
2
+ import type { MailAddressLike } from "@rapidmx/react-shared/mail/mailAddress.js";
3
+ import type { NotificationAction } from "../../notifications/store.js";
4
+ import type { SendRequest } from "./sendJob.js";
5
+ /**
6
+ * A reply (or forward) this tab has sent, as the open conversation shows it until the server's own copy is in the thread.
7
+ *
8
+ * This is a *view* of the background send that is already there, not a second state machine: `startSend()` (`sendJob.ts`) opens an entry, and the
9
+ * places that already report how a send went close it - the client-side failure (`notifyFailure()`), the server's own outcome (`handleSendEvent()`
10
+ * in `sendOutcomes.ts`: `send-succeeded`, `send-failed`) and a synchronous send. The Outbox indicator, the pop-ups and this entry therefore agree,
11
+ * because they are all told by the same code at the same moment.
12
+ *
13
+ * Why the message has to be shown from here for a while: a message the server accepted is in Outbox, which has no `conversationId` yet - it is
14
+ * resolved when the message is relayed and filed in Sent Items (`ScheduledSendJob`), keeping the same `uid`. Until then no fetch of the
15
+ * conversation can return it, so the thread pane draws it from what was composed.
16
+ *
17
+ * `sending` covers everything up to the server's answer (assembling, the request, the queue in Outbox); `failed` is a send that did not go and is
18
+ * the sender's to resolve (`actions` are the very ones the pop-up offers: Retry, Open draft ...); `sent` is a message the server has relayed and
19
+ * that is waiting for the thread to be read again to find its Sent Items copy.
20
+ */
21
+ export type OutgoingState = "sending" | "failed" | "sent";
22
+ export interface OutgoingReply {
23
+ /** The draft's uid - which is also the uid of the message in Outbox and of its Sent Items copy, and so what the real message replaces this by. */
24
+ uid: string;
25
+ mailboxUid: string;
26
+ /** What the message replies to (`Message.inReplyTo` / `references`): how the pane knows which conversation it belongs to. */
27
+ inReplyTo?: string;
28
+ references: string[];
29
+ sender: MailAddressLike;
30
+ to: MailAddressLike[];
31
+ cc: MailAddressLike[];
32
+ bcc: MailAddressLike[];
33
+ subject: string;
34
+ /** The composed body, as the editor wrote it. */
35
+ html: string;
36
+ startedAt: number;
37
+ state: OutgoingState;
38
+ /** `failed`: why. */
39
+ failure?: string;
40
+ /** `failed`: what the sender can do about it. */
41
+ actions: NotificationAction[];
42
+ /** `sent`: how many times the thread was read without finding the message - see `settleOutgoing()`. */
43
+ misses: number;
44
+ }
45
+ /** A sent message that a read of its conversation has not found this many times is not part of that conversation (the server filed it elsewhere). */
46
+ export declare const MAX_SETTLE_MISSES = 2;
47
+ /**
48
+ * Starts showing `request`'s message in the conversation it replies to - or does nothing for a message that replies to nothing (a new message
49
+ * starts a conversation of its own, it is never part of an open one) and for a send-later message (it is not sent yet). Called again for the same
50
+ * message - a Retry - it starts over as `sending`.
51
+ *
52
+ * The thread is `request.threading` (the compose session's), else what the draft itself records: a draft opened again from "Open draft" is a
53
+ * session with no threading of its own, but the server kept it on the message.
54
+ */
55
+ export declare function trackOutgoing(request: SendRequest): void;
56
+ /** The send did not go: `failure` is why, `actions` what can be done (Retry, Open draft ...). */
57
+ export declare function failOutgoing(uid: string, failure: string, actions: NotificationAction[]): void;
58
+ /** A failed send is being tried again. */
59
+ export declare function markOutgoingSending(uid: string): void;
60
+ /** The server has relayed the message: its Sent Items copy is what the conversation shows next. */
61
+ export declare function markOutgoingSent(uid: string): void;
62
+ /** Stops showing a message - it went back to Drafts to be edited. */
63
+ export declare function forgetOutgoing(uid: string): void;
64
+ /** Whether `reply` continues the conversation `messages` are: it answers one of them, or its `References` chain runs through one. */
65
+ export declare function belongsToThread(reply: OutgoingReply, messages: readonly Pick<Message, "messageId">[]): boolean;
66
+ /** The uids of the messages sent from here whose real copy is in `messages`, the thread as it was just read. */
67
+ export declare function adoptedOutgoing(mailboxUid: string, messages: readonly Message[]): string[];
68
+ /**
69
+ * Called with a mailbox's conversation as it was just read, `messages` being the whole thread: forgets the messages whose real copy is now in it
70
+ * (`adoptedOutgoing()`), and gives up on a *sent* message that belongs to this thread but that the read did not find `MAX_SETTLE_MISSES` times in a
71
+ * row - the server filed it in a conversation of its own, and it is not to be shown here. A message of another conversation is left alone.
72
+ *
73
+ * A pane draws the messages it has read *before* it calls this, so that there is never a moment with neither the pending card nor the real one.
74
+ */
75
+ export declare function settleOutgoing(mailboxUid: string, messages: readonly Message[]): void;
76
+ export declare function getOutgoingReplies(): readonly OutgoingReply[];
77
+ /** The messages this tab has sent that a conversation may still need to show, oldest first. */
78
+ export declare function useOutgoingReplies(): readonly OutgoingReply[];
79
+ /** Forgets everything - for a test. */
80
+ export declare function resetOutgoingReplies(): void;