@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
@@ -3,9 +3,11 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  /** A same-origin absolute path: starts with `/`, but not `//` or `/\` (both of which browsers treat as
6
- * protocol-relative URLs to another host). */
6
+ * protocol-relative URLs to another host), and holds no control character, space or backslash anywhere -
7
+ * URL parsers strip tab/CR/LF from anywhere, so `/<tab>/evil.com` would otherwise read as `//evil.com`. */
7
8
  export function isSafePluginHref(href) {
8
- return /^\/(?![/\\])/.test(href);
9
+ // eslint-disable-next-line no-control-regex -- the control characters are exactly what is being rejected
10
+ return /^\/(?![/\\])[^\u0000-\u0020\u007f\\]*$/.test(href);
9
11
  }
10
12
  /**
11
13
  * Appends a plugin's nav items after the core ones. Skips any item that isn't well-formed, whose `href`
@@ -4,7 +4,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
6
  import { pageTitle } from "../../shared/navigation/pageTitle.js";
7
- import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
8
8
  import { DndContext, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
9
9
  import { HiOutlineBars3, HiOutlinePlus } from "react-icons/hi2";
10
10
  import { addDays, addMonths, addWeeks, eachDayOfInterval, endOfDay, endOfMonth, endOfWeek, format, parseISO, startOfDay, startOfMonth, startOfWeek, } from "date-fns";
@@ -17,6 +17,7 @@ import { moveOccurrence, resizeOccurrenceEnd } from "../../../lib/calendar/calen
17
17
  import { resolveDragAction } from "../../../lib/calendar/calendarDragIds.js";
18
18
  import { createFolder } from "../../../lib/mail/mailApi.js";
19
19
  import { expandAllOccurrences } from "../../../lib/calendar/recurrence.js";
20
+ import { useDayKey } from "../../../lib/calendar/useDayKey.js";
20
21
  import { allOccurrences, initialMatchIndex, occurrencesInRange, searchOccurrences, searchTerms, stepSequenceIndex } from "../../../lib/calendar/eventSearch.js";
21
22
  import Drawer from "../../../lib/components/overlays/Drawer.js";
22
23
  import CalendarShell, { useCalendarShell } from "../../shared/components/calendar/layout/CalendarShell.js";
@@ -46,7 +47,7 @@ import { useShortcut } from "../../shared/keyboard/useShortcut.js";
46
47
  import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
47
48
  import { notifyApiError } from "../../shared/notifications/apiErrors.js";
48
49
  import { bookingSettingsHref } from "../../shared/calendar/bookingPlugin.js";
49
- import { redactedEventUidOf } from "../../shared/calendar/calendarLiveUpdates.js";
50
+ import { changedEventOf } from "../../shared/calendar/calendarLiveUpdates.js";
50
51
  /** The furthest instants a `Date` can hold: the List's "range" reaches both. */
51
52
  const MAX_DATE_MS = 8.64e15;
