@rapidmx/web-client 0.29.0 → 0.31.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 (407) hide show
  1. package/apps/admin/audit-log/index.tsx +3 -3
  2. package/apps/admin/branding/index.tsx +3 -3
  3. package/apps/admin/data-requests/index.tsx +18 -7
  4. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  5. package/apps/admin/distribution-lists/index.tsx +3 -3
  6. package/apps/admin/distribution-lists/new/index.tsx +3 -3
  7. package/apps/admin/domains/[uid].tsx +6 -5
  8. package/apps/admin/domains/index.tsx +3 -3
  9. package/apps/admin/domains/new/index.tsx +3 -3
  10. package/apps/admin/escrow-scopes/[uid].tsx +11 -5
  11. package/apps/admin/escrow-scopes/index.tsx +3 -3
  12. package/apps/admin/escrow-scopes/new/index.tsx +9 -3
  13. package/apps/admin/index.tsx +4 -4
  14. package/apps/admin/ingest-queue/index.tsx +3 -3
  15. package/apps/admin/mailboxes/[uid].tsx +15 -7
  16. package/apps/admin/quarantine/index.tsx +4 -4
  17. package/apps/admin/retention-policy/index.tsx +3 -3
  18. package/apps/admin/signing-certificates/index.tsx +3 -3
  19. package/apps/admin/transport-rules/[uid].tsx +4 -4
  20. package/apps/admin/transport-rules/index.tsx +3 -3
  21. package/apps/admin/transport-rules/new/index.tsx +3 -3
  22. package/apps/escrow/matters/[uid].tsx +26 -2
  23. package/apps/escrow/matters/new/index.tsx +4 -0
  24. package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
  25. package/apps/shared/calendar/useCalendarReminders.ts +2 -0
  26. package/apps/shared/components/admin/ActionAlert.tsx +32 -0
  27. package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +1 -1
  28. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
  29. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
  30. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  31. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
  32. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  33. package/apps/shared/components/admin/elevation.ts +19 -0
  34. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
  35. package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
  36. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
  37. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
  38. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
  39. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  40. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
  41. package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
  42. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
  43. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
  44. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
  45. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
  46. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
  47. package/apps/shared/components/admin/settings/PluginsManager.tsx +39 -17
  48. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
  49. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
  50. package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
  51. package/apps/shared/components/admin/signOut.ts +3 -0
  52. package/apps/shared/components/admin/usePagedList.tsx +2 -2
  53. package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
  54. package/apps/shared/components/calendar/EventDetails.tsx +34 -8
  55. package/apps/shared/components/calendar/EventEditor.tsx +90 -40
  56. package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
  57. package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
  58. package/apps/shared/components/calendar/EventModal.tsx +25 -1
  59. package/apps/shared/components/calendar/FindATime.tsx +10 -2
  60. package/apps/shared/components/calendar/ListView.tsx +99 -25
  61. package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
  62. package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
  63. package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
  64. package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
  65. package/apps/shared/components/calendar/gridLayout.ts +94 -0
  66. package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
  67. package/apps/shared/components/calendar/timePicker.ts +5 -2
  68. package/apps/shared/components/contacts/ContactCard.tsx +15 -4
  69. package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
  70. package/apps/shared/components/contacts/ContactForm.tsx +61 -17
  71. package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
  72. package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
  73. package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
  74. package/apps/shared/components/contacts/participantDetails.ts +18 -1
  75. package/apps/shared/components/layout/AppShell.tsx +9 -0
  76. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
  77. package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
  78. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
  79. package/apps/shared/components/mail/ConversationList.tsx +58 -8
  80. package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
  81. package/apps/shared/components/mail/MenuButton.tsx +3 -400
  82. package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
  83. package/apps/shared/components/mail/SwipeRow.tsx +26 -4
  84. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
  85. package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
  86. package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
  87. package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
  88. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
  89. package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
  90. package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
  91. package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
  92. package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
  93. package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
  94. package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
  95. package/apps/shared/components/mail/writableMailboxes.ts +2 -0
  96. package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
  97. package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
  99. package/apps/shared/components/tasks/dueDate.ts +29 -0
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
  101. package/apps/shared/gestures/useLongPress.ts +138 -0
  102. package/apps/shared/mail/decryptedMessages.ts +51 -2
  103. package/apps/shared/mail/listSnapshots.ts +2 -0
  104. package/apps/shared/mail/outbox/sendDecision.ts +14 -3
  105. package/apps/shared/mail/outbox/sendJob.ts +5 -0
  106. package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
  107. package/apps/shared/mail/usePermanentDelete.tsx +3 -1
  108. package/apps/shared/notifications/apiErrors.ts +2 -0
  109. package/apps/shared/notifications/preferences.ts +1 -1
  110. package/apps/shared/notifications/store.ts +3 -1
  111. package/apps/shared/plugins/pluginNav.ts +4 -2
  112. package/apps/www/calendar/index.tsx +24 -12
  113. package/apps/www/contacts/[uid].tsx +6 -1
  114. package/apps/www/contacts/index.tsx +66 -12
  115. package/apps/www/index.tsx +139 -43
  116. package/apps/www/messages/[uid].tsx +8 -3
  117. package/apps/www/settings/auto-reply/index.tsx +13 -0
  118. package/apps/www/settings/blocked-senders/index.tsx +4 -0
  119. package/apps/www/settings/encryption/index.tsx +66 -4
  120. package/apps/www/settings/privacy/index.tsx +4 -1
  121. package/apps/www/settings/profile/index.tsx +12 -1
  122. package/apps/www/settings/sharing/index.tsx +11 -1
  123. package/apps/www/settings/signatures/[uid].tsx +6 -4
  124. package/apps/www/tasks/index.tsx +97 -29
  125. package/dist/apps/admin/audit-log/index.js +3 -3
  126. package/dist/apps/admin/branding/index.js +3 -3
  127. package/dist/apps/admin/data-requests/index.js +17 -7
  128. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  129. package/dist/apps/admin/distribution-lists/index.js +3 -3
  130. package/dist/apps/admin/distribution-lists/new/index.js +3 -3
  131. package/dist/apps/admin/domains/[uid].js +6 -5
  132. package/dist/apps/admin/domains/index.js +3 -3
  133. package/dist/apps/admin/domains/new/index.js +3 -3
  134. package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
  135. package/dist/apps/admin/escrow-scopes/index.js +3 -3
  136. package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
  137. package/dist/apps/admin/index.js +4 -4
  138. package/dist/apps/admin/ingest-queue/index.js +3 -3
  139. package/dist/apps/admin/mailboxes/[uid].js +15 -7
  140. package/dist/apps/admin/quarantine/index.js +4 -4
  141. package/dist/apps/admin/retention-policy/index.js +3 -3
  142. package/dist/apps/admin/signing-certificates/index.js +3 -3
  143. package/dist/apps/admin/transport-rules/[uid].js +4 -4
  144. package/dist/apps/admin/transport-rules/index.js +3 -3
  145. package/dist/apps/admin/transport-rules/new/index.js +3 -3
  146. package/dist/apps/escrow/matters/[uid].js +8 -3
  147. package/dist/apps/escrow/matters/new/index.js +4 -0
  148. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
  149. package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
  150. package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
  151. package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
  152. package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
  153. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +1 -1
  154. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
  155. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
  156. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  157. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
  158. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  159. package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
  160. package/dist/apps/shared/components/admin/elevation.js +16 -0
  161. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
  162. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
  163. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
  164. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
  165. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
  166. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  167. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  168. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
  169. package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
  170. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
  171. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
  172. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
  173. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
  174. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/PluginsManager.js +26 -12
  176. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
  177. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
  178. package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
  179. package/dist/apps/shared/components/admin/signOut.js +3 -0
  180. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  181. package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
  182. package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
  183. package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
  184. package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
  185. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
  186. package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
  187. package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
  188. package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
  189. package/dist/apps/shared/components/calendar/EventModal.js +10 -2
  190. package/dist/apps/shared/components/calendar/FindATime.js +4 -3
  191. package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
  192. package/dist/apps/shared/components/calendar/ListView.js +92 -27
  193. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  194. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
  195. package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
  196. package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
  197. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
  198. package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
  199. package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
  201. package/dist/apps/shared/components/calendar/timePicker.js +5 -2
  202. package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
  203. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
  204. package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
  205. package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
  206. package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
  207. package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
  208. package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
  209. package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
  210. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
  211. package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
  212. package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
  213. package/dist/apps/shared/components/layout/AppShell.js +9 -0
  214. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
  215. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
  216. package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
  217. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
  218. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
  219. package/dist/apps/shared/components/mail/ConversationList.d.ts +8 -3
  220. package/dist/apps/shared/components/mail/ConversationList.js +51 -8
  221. package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
  222. package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
  223. package/dist/apps/shared/components/mail/MenuButton.js +2 -211
  224. package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
  225. package/dist/apps/shared/components/mail/SwipeRow.d.ts +5 -1
  226. package/dist/apps/shared/components/mail/SwipeRow.js +17 -3
  227. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
  228. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
  229. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
  230. package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
  231. package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
  232. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
  233. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
  234. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
  235. package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
  236. package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
  237. package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
  238. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
  239. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
  240. package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
  241. package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
  242. package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
  243. package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
  244. package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
  245. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
  246. package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
  247. package/dist/apps/shared/components/tasks/dueDate.js +26 -0
  248. package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
  249. package/dist/apps/shared/gestures/useLongPress.d.ts +41 -0
  250. package/dist/apps/shared/gestures/useLongPress.js +112 -0
  251. package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
  252. package/dist/apps/shared/mail/decryptedMessages.js +46 -2
  253. package/dist/apps/shared/mail/listSnapshots.js +2 -0
  254. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
  255. package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
  256. package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
  257. package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
  258. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
  259. package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
  260. package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
  261. package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
  262. package/dist/apps/shared/notifications/apiErrors.js +2 -0
  263. package/dist/apps/shared/notifications/preferences.d.ts +1 -1
  264. package/dist/apps/shared/notifications/preferences.js +1 -1
  265. package/dist/apps/shared/notifications/store.js +3 -1
  266. package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
  267. package/dist/apps/shared/plugins/pluginNav.js +4 -2
  268. package/dist/apps/www/calendar/index.js +24 -12
  269. package/dist/apps/www/contacts/[uid].js +6 -2
  270. package/dist/apps/www/contacts/index.js +62 -11
  271. package/dist/apps/www/index.js +124 -40
  272. package/dist/apps/www/messages/[uid].js +8 -3
  273. package/dist/apps/www/settings/auto-reply/index.js +13 -1
  274. package/dist/apps/www/settings/blocked-senders/index.js +4 -0
  275. package/dist/apps/www/settings/encryption/index.js +29 -5
  276. package/dist/apps/www/settings/privacy/index.js +3 -2
  277. package/dist/apps/www/settings/profile/index.js +4 -1
  278. package/dist/apps/www/settings/sharing/index.js +10 -1
  279. package/dist/apps/www/settings/signatures/[uid].js +6 -4
  280. package/dist/apps/www/tasks/index.js +94 -32
  281. package/dist/lib/admin/auditLogApi.js +2 -2
  282. package/dist/lib/admin/distributionListsApi.js +2 -2
  283. package/dist/lib/admin/domainsApi.js +2 -2
  284. package/dist/lib/admin/escrowAuditLogApi.js +2 -2
  285. package/dist/lib/admin/escrowScopesApi.js +2 -2
  286. package/dist/lib/admin/pluginsApi.d.ts +13 -2
  287. package/dist/lib/admin/pluginsApi.js +8 -0
  288. package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
  289. package/dist/lib/admin/retentionPolicyApi.js +3 -0
  290. package/dist/lib/admin/transportRulesApi.js +2 -2
  291. package/dist/lib/appearance/preferencesApi.d.ts +1 -1
  292. package/dist/lib/appearance/preferencesApi.js +3 -14
  293. package/dist/lib/auth/session.d.ts +51 -12
  294. package/dist/lib/auth/session.js +214 -38
  295. package/dist/lib/branding/brandingApi.d.ts +3 -3
  296. package/dist/lib/branding/brandingApi.js +9 -21
  297. package/dist/lib/calendar/calendarApi.js +3 -1
  298. package/dist/lib/calendar/eventSearch.js +43 -6
  299. package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
  300. package/dist/lib/calendar/freeBusyApi.js +13 -5
  301. package/dist/lib/calendar/recurrence.js +51 -3
  302. package/dist/lib/calendar/useDayKey.d.ts +7 -0
  303. package/dist/lib/calendar/useDayKey.js +43 -0
  304. package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
  305. package/dist/lib/components/avatar/ContactAvatar.js +37 -8
  306. package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
  307. package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
  308. package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
  309. package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
  310. package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
  311. package/dist/lib/components/forms/ImageEditBadge.js +62 -0
  312. package/dist/lib/components/menus/MenuButton.d.ts +78 -0
  313. package/dist/lib/components/menus/MenuButton.js +219 -0
  314. package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
  315. package/dist/lib/components/navigation/BottomTabBar.js +22 -3
  316. package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
  317. package/dist/lib/components/overlays/PopoverPortal.js +3 -2
  318. package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
  319. package/dist/lib/contacts/contactsApi.d.ts +5 -2
  320. package/dist/lib/contacts/contactsApi.js +16 -19
  321. package/dist/lib/contacts/gravatar.d.ts +14 -3
  322. package/dist/lib/contacts/gravatar.js +42 -5
  323. package/dist/lib/contacts/preparePhoto.d.ts +24 -0
  324. package/dist/lib/contacts/preparePhoto.js +250 -0
  325. package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
  326. package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
  327. package/dist/lib/contacts/vcard.d.ts +3 -0
  328. package/dist/lib/contacts/vcard.js +59 -7
  329. package/dist/lib/crypto/escrowKeys.d.ts +8 -0
  330. package/dist/lib/crypto/escrowKeys.js +20 -1
  331. package/dist/lib/crypto/messageSecurity.d.ts +7 -4
  332. package/dist/lib/crypto/messageSecurity.js +23 -5
  333. package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
  334. package/dist/lib/crypto/passwordUnlock.js +13 -2
  335. package/dist/lib/crypto/smime.js +12 -2
  336. package/dist/lib/mail/compose/composeQuoting.js +3 -2
  337. package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
  338. package/dist/lib/mail/compose/quotedImages.js +35 -9
  339. package/dist/lib/mail/labelsApi.js +2 -2
  340. package/dist/lib/mail/mailApi.d.ts +8 -5
  341. package/dist/lib/mail/mailApi.js +73 -39
  342. package/dist/lib/mail/mailDetailHooks.js +11 -3
  343. package/dist/lib/mail/mailFilterRulesApi.js +2 -2
  344. package/dist/lib/mail/mailSignaturesApi.js +2 -2
  345. package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
  346. package/dist/lib/mail/mailboxImportApi.js +3 -15
  347. package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
  348. package/dist/lib/mail/messageBodySanitizer.js +44 -1
  349. package/dist/lib/search/queryGrammar.js +12 -4
  350. package/dist/lib/search/searchTier3.d.ts +6 -1
  351. package/dist/lib/search/searchTier3.js +54 -25
  352. package/dist/lib/tasks/tasksApi.js +3 -3
  353. package/dist/lib/util/api.d.ts +26 -4
  354. package/dist/lib/util/api.js +73 -16
  355. package/dist/lib/util/apiQuery.d.ts +11 -0
  356. package/dist/lib/util/apiQuery.js +11 -0
  357. package/dist/lib/util/settleWithLimit.d.ts +5 -0
  358. package/dist/lib/util/settleWithLimit.js +25 -0
  359. package/lib/admin/auditLogApi.ts +2 -2
  360. package/lib/admin/distributionListsApi.ts +2 -2
  361. package/lib/admin/domainsApi.ts +2 -2
  362. package/lib/admin/escrowAuditLogApi.ts +2 -2
  363. package/lib/admin/escrowScopesApi.ts +2 -2
  364. package/lib/admin/pluginsApi.ts +21 -2
  365. package/lib/admin/retentionPolicyApi.ts +4 -0
  366. package/lib/admin/transportRulesApi.ts +2 -2
  367. package/lib/appearance/preferencesApi.ts +3 -14
  368. package/lib/auth/session.ts +239 -39
  369. package/lib/branding/brandingApi.ts +9 -21
  370. package/lib/calendar/calendarApi.ts +3 -1
  371. package/lib/calendar/eventSearch.ts +46 -6
  372. package/lib/calendar/freeBusyApi.ts +14 -6
  373. package/lib/calendar/recurrence.ts +59 -8
  374. package/lib/calendar/useDayKey.ts +45 -0
  375. package/lib/components/avatar/ContactAvatar.tsx +46 -8
  376. package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
  377. package/lib/components/forms/CameraCaptureModal.tsx +133 -0
  378. package/lib/components/forms/ImageEditBadge.tsx +121 -0
  379. package/lib/components/menus/MenuButton.tsx +408 -0
  380. package/lib/components/navigation/BottomTabBar.tsx +30 -8
  381. package/lib/components/overlays/PopoverPortal.tsx +3 -2
  382. package/lib/components/pickers/TimeZonePicker.tsx +17 -1
  383. package/lib/contacts/contactsApi.ts +18 -19
  384. package/lib/contacts/gravatar.ts +47 -5
  385. package/lib/contacts/preparePhoto.ts +273 -0
  386. package/lib/contacts/useContactPhotoSrc.ts +177 -0
  387. package/lib/contacts/vcard.ts +60 -7
  388. package/lib/crypto/escrowKeys.ts +21 -1
  389. package/lib/crypto/messageSecurity.ts +33 -9
  390. package/lib/crypto/passwordUnlock.ts +14 -2
  391. package/lib/crypto/smime.ts +13 -2
  392. package/lib/mail/compose/composeQuoting.ts +3 -2
  393. package/lib/mail/compose/quotedImages.ts +39 -9
  394. package/lib/mail/labelsApi.ts +2 -2
  395. package/lib/mail/mailApi.ts +96 -39
  396. package/lib/mail/mailDetailHooks.ts +11 -3
  397. package/lib/mail/mailFilterRulesApi.ts +2 -2
  398. package/lib/mail/mailSignaturesApi.ts +2 -2
  399. package/lib/mail/mailboxImportApi.ts +9 -15
  400. package/lib/mail/messageBodySanitizer.ts +47 -1
  401. package/lib/search/queryGrammar.ts +13 -4
  402. package/lib/search/searchTier3.ts +65 -32
  403. package/lib/tasks/tasksApi.ts +3 -3
  404. package/lib/util/api.ts +99 -16
  405. package/lib/util/apiQuery.ts +13 -0
  406. package/lib/util/settleWithLimit.ts +25 -0
  407. package/package.json +1 -1
@@ -1,215 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
1
  ///////////////////////////////////////////////////////////////////////////////
3
2
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
3
  // SPDX-License-Identifier: MPL-2.0
5
4
  ///////////////////////////////////////////////////////////////////////////////
6
- import { useEffect, useRef, useState } from "react";
7
- import { HiCheck, HiChevronDown, HiChevronLeft, HiChevronRight, HiMinus } from "react-icons/hi2";
8
- import PopoverPortal from "../../../../lib/components/overlays/PopoverPortal.js";
9
- // `PopoverPortal` positions a fixed-size box (it has no auto-height mode), so the menu's height is
10
- // computed from its own contents rather than measured - from whichever level is *shown*, so a submenu
11
- // isn't left standing in the parent menu's taller box. These are the exact heights the classes below
12
- // render at, so the box is never short enough to clip its last row: an item is `h-9`, a group label
13
- // `h-6`, a note as many `leading-4` lines as it wraps to, a separator a 1px rule inside `my-1`, and the
14
- // list itself `py-1`.
15
- const ITEM_HEIGHT = 36;
16
- const ITEM_DESCRIPTION_HEIGHT = 16;
17
- const GROUP_LABEL_HEIGHT = 24;
18
- const NOTE_LINE_HEIGHT = 16;
19
- const NOTE_PADDING = 4;
20
- /** A note wraps, so its height depends on how much of it fits a line: `text-xs` averages a little over 6px
21
- * a character, and the note sits inside the list's `px-3`. Rounded so the estimate is never *under* the
22
- * lines the browser actually draws - a box a few pixels too tall shows blank space, one too short clips. */
23
- const NOTE_CHAR_WIDTH = 6.4;
24
- const NOTE_PADDING_X = 24;
25
- const SEPARATOR_HEIGHT = 9;
26
- const LIST_PADDING = 8;
27
- const MENU_MAX_HEIGHT = 460;
28
- const DEFAULT_MENU_WIDTH = 248;
29
- /** How tall `note` renders at `width`, wrapped. */
30
- function noteHeight(note, width) {
31
- const charsPerLine = Math.max(1, Math.floor((width - NOTE_PADDING_X) / NOTE_CHAR_WIDTH));
32
- return Math.ceil(note.length / charsPerLine) * NOTE_LINE_HEIGHT + NOTE_PADDING;
33
- }
34
- /** The height `PopoverPortal` is asked for - exact for a short menu, capped (the list scrolls) for a long one. */
35
- export function menuHeight(sections, width = DEFAULT_MENU_WIDTH) {
36
- let height = LIST_PADDING;
37
- sections.forEach((section, index) => {
38
- if (index > 0) {
39
- height += SEPARATOR_HEIGHT;
40
- }
41
- if (section.label) {
42
- height += GROUP_LABEL_HEIGHT;
43
- }
44
- for (const item of section.items) {
45
- height += ITEM_HEIGHT + (item.description ? ITEM_DESCRIPTION_HEIGHT : 0);
46
- }
47
- if (section.note) {
48
- height += noteHeight(section.note, width);
49
- }
50
- });
51
- return Math.min(height, MENU_MAX_HEIGHT);
52
- }
53
- /**
54
- * A toolbar button that opens an ARIA menu: `role="menu"` rows with checkmarks for the current choice,
55
- * full keyboard support (Enter/Space or Arrow Down to open, arrows/Home/End to move with wrap, Enter/Space
56
- * to choose, Escape or Tab to close with focus returning to the trigger) and a click outside to dismiss.
57
- *
58
- * Rendered through `PopoverPortal` rather than as an absolutely-positioned child: the mail list toolbar
59
- * lives inside the list's own `overflow-y-auto` scroll container, which clips an absolute popup however
60
- * high its `z-index` - the same reason Compose's own pickers portal out (see `PopoverPortal`'s own doc
61
- * comment). The portal supplies the positioning, the outside-click dismissal and the box; the menu
62
- * semantics, roving focus and keyboard handling are this component's own.
63
- */
64
- export default function MenuButton({ label, iconOnly = false, icon, disabled, title, sections, width = DEFAULT_MENU_WIDTH, className = "", "aria-label": ariaLabel, onOpenChange, }) {
65
- const [open, setOpen] = useState(false);
66
- const [activeIndex, setActiveIndex] = useState(0);
67
- // The key of the item whose submenu is showing, or `null` at the top level. Held as a key, not as the
68
- // item itself, because `sections` is rebuilt on every render.
69
- const [submenuKey, setSubmenuKey] = useState(null);
70
- const triggerRef = useRef(null);
71
- const itemRefs = useRef([]);
72
- // State, not a ref: `PopoverPortal` renders nothing at all until its own positioning effect has run, so
73
- // the rows don't exist yet when this component's effects first fire. Keying the focus effect on the
74
- // menu node itself makes it run again once they do.
75
- const [menuNode, setMenuNode] = useState(null);
76
- const openSubmenu = sections
77
- .flatMap((section) => section.items)
78
- .find((item) => item.key === submenuKey && !!item.submenu);
79
- /** The row that leaves a submenu again - synthesized rather than asked of the caller, so every submenu
80
- * has the same way back however it was built. */
81
- const backItem = {
82
- key: "__back",
83
- label: `Back to ${ariaLabel}`,
84
- keepOpen: true,
85
- onSelect: () => leaveSubmenu(),
86
- };
87
- const shownSections = openSubmenu
88
- ? [{ key: "__back", items: [backItem] }, ...openSubmenu.submenu]
89
- : sections;
90
- const items = shownSections.flatMap((section) => section.items);
91
- const enabledIndexes = items.map((item, index) => (item.disabled ? -1 : index)).filter((index) => index !== -1);
92
- /** The row focus starts on: the current choice where there is one, else the first row that can take focus. */
93
- function initialActiveIndex(within) {
94
- const checked = within.findIndex((item) => item.checked === true && !item.disabled);
95
- const enabled = within.map((item, index) => (item.disabled ? -1 : index)).filter((index) => index !== -1);
96
- return checked === -1 ? (enabled[0] ?? 0) : checked;
97
- }
98
- function openMenu() {
99
- setSubmenuKey(null);
100
- setActiveIndex(initialActiveIndex(sections.flatMap((section) => section.items)));
101
- setOpen(true);
102
- onOpenChange?.(true);
103
- }
104
- function closeMenu(returnFocus) {
105
- setOpen(false);
106
- setSubmenuKey(null);
107
- onOpenChange?.(false);
108
- if (returnFocus) {
109
- triggerRef.current?.focus();
110
- }
111
- }
112
- function enterSubmenu(item) {
113
- setSubmenuKey(item.key);
114
- const rows = item.submenu.flatMap((section) => section.items);
115
- // Past the Back row, onto the first row of the submenu itself - or, when none of them can take focus (every row is unavailable right now), onto
116
- // the Back row, so the keyboard still has a place to be.
117
- setActiveIndex(rows.some((row) => !row.disabled) ? 1 + initialActiveIndex(rows) : 0);
118
- }
119
- function leaveSubmenu() {
120
- // Back on the row the submenu was opened from.
121
- const parentIndex = sections.flatMap((section) => section.items).findIndex((item) => item.key === submenuKey);
122
- setSubmenuKey(null);
123
- setActiveIndex(parentIndex);
124
- }
125
- /** What a row does when it is chosen: open its submenu, or run it and close unless it asked to stay. */
126
- function selectItem(item) {
127
- if (item.submenu) {
128
- enterSubmenu(item);
129
- return;
130
- }
131
- item.onSelect();
132
- if (!item.keepOpen) {
133
- closeMenu(true);
134
- }
135
- }
136
- // Roving focus: only the active row is tabbable, and it takes DOM focus whenever it changes, so the
137
- // arrow keys move the screen reader's cursor too rather than only a visual highlight.
138
- useEffect(() => {
139
- if (open && menuNode) {
140
- // A disabled row can't take focus, so a menu with nothing enabled focuses its own container -
141
- // otherwise the keys it handles would go to whatever had focus before it opened.
142
- if (enabledIndexes.length === 0) {
143
- menuNode.focus();
144
- }
145
- else {
146
- itemRefs.current[activeIndex]?.focus();
147
- }
148
- }
149
- // `submenuKey` too: drilling in or out can land on the same index in the other level's list, and
150
- // the row that index *means* is a different button, which the effect must move focus to.
151
- }, [open, activeIndex, submenuKey, menuNode]);
152
- function moveActive(delta) {
153
- if (enabledIndexes.length === 0) {
154
- return;
155
- }
156
- const position = enabledIndexes.indexOf(activeIndex);
157
- setActiveIndex(enabledIndexes[(position + delta + enabledIndexes.length) % enabledIndexes.length]);
158
- }
159
- function handleMenuKeyDown(e) {
160
- if (e.key === "ArrowDown") {
161
- e.preventDefault();
162
- moveActive(1);
163
- }
164
- else if (e.key === "ArrowUp") {
165
- e.preventDefault();
166
- moveActive(-1);
167
- }
168
- else if (e.key === "Home") {
169
- e.preventDefault();
170
- setActiveIndex(enabledIndexes[0] ?? 0);
171
- }
172
- else if (e.key === "End") {
173
- e.preventDefault();
174
- setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? 0);
175
- }
176
- else if (e.key === "ArrowRight" && items[activeIndex]?.submenu) {
177
- e.preventDefault();
178
- enterSubmenu(items[activeIndex]);
179
- }
180
- else if ((e.key === "ArrowLeft" || e.key === "Escape") && openSubmenu) {
181
- // ARIA's own submenu behaviour: Escape leaves the submenu for its parent menu rather than
182
- // dismissing the whole thing.
183
- e.preventDefault();
184
- leaveSubmenu();
185
- }
186
- else if (e.key === "Escape" || e.key === "Tab") {
187
- e.preventDefault();
188
- closeMenu(true);
189
- }
190
- }
191
- function handleTriggerKeyDown(e) {
192
- if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
193
- e.preventDefault();
194
- openMenu();
195
- }
196
- }
197
- let itemIndex = -1;
198
- return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: triggerRef, type: "button", "aria-haspopup": "menu", "aria-expanded": open, "aria-label": ariaLabel, title: title, disabled: disabled, onClick: () => (open ? closeMenu(false) : openMenu()), onKeyDown: handleTriggerKeyDown, className: iconOnly
199
- ? className
200
- : [
201
- // `min-w-0` so a long label ("Filter: Has attachments") truncates instead of pushing
202
- // whatever sits beside it in the toolbar off the edge of a 384px list column.
203
- "inline-flex min-w-0 items-center gap-1 px-2 py-1 rounded-md text-sm text-text hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent",
204
- className,
205
- ].join(" "), children: [icon, !iconOnly && _jsx("span", { className: "truncate", children: label }), !iconOnly && _jsx(HiChevronDown, { size: 14, "aria-hidden": "true", className: "shrink-0 text-text-muted" })] }), open && (_jsx(PopoverPortal, { anchorRef: triggerRef, onClose: () => closeMenu(false), width: width, height: menuHeight(shownSections, width), "aria-label": ariaLabel, children: _jsx("div", { ref: setMenuNode, role: "menu", tabIndex: -1, "aria-label": ariaLabel, onKeyDown: handleMenuKeyDown, className: "flex-1 overflow-y-auto py-1", children: shownSections.map((section, sectionIndex) => (_jsxs("div", { role: "group", "aria-label": section.label, className: sectionIndex > 0 ? "border-t border-border mt-1 pt-1" : "", children: [section.label && (_jsx("div", { "aria-hidden": "true", className: "h-6 flex items-center px-3 text-xs font-semibold uppercase tracking-wide text-text-muted", children: section.label })), section.items.map((item) => {
206
- itemIndex += 1;
207
- const index = itemIndex;
208
- const role = item.role ?? "menuitem";
209
- return (_jsxs("button", { ref: (node) => {
210
- itemRefs.current[index] = node;
211
- }, type: "button", role: role, disabled: item.disabled, title: item.title, tabIndex: index === activeIndex ? 0 : -1, ...(role === "menuitem"
212
- ? {}
213
- : { "aria-checked": item.checked === "mixed" ? "mixed" : !!item.checked }), ...(item.submenu ? { "aria-haspopup": "menu", "aria-expanded": false } : {}), onClick: () => selectItem(item), className: "w-full flex items-start gap-2 px-3 py-2 text-left text-sm text-text hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent", children: [_jsxs("span", { className: "w-4 shrink-0 flex justify-center pt-0.5", children: [item.key === "__back" && (_jsx(HiChevronLeft, { size: 14, "aria-hidden": "true", className: "text-text-muted" })), item.checked === "mixed" ? (_jsx(HiMinus, { size: 14, "aria-hidden": "true", className: "text-primary-dark" })) : (item.checked && _jsx(HiCheck, { size: 14, "aria-hidden": "true", className: "text-primary-dark" }))] }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex h-5 items-center gap-1.5", children: [item.icon && (_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: item.icon })), item.swatchColor && (_jsx("span", { "aria-hidden": "true", className: "w-2.5 h-2.5 rounded-full shrink-0", style: { backgroundColor: item.swatchColor } })), _jsx("span", { className: "truncate", children: item.label })] }), item.description && (_jsx("span", { className: "block h-4 text-xs leading-4 text-text-muted truncate font-normal", children: item.description }))] }), item.submenu && (_jsx(HiChevronRight, { size: 14, "aria-hidden": "true", className: "shrink-0 mt-0.5 text-text-muted" }))] }, item.key));
214
- }), section.note && _jsx("p", { className: "px-3 py-1 text-xs leading-4 text-text-muted", children: section.note })] }, section.key))) }) }))] }));
215
- }
5
+ // `MenuButton` lives in `lib/components/menus` so shared form components (`ImageEditBadge`) can use it; this keeps the mail components' import path working.
6
+ export { default, menuHeight } from "../../../../lib/components/menus/MenuButton.js";
@@ -48,7 +48,7 @@ import LabelMenuButton from "./labelMenu.js";
48
48
  import Button from "../../../../lib/components/buttons/Button.js";