52
53
  function CalendarPage(props) {
@@ -236,15 +237,20 @@ function CalendarContent({ userUid, bookingHref }) {
236
237
  const calendarFolderUidsKey = calendarFolders.map((f) => f.uid).sort().join(",");
237
238
  const checkedFolderUidsKey = Array.from(checkedFolderUids).sort().join(",");
238
239
  useEffect(reload, [calendarFolderUidsKey, checkedFolderUidsKey]);
239
- // A private or confidential event that somebody creates or changes is announced on the push connection as its busy block, whatever the reader may
240
- // see (see `redactedEventUidOf()`): that payload is never stored here. The event is fetched again by its uid, which comes back as much as this
241
- // reader may see - all of it for the owner - and replaces (or joins) what the calendar holds; one that is gone (a 404) leaves it. A failed fetch
242
- // changes nothing: the next load shows the event as it is.
240
+ // An event that somebody creates, changes or deletes is announced on the push connection - a private or confidential one as its busy block, whatever the
241
+ // reader may see (see `changedEventOf()`): that payload is never stored here. The event is fetched again by its uid, which comes back as much as this
242
+ // reader may see - all of it for the owner - and replaces (or joins) what the calendar holds; one that is gone (a 404, or a delete) leaves it. A failed
243
+ // fetch changes nothing: the next load shows the event as it is.
243
244
  const checkedFolderUidsRef = useRef(checkedFolderUids);
244
245
  checkedFolderUidsRef.current = checkedFolderUids;
245
246
  useEffect(() => getPushClient().onEvent((event) => {
246
- const uid = redactedEventUidOf(event);
247
- if (!uid) {
247
+ const change = changedEventOf(event);
248
+ if (!change) {
249
+ return;
250
+ }
251
+ const { uid } = change;
252
+ if (change.deleted) {
253
+ setEvents((previous) => previous.filter((existing) => existing.uid !== uid));
248
254
  return;
249
255
  }
250
256
  void getCalendarEvent(uid, client).then((fresh) => {
@@ -272,11 +278,14 @@ function CalendarContent({ userUid, bookingHref }) {
272
278
  return () => clearTimeout(timer);
273
279
  }, [searchText]);
274
280
  // The List shows only what is from today on, so that is all a search steps through there; the other views reach the past ones.
281
+ // Today moves on at midnight: the List's "from today on" is worked out again then (`listDay` is only a dependency).
282
+ const listDay = useDayKey();
283
+ const dayOfList = view === "list" ? listDay : "";
275
284
  const matches = useMemo(() => {
276
285
  const found = searching ? searchOccurrences(events, query) : [];
277
286
  const today = new Date();
278
287
  return view === "list" ? found.filter((occurrence) => isUpcoming(occurrence, today)) : found;
279
- }, [events, query, searching, view]);
288
+ }, [events, query, searching, view, dayOfList]);
280
289
  const activeIndex = matches.findIndex((m) => m.occurrenceKey === activeKey);
281
290
  // What an open event card steps through, in order: the matches while a search runs, else every event of the checked calendars (bounded as a
282
291
  // search is). The List holds only what is from today on, so that is all its card steps through. Worked out only when something needs it.
@@ -290,7 +299,7 @@ function CalendarContent({ userUid, bookingHref }) {
290
299
  return everything.filter((occurrence) => isUpcoming(occurrence, today));
291
300
  }
292
301
  return everything;
293
- }, [searching, matches, view, everything]);
302
+ }, [searching, matches, view, everything, dayOfList]);
294
303
  const occurrences = useMemo(() => {
295
304
  if (view === "list") {
296
305
  return sequence;
@@ -425,11 +434,12 @@ function CalendarContent({ userUid, bookingHref }) {
425
434
  initialAllDay,
426
435
  });
427
436
  }
428
- function openEvent(occurrence) {
437
+ // Stable (it only sets state), so the List's rows are not rendered again by every keystroke in the search box.
438
+ const openEvent = useCallback((occurrence) => {
429
439
  setSlide(undefined);
430
440
  setActiveKey(occurrence.occurrenceKey);
431
441
  setModal({ occurrence });
432
- }
442
+ }, []);
433
443
  function closeModal() {
434
444
  setModal(null);
435
445
  }
@@ -477,6 +487,8 @@ function CalendarContent({ userUid, bookingHref }) {
477
487
  }
478
488
  catch (err) {
479
489
  notifyApiError(err, action.type === "move" ? "Couldn't move the event" : "Couldn't resize the event");
490
+ // A refused change (a 409 most of all) means the page holds a stale version: load again, or every later drag of this event fails the same way.
491
+ reload();
480
492
  }
481
493
  }
482
494
  const title = view === "list"
@@ -9,12 +9,13 @@ import { useEffect, useState } from "react";
9
9
  import { ApiRequestError } from "../../../lib/util/api.js";
10
10
  import { deleteContact, getContact } from "../../../lib/contacts/contactsApi.js";
11
11
  import { useApiClient } from "../../../lib/util/apiClientContext.js";
12
- import ContactsShell from "../../shared/components/contacts/layout/ContactsShell.js";
12
+ import ContactsShell, { useContactsShell } from "../../shared/components/contacts/layout/ContactsShell.js";
13
13
  import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
14
14
  import ContactForm from "../../shared/components/contacts/ContactForm.js";
15
15
  import Alert from "../../../lib/components/feedback/Alert.js";
16
16
  import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
17
17
  import { notifyApiError } from "../../shared/notifications/apiErrors.js";
18
+ import { useMailboxUpdateAccess } from "../../shared/mail/useMailboxUpdateAccess.js";
18
19
  /**
19
20
  * Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
20
21
  * existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
@@ -31,6 +32,9 @@ function ContactDetailContent({ uid }) {
31
32
  const [loading, setLoading] = useState(true);
32
33
  const [error, setError] = useState(null);
33
34
  const [mode, setMode] = useState("view");
35
+ const { mailboxes } = useContactsShell();
36
+ // A contact in a mailbox shared with the reader view-only can be read, not changed.
37
+ const writable = useMailboxUpdateAccess(mailboxes.find((mailbox) => mailbox.uid === contact?.mailboxUid));
34
38
  useEffect(() => {
35
39
  setLoading(true);
36
40
  setError(null);
@@ -72,7 +76,7 @@ function ContactDetailContent({ uid }) {
72
76
  setMode("view");
73
77
  }, onCancel: () => setMode("view") })] }));
74
78
  }
75
- return (_jsx("div", { className: "p-6", children: _jsx(ContactDetailPane, { contact: contact, onEdit: () => setMode("edit"), onDelete: () => handleDelete(contact), backHref: backHref, onKeysChanged: handleKeysChanged, onChanged: setContact }) }));
79
+ return (_jsx("div", { className: "p-6", children: _jsx(ContactDetailPane, { contact: contact, onEdit: () => setMode("edit"), onDelete: () => handleDelete(contact), canWrite: writable, backHref: backHref, onKeysChanged: handleKeysChanged, onChanged: setContact }) }));
76
80
  }
77
81
  export default ContactDetailPage;
78
82
  /** The tab's title: `Brand: Contacts` (see `pageTitle()`). */
@@ -7,8 +7,9 @@ import { pageTitle } from "../../shared/navigation/pageTitle.js";
7
7
  import { useNavigate } from "../../shared/navigation/index.js";
8
8
  import { useEffect, useMemo, useRef, useState } from "react";
9
9
  import { ApiRequestError } from "../../../lib/util/api.js";
10
- import { createContact, deleteContact, listContacts, listDeletedContacts, setContactFavorite, contactPhotoUrl, updateContact, } from "../../../lib/contacts/contactsApi.js";
11
- import { contactsToVCardFile, contactToVCard, parseVCards } from "../../../lib/contacts/vcard.js";
10
+ import { createContact, deleteContact, listContacts, listContactsInFolders, listDeletedContacts, setContactFavorite, updateContact, } from "../../../lib/contacts/contactsApi.js";
11
+ import { VCARD_MAX_CHARS, VCARD_MAX_CONTACTS, contactsToVCardFile, contactToVCard, parseVCards } from "../../../lib/contacts/vcard.js";
12
+ import { withoutDuplicates } from "../../shared/components/contacts/participantDetails.js";
12
13
  import useIsMobile from "../../../lib/util/useIsMobile.js";
13
14
  import { useApiClient } from "../../../lib/util/apiClientContext.js";
14
15
  import { useCompose } from "../../shared/components/mail/compose/ComposeContext.js";
@@ -17,11 +18,12 @@ import ContactsSidebar from "../../shared/components/contacts/ContactsSidebar.js
17
18
  import ContactsToolbar from "../../shared/components/contacts/ContactsToolbar.js";
18
19
  import FloatingActionButton from "../../shared/components/layout/FloatingActionButton.js";
19
20
  import { HiOutlineUserPlus } from "react-icons/hi2";
20
- import ContactAvatar from "../../../lib/components/avatar/ContactAvatar.js";
21
+ import ContactPhotoAvatar from "../../../lib/components/avatar/ContactPhotoAvatar.js";
21
22
  import ContactDetailPane from "../../shared/components/contacts/ContactDetailPane.js";
22
23
  import ContactForm from "../../shared/components/contacts/ContactForm.js";
23
24
  import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
24
25
  import { getMyMailboxAccess } from "../../../lib/mail/mailboxAccessApi.js";
26
+ import { useMailboxUpdateAccess } from "../../shared/mail/useMailboxUpdateAccess.js";
25
27
  import Alert from "../../../lib/components/feedback/Alert.js";
26
28
  import Button from "../../../lib/components/buttons/Button.js";
27
29
  import Modal from "../../../lib/components/overlays/Modal.js";
@@ -30,6 +32,7 @@ import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigne
30
32
  import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
31
33
  import { useShortcut } from "../../shared/keyboard/useShortcut.js";
32
34
  import { notifyApiError } from "../../shared/notifications/apiErrors.js";
35
+ import { notify } from "../../shared/notifications/store.js";
33
36
  const INPUT_CLASS = "w-full text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
34
37
  function ContactsPage(props) {
35
38
  return (_jsx(ContactsShell, { ...props, children: _jsx(ContactsContent, { userUid: props.userUid }) }));
@@ -57,6 +60,8 @@ function ContactsContent({ userUid }) {
57
60
  const { folderUid, mailboxUid, mailboxes } = useContactsShell();
58
61
  // The new-contact form's Mailbox choices - view-only shares are left out (see writableMailboxes.ts).
59
62
  const writableMailboxes = useWritableMailboxes(mailboxes, userUid, mailboxUid);
63
+ // A mailbox shared with the reader view-only: its contacts can be read, not changed.
64
+ const mailboxWritable = useMailboxUpdateAccess(mailboxes.find((mb) => mb.uid === mailboxUid));
60
65
  const { openCompose } = useCompose();
61
66
  const isMobile = useIsMobile();
62
67
  const navigate = useNavigate();
@@ -263,6 +268,22 @@ function ContactsContent({ userUid }) {
263
268
  }
264
269
  }
265
270
  // The bulk handlers below try every contact and report each failure: the same failure repeated is one pop-up with a count.
271
+ // A bulk action sends every checked row's own version: pressed again before it finishes, it would send stale ones and raise a stream of 409s.
272
+ const bulkRunning = useRef(false);
273
+ function singleFlight(run) {
274
+ return async () => {
275
+ if (bulkRunning.current) {
276
+ return;
277
+ }
278
+ bulkRunning.current = true;
279
+ try {
280
+ await run();
281
+ }
282
+ finally {
283
+ bulkRunning.current = false;
284
+ }
285
+ };
286
+ }
266
287
  async function handleBulkDelete() {
267
288
  setConfirmingBulkDelete(false);
268
289
  for (const contact of checkedContacts) {
@@ -291,7 +312,7 @@ function ContactsContent({ userUid }) {
291
312
  // contacts happen to be open - the compose window's From field can still change it.
292
313
  openCompose({ to: addresses.join(", ") });
293
314
  }
294
- async function handleToggleFavorite() {
315
+ const handleToggleFavorite = singleFlight(async () => {
295
316
  const allFavorited = checkedContacts.every((c) => c.favorite);
296
317
  for (const contact of checkedContacts) {
297
318
  try {
@@ -303,8 +324,8 @@ function ContactsContent({ userUid }) {
303
324
  }
304
325
  clearPinnedSignerCache();
305
326
  await reload();
306
- }
307
- async function handleAddCategory() {
327
+ });
328
+ const handleAddCategory = singleFlight(async () => {
308
329
  const category = window.prompt("Category name");
309
330
  if (!category?.trim()) {
310
331
  return;
@@ -322,7 +343,7 @@ function ContactsContent({ userUid }) {
322
343
  }
323
344
  clearPinnedSignerCache();
324
345
  await reload();
325
- }
346
+ });
326
347
  function handleExportVCard() {
327
348
  downloadTextFile(checkedContacts.length === 1 ? `${checkedContacts[0].displayName}.vcf` : "contacts.vcf", checkedContacts.length === 1 ? contactToVCard(checkedContacts[0]) : contactsToVCardFile(checkedContacts));
328
349
  }
@@ -330,9 +351,37 @@ function ContactsContent({ userUid }) {
330
351
  if (!mailboxUid || !folderUid) {
331
352
  return;
332
353
  }
333
- const text = await file.text();
334
- const parsed = parseVCards(text);
335
- for (const input of parsed) {
354
+ // A file is only read, and only so many records taken from it (see `parseVCards()`): one contact is one request, and a huge export would
355
+ // lock the page for minutes and flood the server. Whatever goes wrong while reading it is said, not left as an unhandled rejection.
356
+ if (file.size > VCARD_MAX_CHARS) {
357
+ notify({ kind: "error", title: "Couldn't import contacts", message: `That file is larger than ${Math.round(VCARD_MAX_CHARS / 1000000)} MB. Split it into smaller files.` });
358
+ return;
359
+ }
360
+ let parsed;
361
+ try {
362
+ parsed = parseVCards(await file.text());
363
+ }
364
+ catch (err) {
365
+ notifyApiError(err, "Couldn't read that contacts file");
366
+ return;
367
+ }
368
+ if (parsed.length >= VCARD_MAX_CONTACTS) {
369
+ notify({ kind: "warning", title: "Only part of the file was imported", message: `A file is imported up to its first ${VCARD_MAX_CONTACTS} contacts - import the rest from another file.` });
370
+ }
371
+ // Importing a file twice must not double every contact: one whose address (or, with no address, name and phone) is already a contact's - or an
372
+ // earlier card's in the file - is left alone. The folder is read in full for that, not just the page of it the list holds.
373
+ let fresh;
374
+ try {
375
+ fresh = withoutDuplicates(parsed, await listContactsInFolders([folderUid], client)).fresh;
376
+ }
377
+ catch (err) {
378
+ notifyApiError(err, "Couldn't check the file against your contacts");
379
+ return;
380
+ }
381
+ if (fresh.length < parsed.length) {
382
+ notify({ kind: "info", title: "Some contacts were skipped", message: `${parsed.length - fresh.length} of the contacts in that file are already in your contacts.` });
383
+ }
384
+ for (const input of fresh) {
336
385
  try {
337
386
  await createContact({ mailboxUid, folderUid, ...input }, client);
338
387
  }
@@ -346,7 +395,9 @@ function ContactsContent({ userUid }) {
346
395
  return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row md:flex-none md:h-[calc(100dvh_-_var(--rr-header-h,4rem))] md:overflow-hidden", children: [_jsx(ContactsSidebar, { mailboxUid: mailboxUid, contacts: contacts, active: view, onSelect: handleSelectView, showDeleted: canViewDeleted }), _jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex", children: [_jsxs("div", { className: [
347
396
  "w-full md:w-[clamp(16rem,32vw,26rem)] min-w-0 md:shrink-0 md:border-r border-border md:flex flex-col min-h-0",
348
397
  mode === "new" ? "hidden md:flex" : "flex",
349
- ].join(" "), children: [_jsx(ContactsToolbar, { selectedCount: checkedContacts.length, allSelectedFavorited: checkedContacts.length > 0 && checkedContacts.every((c) => c.favorite), onNewContact: handleNew, onEdit: handleToolbarEdit, onDelete: () => setConfirmingBulkDelete(true), onEmail: handleEmail, onToggleFavorite: handleToggleFavorite, onAddCategory: handleAddCategory, onExportVCard: handleExportVCard, onImportFile: handleImportFile, shortcuts: true, hideNew: isMobile }), _jsx("div", { className: "p-3 border-b border-border", children: _jsx("input", { ref: searchInputRef, type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search contacts", "aria-label": "Search contacts", className: INPUT_CLASS }) }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), (isDeletedView ? deletedTruncated : truncated) && (_jsx("div", { className: "p-3", children: _jsxs(Alert, { children: ["This folder has more contacts than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] }) })), deletedError && isDeletedView && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: deletedError }) })), (isDeletedView ? deletedLoading : loading) ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : sorted.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "No contacts found." })) : (_jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: _jsxs("table", { className: "w-full table-fixed text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [!isDeletedView && (_jsx("th", { className: "w-10 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all contacts", checked: allChecked, onChange: toggleCheckAll }) })), _jsx("th", { className: "w-[58%] px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("name"), className: "block max-w-full truncate text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Name", sortColumn === "name" ? (sortDesc ? " ▾" : " ▴") : ""] }) }), _jsx("th", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("info"), className: "block max-w-full truncate text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Contact info", sortColumn === "info" ? (sortDesc ? " ▾" : " ▴") : ""] }) })] }) }), _jsx("tbody", { children: sorted.map((contact) => (_jsxs("tr", { className: ["border-b border-border", contact.uid === selectedUid ? "bg-primary/10" : "hover:bg-surface-alt"].join(" "), children: [!isDeletedView && (_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${contact.displayName}`, checked: checkedUids.has(contact.uid), onChange: () => toggleChecked(contact.uid) }) })), _jsx("td", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSelectRow(contact), className: "flex w-full min-w-0 items-center gap-2 text-left", children: [_jsx(ContactAvatar, { displayName: contact.displayName, size: 28, photoUrl: contactPhotoUrl(contact), email: contact.emails[0]?.address }), _jsxs("span", { className: "min-w-0 truncate font-medium", children: [contact.displayName, contact.favorite && _jsx("span", { "aria-label": "Favorite", children: " \u2605" })] })] }) }), _jsx("td", { className: "max-w-0 truncate px-3 py-2 text-text-muted", children: primaryInfo(contact) })] }, contact.uid))) })] }) }))] }), _jsxs("div", { className: ["flex-1 min-w-0 min-h-0 flex-col md:flex", mode === "new" ? "flex" : "hidden"].join(" "), children: [savedElsewhere && mode !== "new" && (_jsxs("p", { role: "status", className: "m-6 mb-0 text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: [savedElsewhere.displayName, " was added to", " ", mailboxes.find((mb) => mb.uid === savedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/contacts?mailboxUid=${encodeURIComponent(savedElsewhere.mailboxUid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s contacts" })] })), mode === "new" && mailboxUid && folderUid ? (_jsx(ContactForm, { mailboxUid: mailboxUid, folderUid: folderUid, mailboxes: writableMailboxes, onSaved: handleSaved, onCancel: handleCancel })) : mode === "edit" && selected ? (_jsx(ContactForm, { contact: selected, onSaved: handleSaved, onCancel: handleCancel })) : selected ? (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-6", children: _jsx(ContactDetailPane, { contact: selected, onEdit: () => setMode("edit"), onDelete: () => handleDelete(selected), onKeysChanged: () => void reload(), onChanged: () => void reload(), canResolveKeys: ownsMailbox || delegateCanUpdate }) })) : (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: _jsx("p", { className: "text-sm text-text-muted", children: "Select a contact, or create a new one." }) }))] })] }), _jsxs(Modal, { open: confirmingBulkDelete, onClose: () => setConfirmingBulkDelete(false), title: "Delete contacts", children: [_jsxs("p", { className: "text-sm mb-5", children: ["Delete ", checkedContacts.length, " selected ", checkedContacts.length === 1 ? "contact" : "contacts", "? They move to Deleted contacts."] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setConfirmingBulkDelete(false), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleBulkDelete, children: "Delete" })] })] }), isMobile && mode !== "new" && _jsx(FloatingActionButton, { label: "New contact", icon: HiOutlineUserPlus, onClick: handleNew })] }));
398
+ ].join(" "), children: [_jsx(ContactsToolbar, { selectedCount: checkedContacts.length, allSelectedFavorited: checkedContacts.length > 0 && checkedContacts.every((c) => c.favorite), onNewContact: handleNew, onEdit: handleToolbarEdit, onDelete: () => setConfirmingBulkDelete(true), onEmail: handleEmail, onToggleFavorite: handleToggleFavorite, onAddCategory: handleAddCategory, onExportVCard: handleExportVCard, onImportFile: handleImportFile, shortcuts: true, hideNew: isMobile }), _jsx("div", { className: "p-3 border-b border-border", children: _jsx("input", { ref: searchInputRef, type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search contacts", "aria-label": "Search contacts", className: INPUT_CLASS }) }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), (isDeletedView ? deletedTruncated : truncated) && (_jsx("div", { className: "p-3", children: _jsxs(Alert, { children: ["This folder has more contacts than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] }) })), deletedError && isDeletedView && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: deletedError }) })), (isDeletedView ? deletedLoading : loading) ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : sorted.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "No contacts found." })) : (_jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: _jsxs("table", { className: "w-full table-fixed text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [!isDeletedView && (_jsx("th", { className: "w-10 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all contacts", checked: allChecked, onChange: toggleCheckAll }) })), _jsx("th", { className: "w-[58%] px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("name"), className: "block max-w-full truncate text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Name", sortColumn === "name" ? (sortDesc ? " ▾" : " ▴") : ""] }) }), _jsx("th", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("info"), className: "block max-w-full truncate text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Contact info", sortColumn === "info" ? (sortDesc ? " ▾" : " ▴") : ""] }) })] }) }), _jsx("tbody", { children: sorted.map((contact) => (_jsxs("tr", { className: ["border-b border-border", contact.uid === selectedUid ? "bg-primary/10" : "hover:bg-surface-alt"].join(" "), children: [!isDeletedView && (_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${contact.displayName}`, checked: checkedUids.has(contact.uid), onChange: () => toggleChecked(contact.uid) }) })), _jsx("td", { className: "px-3 py-2", children: _jsxs("button", { type: "button",
399
+ // A deleted contact has no pane to show (it can't be restored or opened), and its route would answer 404.
400
+ disabled: isDeletedView, onClick: () => handleSelectRow(contact), className: "flex w-full min-w-0 items-center gap-2 text-left disabled:cursor-default", children: [_jsx(ContactPhotoAvatar, { lazy: true, displayName: contact.displayName, size: 28, contact: contact, email: contact.emails[0]?.address }), _jsxs("span", { className: "min-w-0 truncate font-medium", children: [contact.displayName, contact.favorite && _jsx("span", { "aria-label": "Favorite", children: " \u2605" })] })] }) }), _jsx("td", { className: "max-w-0 truncate px-3 py-2 text-text-muted", children: primaryInfo(contact) })] }, contact.uid))) })] }) }))] }), _jsxs("div", { className: ["flex-1 min-w-0 min-h-0 flex-col md:flex", mode === "new" ? "flex" : "hidden"].join(" "), children: [savedElsewhere && mode !== "new" && (_jsxs("p", { role: "status", className: "m-6 mb-0 text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: [savedElsewhere.displayName, " was added to", " ", mailboxes.find((mb) => mb.uid === savedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/contacts?mailboxUid=${encodeURIComponent(savedElsewhere.mailboxUid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s contacts" })] })), mode === "new" && mailboxUid && folderUid ? (_jsx(ContactForm, { mailboxUid: mailboxUid, folderUid: folderUid, mailboxes: writableMailboxes, onSaved: handleSaved, onCancel: handleCancel })) : mode === "edit" && selected ? (_jsx(ContactForm, { contact: selected, onSaved: handleSaved, onCancel: handleCancel })) : selected ? (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-6", children: _jsx(ContactDetailPane, { contact: selected, onEdit: () => setMode("edit"), onDelete: () => handleDelete(selected), canWrite: mailboxWritable, onKeysChanged: () => void reload(), onChanged: () => void reload(), canResolveKeys: ownsMailbox || delegateCanUpdate }) })) : (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: _jsx("p", { className: "text-sm text-text-muted", children: "Select a contact, or create a new one." }) }))] })] }), _jsxs(Modal, { open: confirmingBulkDelete, onClose: () => setConfirmingBulkDelete(false), title: "Delete contacts", children: [_jsxs("p", { className: "text-sm mb-5", children: ["Delete ", checkedContacts.length, " selected ", checkedContacts.length === 1 ? "contact" : "contacts", "? They move to Deleted contacts."] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setConfirmingBulkDelete(false), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleBulkDelete, children: "Delete" })] })] }), isMobile && mode !== "new" && _jsx(FloatingActionButton, { label: "New contact", icon: HiOutlineUserPlus, onClick: handleNew })] }));
350
401
  }
351
402
  export default ContactsPage;
352
403
  /** The tab's title: `Brand: Contacts` (see `pageTitle()`). */