49
49
  import EncryptedBody from "./reading/EncryptedBody.js";
50
50
  import { ENCRYPTED_SUBJECT_PLACEHOLDER, displaySubject } from "./reading/EncryptedPreview.js";
51
- import { rememberDecrypted } from "../../mail/decryptedMessages.js";
51
+ import { mapWithConcurrency, rememberDecrypted } from "../../mail/decryptedMessages.js";
52
52
  import MessageBody, { BodySkeleton } from "./reading/MessageBody.js";
53
53
  import { BODY_FONT_STYLE, CardShell, SenderAvatar, SubjectCard } from "./reading/MessageCard.js";
54
54
  import MessageMoreMenu from "./reading/MessageMoreMenu.js";
@@ -210,6 +210,8 @@ function subjectCore(subject) {
210
210
  function sameSubject(subject, threadSubject) {
211
211
  return subjectCore(subject) === subjectCore(threadSubject);
212
212
  }
213
+ /** How many earlier messages of a conversation are loaded at once when printing it. */
214
+ const PRINT_CONCURRENCY = 4;
213
215
  function formatBytes(bytes) {
214
216
  if (bytes >= 1000000)
215
217
  return `${(bytes / 1000000).toFixed(1)} MB`;
@@ -779,10 +781,11 @@ function MessageDetailContent({ message: messageProp, attachments, backHref, isS
779
781
  (verifiedOrSealed || security.state === "encrypted" || security.state === "encrypted_unverified_signer" || security.state === "signature_failed")
780
782
  ? security.attachments
781
783
  : undefined;
782
- // Any state that involves a signature shows the address actually signed for (the protected From when the message
784
+ // Any state with a valid signature shows the address actually signed for (the protected From when the message
783
785
  // carries one - it equals the outer From's address, or verification would have failed) next to the badge, never a
784
- // display name alone: anyone can put "ceo@corp.com" in the name of a message sent from x@corp-pay.com.
785
- const signatureShown = security !== null && security.state !== "unprotected" && security.state !== "encrypted";
786
+ // display name alone: anyone can put "ceo@corp.com" in the name of a message sent from x@corp-pay.com. A failed
787
+ // signature vouches for nothing, so its message shows the outer address like an unsigned one.
788
+ const signatureShown = security !== null && (verifiedOrSealed || UNVERIFIED_SIGNER_STATES.has(security.state));
786
789
  const senderAddress = (signatureShown ? extractAddresses(security.protectedHeaders?.from)[0] : undefined) ?? message.from.address;
787
790
  const senderName = message.from.displayName;
788
791
  const senderNameCheck = checkSenderName(senderName, senderAddress);
@@ -841,11 +844,12 @@ function MessageDetailContent({ message: messageProp, attachments, backHref, isS
841
844
  try {
842
845
  const content = bodyContent ?? (await fetchBodyContent(message.uid, message.version));
843
846
  const older = await olderConversationMessages(message, client).catch(() => []);
844
- const earlier = await Promise.all(older.map((other) => printableOlderMessage(other).catch(() => ({
847
+ // A few at a time: a long conversation is hundreds of messages, each of them several requests.
848
+ const earlier = await mapWithConcurrency(older, PRINT_CONCURRENCY, (other) => printableOlderMessage(other).catch(() => ({
845
849
  subject: other.subject || "(no subject)",
846
850
  headers: printHeaders(other, formatMailAddress(other.from)),
847
851
  content: { kind: "text", text: "This message could not be loaded, so it is not printed." },
848
- }))));
852
+ })));
849
853
  const printable = buildConversationPrintDocument(shownSubject, [
850
854
  {
851
855
  subject: shownSubject,
@@ -8,6 +8,8 @@ export interface SwipeRowProps extends HTMLAttributes<HTMLElement> {
8
8
  onArchive: () => Promise<boolean>;
9
9
  /** Left to right. The row comes back at once, and this asks where to (a prompt for the folder). */
10
10
  onMove: () => void;
11
+ /** A finger held on the row (see `useLongPress()`) - the phone's way into select mode. Heard whether or not swipes are `enabled`; absent, the row has none. */
12
+ onLongPress?: () => void;
11
13
  children: ReactNode;
12
14
  }
13
15
  /**
@@ -18,5 +20,7 @@ export interface SwipeRowProps extends HTMLAttributes<HTMLElement> {
18
20
  *
19
21
  * Only a swipe that starts out horizontal counts (see `useSwipe()`), so scrolling the list is untouched, and it is an addition
20
22
  * to, never the only way of, archiving and moving: the selection bar and the reading pane do the same.
23
+ *
24
+ * The row is also where a press and hold is heard (`onLongPress`), which shares the finger with the swipe: a hold gives way to the first drag.
21
25
  */
22
- export default function SwipeRow({ as: Tag, enabled, onArchive, onMove, children, className, style, ...rest }: SwipeRowProps): React.JSX.Element;
26
+ export default function SwipeRow({ as: Tag, enabled, onArchive, onMove, onLongPress, children, className, style, ...rest }: SwipeRowProps): React.JSX.Element;
@@ -5,9 +5,17 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
6
  import { useEffect, useRef, useState } from "react";
7
7
  import { HiOutlineArchiveBox, HiOutlineFolder } from "react-icons/hi2";
8
+ import { useLongPress } from "../../gestures/useLongPress.js";
8
9
  import { useSwipe } from "../../gestures/useSwipe.js";
9
10
  /** How long the row takes to leave, or come back - `motion-reduce` skips it. */
10
11
  const SLIDE = "transform 150ms ease-out";
12
+ /** Both hooks' handlers for one touch event; `useSwipe()` has none while swipes are off. */
13
+ function both(first, second) {
14
+ return (event) => {
15
+ first(event);
16
+ second?.(event);
17
+ };
18
+ }
11
19
  /**
12
20
  * A row of a list of mail that slides with a finger: right to left it takes the row away to Archive (a green panel with the
13
21
  * archive icon follows the row in), left to right it comes back and asks for a folder to move it to (a blue panel with a
@@ -16,8 +24,10 @@ const SLIDE = "transform 150ms ease-out";
16
24
  *
17
25
  * Only a swipe that starts out horizontal counts (see `useSwipe()`), so scrolling the list is untouched, and it is an addition
18
26
  * to, never the only way of, archiving and moving: the selection bar and the reading pane do the same.
27
+ *
28
+ * The row is also where a press and hold is heard (`onLongPress`), which shares the finger with the swipe: a hold gives way to the first drag.
19
29
  */
20
- export default function SwipeRow({ as: Tag = "div", enabled, onArchive, onMove, children, className, style, ...rest }) {
30
+ export default function SwipeRow({ as: Tag = "div", enabled, onArchive, onMove, onLongPress, children, className, style, ...rest }) {
21
31
  const [offset, setOffset] = useState(0);
22
32
  const [width, setWidth] = useState(0);
23
33
  /** The row is on its way out (or waiting for the server to confirm it), so a finger can't drag it. */
@@ -56,15 +66,19 @@ export default function SwipeRow({ as: Tag = "div", enabled, onArchive, onMove,
56
66
  });
57
67
  },
58
68
  });
69
+ // What `useSwipe()` hands over is `{}` while swipes are off.
70
+ const swipeTouch = swipe.handlers;
71
+ const press = useLongPress({ enabled: !!onLongPress, onLongPress: onLongPress });
59
72
  if (!enabled) {
60
- return (_jsx(Tag, { className: className, style: style, ...rest, children: children }));
73
+ return (_jsx(Tag, { className: className, style: { ...style, ...press.style }, ...rest, ...press.handlers, children: children }));
61
74
  }
62
75
  const active = offset !== 0;
63
- return (_jsxs(Tag, { ...rest, ...swipe.handlers, ref: (node) => {
76
+ return (_jsxs(Tag, { ...rest, ...press.handlers, onTouchStart: both(press.handlers.onTouchStart, swipeTouch.onTouchStart), onTouchMove: both(press.handlers.onTouchMove, swipeTouch.onTouchMove), onTouchEnd: both(press.handlers.onTouchEnd, swipeTouch.onTouchEnd), onTouchCancel: both(press.handlers.onTouchCancel, swipeTouch.onTouchCancel), ref: (node) => {
64
77
  element.current = node;
65
78
  }, className: className, style: {
66
79
  ...style,
67
80
  ...swipe.style,
81
+ ...press.style,
68
82
  transform: active ? `translateX(${offset}px)` : undefined,
69
83
  transition: dragging ? "none" : SLIDE,
70
84
  }, "data-swiping": active ? "true" : undefined, children: [children, (active || leaving) && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", "data-swipe-panel": "move", className: "absolute inset-y-0 right-full w-full bg-primary text-white flex items-center justify-end pr-6", children: _jsx(HiOutlineFolder, { size: 24 }) }), _jsx("span", { "aria-hidden": "true", "data-swipe-panel": "archive", style: width ? { width } : undefined, className: "absolute inset-y-0 left-full w-full bg-success text-white flex items-center justify-start pl-6", children: _jsx(HiOutlineArchiveBox, { size: 24 }) })] }))] }));
@@ -1,5 +1,8 @@
1
1
  import React from "react";
2
2
  import type { Editor } from "@tiptap/react";
3
+ /** What a typed link target is stored as. One without a scheme (`example.com`) would be a relative link, which readers' clients drop as dead text: a bare
4
+ * email address becomes `mailto:`, anything else that is not a path or an anchor `https://`. A target with a scheme is kept as it is. */
5
+ export declare function linkHref(typed: string): string;
3
6
  export interface ComposeToolbarProps {
4
7
  /** `null` before the editor has mounted client-side (see `RichTextEditor`'s `immediatelyRender: false`
5
8
  * doc comment) — every button is disabled in that state rather than the toolbar rendering nothing, so
@@ -6,6 +6,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  import { Suspense, lazy, useRef, useState } from "react";
7
7
  import { BsArrowClockwise, BsArrowCounterclockwise, BsEraser, BsHighlighter, BsImage, BsIndent, BsJustify, BsLink45Deg, BsListOl, BsListUl, BsPaletteFill, BsTable, BsTextCenter, BsTextLeft, BsTextRight, BsTypeBold, BsTypeItalic, BsTypeStrikethrough, BsTypeUnderline, BsUnindent, } from "react-icons/bs";
8
8
  import GifPicker from "./GifPicker.js";
9
+ /** What a typed link target is stored as. One without a scheme (`example.com`) would be a relative link, which readers' clients drop as dead text: a bare
10
+ * email address becomes `mailto:`, anything else that is not a path or an anchor `https://`. A target with a scheme is kept as it is. */
11
+ export function linkHref(typed) {
12
+ const target = typed.trim();
13
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target) || /^[/#]/.test(target)) {
14
+ return target;
15
+ }
16
+ return /^[^\s@/]+@[^\s@/]+\.[^\s@/]+$/.test(target) ? `mailto:${target}` : `https://${target}`;
17
+ }
9
18
  /** Loaded when the picker is first opened: the emoji list is a half-megabyte JSON file that nothing else needs. */
10
19
  const EmojiPicker = lazy(() => import("./EmojiPicker.js"));
11
20
  const FONT_FAMILIES = [
@@ -73,7 +82,7 @@ export default function ComposeToolbar({ editor, onUploadImage }) {
73
82
  run((ed) => ed.chain().focus().extendMarkRange("link").unsetLink().run());
74
83
  }
75
84
  else {
76
- run((ed) => ed.chain().focus().extendMarkRange("link").setLink({ href: linkUrl.trim() }).run());
85
+ run((ed) => ed.chain().focus().extendMarkRange("link").setLink({ href: linkHref(linkUrl) }).run());
77
86
  }
78
87
  setOpenPopup(null);
79
88
  }