@rapidmx/web-client 0.20.0 → 0.22.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 (906) hide show
  1. package/README.md +415 -381
  2. package/apps/admin/_layout.tsx +2 -2
  3. package/apps/admin/audit-log/index.tsx +4 -4
  4. package/apps/admin/branding/index.tsx +39 -39
  5. package/apps/admin/data-requests/index.tsx +482 -482
  6. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  7. package/apps/admin/distribution-lists/index.tsx +4 -4
  8. package/apps/admin/distribution-lists/new/index.tsx +5 -5
  9. package/apps/admin/domains/[uid].tsx +166 -166
  10. package/apps/admin/domains/index.tsx +4 -4
  11. package/apps/admin/domains/new/index.tsx +5 -5
  12. package/apps/admin/encryption-policy/index.tsx +1 -1
  13. package/apps/admin/escrow-scopes/[uid].tsx +350 -350
  14. package/apps/admin/escrow-scopes/index.tsx +4 -4
  15. package/apps/admin/escrow-scopes/new/index.tsx +5 -5
  16. package/apps/admin/index.tsx +129 -129
  17. package/apps/admin/ingest-queue/index.tsx +4 -4
  18. package/apps/admin/mailbox-policy/index.tsx +1 -1
  19. package/apps/admin/mailboxes/[uid].tsx +271 -271
  20. package/apps/admin/mailboxes/new/index.tsx +30 -30
  21. package/apps/admin/plugins/index.tsx +15 -15
  22. package/apps/admin/quarantine/index.tsx +5 -5
  23. package/apps/admin/retention-policy/index.tsx +39 -39
  24. package/apps/admin/signing-certificates/index.tsx +343 -343
  25. package/apps/admin/transport-rules/[uid].tsx +5 -5
  26. package/apps/admin/transport-rules/_transportRuleConfig.tsx +3 -3
  27. package/apps/admin/transport-rules/index.tsx +4 -4
  28. package/apps/admin/transport-rules/new/index.tsx +5 -5
  29. package/apps/escrow/_layout.tsx +1 -1
  30. package/apps/escrow/audit-log/index.tsx +196 -196
  31. package/apps/escrow/index.tsx +4 -4
  32. package/apps/escrow/matters/[uid].tsx +621 -621
  33. package/apps/escrow/matters/new/index.tsx +6 -6
  34. package/apps/shared/appearance/AppearanceForm.tsx +3 -3
  35. package/apps/shared/appearance/AppearanceHead.tsx +1 -1
  36. package/apps/shared/appearance/AppearanceProvider.tsx +3 -3
  37. package/apps/shared/appearance/appearanceCache.ts +1 -1
  38. package/apps/shared/appearance/appearanceContext.ts +1 -1
  39. package/apps/shared/appearance/theme.ts +1 -1
  40. package/apps/shared/auth/adminAccess.ts +99 -99
  41. package/apps/shared/calendar/bookingPlugin.ts +1 -1
  42. package/apps/shared/calendar/calendarLiveUpdates.ts +1 -1
  43. package/apps/shared/calendar/calendarReminders.ts +1 -1
  44. package/apps/shared/calendar/useCalendarReminders.ts +1 -1
  45. package/apps/shared/components/admin/diagnostics/DiagnosticsManager.tsx +1 -1
  46. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +2 -2
  47. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +2 -2
  48. package/apps/shared/components/admin/diagnostics/PackagesTable.tsx +1 -1
  49. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  50. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +2 -2
  51. package/apps/shared/components/admin/diagnostics/VersionsPanel.tsx +1 -1
  52. package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +1 -1
  53. package/apps/shared/components/admin/diagnostics/format.ts +1 -1
  54. package/apps/shared/components/admin/diagnostics/logClient.ts +1 -1
  55. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  56. package/apps/shared/components/admin/elevation.ts +1 -1
  57. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +3 -3
  58. package/apps/shared/components/admin/layout/AdminShell.tsx +384 -384
  59. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -238
  60. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +154 -152
  61. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -194
  62. package/apps/shared/components/admin/mailboxes/MailboxTable.tsx +1 -1
  63. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +19 -14
  64. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +5 -5
  65. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  66. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +5 -5
  67. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  68. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +2 -2
  69. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +32 -27
  70. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  71. package/apps/shared/components/admin/settings/PluginsManager.tsx +2093 -2093
  72. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  73. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  74. package/apps/shared/components/admin/setup/SetupWizard.tsx +450 -446
  75. package/apps/shared/components/admin/signOut.ts +1 -1
  76. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  77. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +3 -3
  78. package/apps/shared/components/calendar/CalendarListSidebar.tsx +3 -3
  79. package/apps/shared/components/calendar/DescriptionEditor.tsx +1 -1
  80. package/apps/shared/components/calendar/EventDescriptionView.tsx +1 -1
  81. package/apps/shared/components/calendar/EventDetails.tsx +16 -14
  82. package/apps/shared/components/calendar/EventEditor.tsx +54 -39
  83. package/apps/shared/components/calendar/EventExpandedForm.tsx +3 -3
  84. package/apps/shared/components/calendar/EventFormParts.tsx +2 -2
  85. package/apps/shared/components/calendar/EventModal.tsx +206 -206
  86. package/apps/shared/components/calendar/EventQuickForm.tsx +3 -3
  87. package/apps/shared/components/calendar/EventShell.tsx +1 -1
  88. package/apps/shared/components/calendar/FindATime.tsx +6 -4
  89. package/apps/shared/components/calendar/GuestInput.tsx +2 -2
  90. package/apps/shared/components/calendar/MonthView.tsx +185 -185
  91. package/apps/shared/components/calendar/QuickCreateFaces.tsx +1 -1
  92. package/apps/shared/components/calendar/QuickFaceFrame.tsx +1 -1
  93. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
  94. package/apps/shared/components/calendar/RequestChangeForm.tsx +8 -6
  95. package/apps/shared/components/calendar/ResourcePicker.tsx +7 -5
  96. package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
  97. package/apps/shared/components/calendar/TaskQuickForm.tsx +23 -18
  98. package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
  99. package/apps/shared/components/calendar/allDay.ts +124 -124
  100. package/apps/shared/components/calendar/eventDialogFields.ts +3 -3
  101. package/apps/shared/components/calendar/eventForm.ts +3 -3
  102. package/apps/shared/components/calendar/eventFormat.ts +3 -3
  103. package/apps/shared/components/calendar/findTimeGrid.ts +2 -2
  104. package/apps/shared/components/calendar/layout/CalendarShell.tsx +11 -9
  105. package/apps/shared/components/contacts/ContactDetailPane.tsx +4 -4
  106. package/apps/shared/components/contacts/ContactForm.tsx +393 -383
  107. package/apps/shared/components/contacts/ContactsSidebar.tsx +7 -5
  108. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -103
  109. package/apps/shared/components/contacts/KeyChangeReview.tsx +5 -5
  110. package/apps/shared/components/contacts/contactKeys.ts +3 -3
  111. package/apps/shared/components/contacts/layout/ContactsShell.tsx +9 -7
  112. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  113. package/apps/shared/components/forms/StringListField.tsx +1 -1
  114. package/apps/shared/components/layout/AppShell.tsx +465 -461
  115. package/apps/shared/components/layout/BrandingChrome.tsx +1 -1
  116. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  117. package/apps/shared/components/layout/MailboxProvisioning.tsx +170 -168
  118. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +9 -9
  119. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  120. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  121. package/apps/shared/components/layout/UnlockPromptProvider.tsx +6 -6
  122. package/apps/shared/components/layout/UserMenu.tsx +441 -439
  123. package/apps/shared/components/mail/ConversationList.tsx +334 -332
  124. package/apps/shared/components/mail/ConversationThreadPane.tsx +624 -613
  125. package/apps/shared/components/mail/InviteCard.tsx +11 -9
  126. package/apps/shared/components/mail/LazyReadingPane.tsx +2 -2
  127. package/apps/shared/components/mail/MailAddress.tsx +1 -1
  128. package/apps/shared/components/mail/MailListToolbar.tsx +2 -2
  129. package/apps/shared/components/mail/MailSelectionBar.tsx +240 -240
  130. package/apps/shared/components/mail/MenuButton.tsx +404 -404
  131. package/apps/shared/components/mail/MessageDetailPane.tsx +1759 -1757
  132. package/apps/shared/components/mail/MoveToFolderDialog.tsx +8 -6
  133. package/apps/shared/components/mail/OutboxRowStatus.tsx +1 -1
  134. package/apps/shared/components/mail/PermanentDeleteDialog.tsx +2 -2
  135. package/apps/shared/components/mail/compose/ComposeContext.tsx +323 -312
  136. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  137. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1709 -1681
  138. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +4 -4
  139. package/apps/shared/components/mail/compose/EmojiPicker.tsx +2 -2
  140. package/apps/shared/components/mail/compose/GifPicker.tsx +3 -3
  141. package/apps/shared/components/mail/compose/RecipientInput.tsx +1 -1
  142. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  143. package/apps/shared/components/mail/compose/ScheduleSendPicker.tsx +1 -1
  144. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  145. package/apps/shared/components/mail/compose/encryptionRequirement.ts +2 -2
  146. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  147. package/apps/shared/components/mail/compose/recipients.ts +1 -1
  148. package/apps/shared/components/mail/invite/InviteRowChip.tsx +2 -2
  149. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +9 -7
  150. package/apps/shared/components/mail/invite/InviteTimeline.tsx +1 -1
  151. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +2 -2
  152. package/apps/shared/components/mail/invite/inviteFormat.ts +2 -2
  153. package/apps/shared/components/mail/invite/inviteStore.ts +14 -6
  154. package/apps/shared/components/mail/labelMenu.tsx +9 -7
  155. package/apps/shared/components/mail/layout/MailShell.tsx +4 -4
  156. package/apps/shared/components/mail/listPreferences.ts +2 -2
  157. package/apps/shared/components/mail/pinnedSigners.ts +45 -19
  158. package/apps/shared/components/mail/reading/EncryptedBody.tsx +2 -2
  159. package/apps/shared/components/mail/reading/MessageBody.tsx +6 -6
  160. package/apps/shared/components/mail/reading/MessageCard.tsx +3 -3
  161. package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -258
  162. package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -79
  163. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +1 -1
  164. package/apps/shared/components/mail/reading/bodyContent.ts +3 -3
  165. package/apps/shared/components/mail/reading/bodyHtml.ts +1 -1
  166. package/apps/shared/components/mail/reading/messageExport.ts +59 -59
  167. package/apps/shared/components/mail/reading/printMessage.ts +99 -99
  168. package/apps/shared/components/mail/reading/useMessageActions.ts +450 -443
  169. package/apps/shared/components/mail/unreadStyle.tsx +1 -1
  170. package/apps/shared/components/mail/verificationSeals.ts +129 -125
  171. package/apps/shared/components/mail/writableMailboxes.ts +14 -7
  172. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  173. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  174. package/apps/shared/components/settings/layout/SettingsShell.tsx +8 -6
  175. package/apps/shared/components/sharing/PrincipalListField.tsx +1 -1
  176. package/apps/shared/components/sharing/PrincipalPicker.tsx +1 -1
  177. package/apps/shared/components/sharing/PrincipalResolver.tsx +4 -4
  178. package/apps/shared/components/tasks/TasksSidebar.tsx +7 -5
  179. package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -7
  180. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  181. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  182. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  183. package/apps/shared/keyboard/dispatch.ts +124 -124
  184. package/apps/shared/keyboard/format.ts +89 -89
  185. package/apps/shared/keyboard/keymap.ts +114 -114
  186. package/apps/shared/keyboard/registry.ts +65 -65
  187. package/apps/shared/keyboard/targets.ts +79 -79
  188. package/apps/shared/mail/findWellKnownFolderUid.ts +6 -4
  189. package/apps/shared/mail/folderCounts.ts +8 -4
  190. package/apps/shared/mail/folderOfType.ts +51 -49
  191. package/apps/shared/mail/folderTree.ts +143 -143
  192. package/apps/shared/mail/listAllPages.ts +39 -39
  193. package/apps/shared/mail/listSnapshots.ts +2 -2
  194. package/apps/shared/mail/messageReadState.ts +6 -5
  195. package/apps/shared/mail/newMailNotifications.ts +171 -171
  196. package/apps/shared/mail/outbox/composeBridge.ts +2 -2
  197. package/apps/shared/mail/outbox/outboxState.ts +1 -1
  198. package/apps/shared/mail/outbox/outgoingReplies.ts +2 -2
  199. package/apps/shared/mail/outbox/sendDecision.ts +2 -2
  200. package/apps/shared/mail/outbox/sendJob.ts +450 -445
  201. package/apps/shared/mail/outbox/sendOutcomes.ts +160 -155
  202. package/apps/shared/mail/outbox/useOutboxStatus.ts +5 -3
  203. package/apps/shared/mail/permanentDelete.ts +8 -7
  204. package/apps/shared/mail/primaryMailbox.ts +1 -1
  205. package/apps/shared/mail/reportNotices.ts +66 -66
  206. package/apps/shared/mail/senderBlocking.ts +157 -141
  207. package/apps/shared/mail/useMailConnection.ts +207 -205
  208. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  209. package/apps/shared/mail/useMailboxUpdateAccess.ts +84 -60
  210. package/apps/shared/mail/useMarkMessageRead.ts +54 -47
  211. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  212. package/apps/shared/mail/usePermanentDelete.tsx +5 -3
  213. package/apps/shared/navigation/pageTitle.ts +1 -1
  214. package/apps/shared/notifications/NotificationCenter.tsx +1 -1
  215. package/apps/shared/notifications/NotificationHistoryDialog.tsx +2 -2
  216. package/apps/shared/notifications/apiErrors.ts +2 -2
  217. package/apps/shared/notifications/pushStatus.ts +1 -1
  218. package/apps/shared/notifications/store.ts +560 -560
  219. package/apps/shared/notifications/systemErrors.ts +1 -1
  220. package/apps/shared/search/LocalIndexLifecycle.tsx +120 -114
  221. package/apps/shared/search/crossMailboxSearch.ts +3 -3
  222. package/apps/shared/search/localIndexBuilder.ts +493 -481
  223. package/apps/shared/search/localIndexKey.ts +1 -1
  224. package/apps/shared/search/localIndexRpcClient.ts +150 -30
  225. package/apps/shared/search/localIndexTransport.ts +84 -0
  226. package/apps/shared/search/localIndexWorker.ts +1 -1
  227. package/apps/shared/search/searchTier2.ts +3 -3
  228. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  229. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  230. package/apps/shared/signing/enrollmentView.ts +251 -251
  231. package/apps/shared/signing/signingInfo.ts +1 -1
  232. package/apps/shared/signing/useNow.ts +19 -19
  233. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +92 -90
  234. package/apps/shared/styles/app.css +387 -396
  235. package/apps/www/_layout.tsx +2 -2
  236. package/apps/www/calendar/index.tsx +583 -581
  237. package/apps/www/contacts/[uid].tsx +114 -112
  238. package/apps/www/contacts/index.tsx +19 -17
  239. package/apps/www/index.tsx +3021 -3012
  240. package/apps/www/messages/[uid].tsx +141 -139
  241. package/apps/www/settings/auto-reply/index.tsx +138 -136
  242. package/apps/www/settings/blocked-senders/index.tsx +305 -303
  243. package/apps/www/settings/encryption/index.tsx +1296 -1290
  244. package/apps/www/settings/filters/[uid].tsx +184 -179
  245. package/apps/www/settings/filters/_mailFilterRuleConfig.tsx +2 -2
  246. package/apps/www/settings/filters/index.tsx +105 -105
  247. package/apps/www/settings/filters/new/index.tsx +167 -165
  248. package/apps/www/settings/labels/index.tsx +209 -207
  249. package/apps/www/settings/privacy/index.tsx +499 -495
  250. package/apps/www/settings/profile/index.tsx +256 -251
  251. package/apps/www/settings/read-receipts/index.tsx +155 -150
  252. package/apps/www/settings/sharing/index.tsx +259 -259
  253. package/apps/www/settings/signatures/[uid].tsx +175 -175
  254. package/apps/www/settings/signatures/index.tsx +91 -91
  255. package/apps/www/settings/signatures/new/index.tsx +138 -138
  256. package/apps/www/settings/signatures/signatureDefaults.ts +1 -1
  257. package/apps/www/tasks/index.tsx +659 -654
  258. package/dist/apps/admin/_layout.d.ts +1 -1
  259. package/dist/apps/admin/_layout.js +1 -1
  260. package/dist/apps/admin/audit-log/index.d.ts +1 -1
  261. package/dist/apps/admin/audit-log/index.js +4 -4
  262. package/dist/apps/admin/branding/index.js +3 -3
  263. package/dist/apps/admin/data-requests/index.js +8 -8
  264. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  265. package/dist/apps/admin/distribution-lists/index.js +4 -4
  266. package/dist/apps/admin/distribution-lists/new/index.js +5 -5
  267. package/dist/apps/admin/domains/[uid].js +6 -6
  268. package/dist/apps/admin/domains/index.js +4 -4
  269. package/dist/apps/admin/domains/new/index.js +5 -5
  270. package/dist/apps/admin/encryption-policy/index.js +1 -1
  271. package/dist/apps/admin/escrow-scopes/[uid].d.ts +1 -1
  272. package/dist/apps/admin/escrow-scopes/[uid].js +7 -7
  273. package/dist/apps/admin/escrow-scopes/index.js +4 -4
  274. package/dist/apps/admin/escrow-scopes/new/index.js +5 -5
  275. package/dist/apps/admin/index.js +6 -6
  276. package/dist/apps/admin/ingest-queue/index.js +4 -4
  277. package/dist/apps/admin/mailbox-policy/index.js +1 -1
  278. package/dist/apps/admin/mailboxes/[uid].js +5 -5
  279. package/dist/apps/admin/quarantine/index.js +5 -5
  280. package/dist/apps/admin/retention-policy/index.js +3 -3
  281. package/dist/apps/admin/signing-certificates/index.js +5 -5
  282. package/dist/apps/admin/transport-rules/[uid].js +5 -5
  283. package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +1 -1
  284. package/dist/apps/admin/transport-rules/_transportRuleConfig.js +2 -2
  285. package/dist/apps/admin/transport-rules/index.js +4 -4
  286. package/dist/apps/admin/transport-rules/new/index.js +5 -5
  287. package/dist/apps/escrow/_layout.d.ts +1 -1
  288. package/dist/apps/escrow/audit-log/index.d.ts +1 -1
  289. package/dist/apps/escrow/audit-log/index.js +4 -4
  290. package/dist/apps/escrow/index.js +4 -4
  291. package/dist/apps/escrow/matters/[uid].js +10 -10
  292. package/dist/apps/escrow/matters/new/index.js +6 -6
  293. package/dist/apps/shared/appearance/AppearanceForm.js +3 -3
  294. package/dist/apps/shared/appearance/AppearanceHead.js +1 -1
  295. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +1 -1
  296. package/dist/apps/shared/appearance/AppearanceProvider.js +3 -3
  297. package/dist/apps/shared/appearance/appearanceCache.d.ts +1 -1
  298. package/dist/apps/shared/appearance/appearanceCache.js +1 -1
  299. package/dist/apps/shared/appearance/appearanceContext.d.ts +1 -1
  300. package/dist/apps/shared/appearance/theme.d.ts +1 -1
  301. package/dist/apps/shared/auth/adminAccess.js +1 -1
  302. package/dist/apps/shared/calendar/bookingPlugin.js +1 -1
  303. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +1 -1
  304. package/dist/apps/shared/calendar/calendarReminders.d.ts +1 -1
  305. package/dist/apps/shared/calendar/useCalendarReminders.js +1 -1
  306. package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.js +1 -1
  307. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.d.ts +1 -1
  308. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +2 -2
  309. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +2 -2
  310. package/dist/apps/shared/components/admin/diagnostics/PackagesTable.js +1 -1
  311. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  312. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +2 -2
  313. package/dist/apps/shared/components/admin/diagnostics/VersionsPanel.js +1 -1
  314. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.js +1 -1
  315. package/dist/apps/shared/components/admin/diagnostics/format.js +1 -1
  316. package/dist/apps/shared/components/admin/diagnostics/logClient.js +1 -1
  317. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +1 -1
  318. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  319. package/dist/apps/shared/components/admin/elevation.js +1 -1
  320. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -3
  321. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  322. package/dist/apps/shared/components/admin/layout/AdminShell.js +8 -8
  323. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +1 -1
  324. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +6 -6
  325. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +1 -1
  326. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +10 -8
  327. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  328. package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +1 -1
  329. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +1 -1
  330. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +7 -5
  331. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +5 -5
  332. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +1 -1
  333. package/dist/apps/shared/components/admin/settings/BrandingForm.js +8 -8
  334. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +1 -1
  335. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +5 -5
  336. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +1 -1
  337. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +4 -4
  338. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +2 -2
  339. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +1 -1
  340. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +14 -12
  341. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +1 -1
  342. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +4 -4
  343. package/dist/apps/shared/components/admin/settings/PluginsManager.js +5 -5
  344. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +1 -1
  345. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +5 -5
  346. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +7 -7
  347. package/dist/apps/shared/components/admin/setup/SetupWizard.js +26 -22
  348. package/dist/apps/shared/components/admin/signOut.js +1 -1
  349. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  350. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +3 -3
  351. package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +1 -1
  352. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +2 -2
  353. package/dist/apps/shared/components/calendar/DescriptionEditor.js +1 -1
  354. package/dist/apps/shared/components/calendar/EventDescriptionView.js +1 -1
  355. package/dist/apps/shared/components/calendar/EventDetails.d.ts +2 -2
  356. package/dist/apps/shared/components/calendar/EventDetails.js +16 -14
  357. package/dist/apps/shared/components/calendar/EventEditor.d.ts +2 -2
  358. package/dist/apps/shared/components/calendar/EventEditor.js +22 -20
  359. package/dist/apps/shared/components/calendar/EventExpandedForm.js +2 -2
  360. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +1 -1
  361. package/dist/apps/shared/components/calendar/EventFormParts.js +1 -1
  362. package/dist/apps/shared/components/calendar/EventModal.d.ts +2 -2
  363. package/dist/apps/shared/components/calendar/EventModal.js +3 -3
  364. package/dist/apps/shared/components/calendar/EventQuickForm.js +3 -3
  365. package/dist/apps/shared/components/calendar/EventShell.js +1 -1
  366. package/dist/apps/shared/components/calendar/FindATime.js +6 -4
  367. package/dist/apps/shared/components/calendar/GuestInput.d.ts +2 -2
  368. package/dist/apps/shared/components/calendar/MonthView.d.ts +1 -1
  369. package/dist/apps/shared/components/calendar/MonthView.js +1 -1
  370. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +1 -1
  371. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +1 -1
  372. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +1 -1
  373. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +1 -1
  374. package/dist/apps/shared/components/calendar/RequestChangeForm.js +8 -6
  375. package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +1 -1
  376. package/dist/apps/shared/components/calendar/ResourcePicker.js +7 -5
  377. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +1 -1
  378. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +1 -1
  379. package/dist/apps/shared/components/calendar/TaskQuickForm.js +9 -7
  380. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +1 -1
  381. package/dist/apps/shared/components/calendar/TimeGridView.js +1 -1
  382. package/dist/apps/shared/components/calendar/allDay.d.ts +2 -2
  383. package/dist/apps/shared/components/calendar/allDay.js +1 -1
  384. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +2 -2
  385. package/dist/apps/shared/components/calendar/eventDialogFields.js +2 -2
  386. package/dist/apps/shared/components/calendar/eventForm.d.ts +3 -3
  387. package/dist/apps/shared/components/calendar/eventFormat.d.ts +1 -1
  388. package/dist/apps/shared/components/calendar/eventFormat.js +2 -2
  389. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +1 -1
  390. package/dist/apps/shared/components/calendar/findTimeGrid.js +1 -1
  391. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  392. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +11 -9
  393. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +1 -1
  394. package/dist/apps/shared/components/contacts/ContactDetailPane.js +2 -2
  395. package/dist/apps/shared/components/contacts/ContactForm.d.ts +2 -2
  396. package/dist/apps/shared/components/contacts/ContactForm.js +12 -8
  397. package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +1 -1
  398. package/dist/apps/shared/components/contacts/ContactsSidebar.js +7 -5
  399. package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +1 -1
  400. package/dist/apps/shared/components/contacts/KeyChangeReview.js +5 -5
  401. package/dist/apps/shared/components/contacts/contactKeys.d.ts +2 -2
  402. package/dist/apps/shared/components/contacts/contactKeys.js +2 -2
  403. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  404. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +9 -7
  405. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -7
  406. package/dist/apps/shared/components/forms/StringListField.js +1 -1
  407. package/dist/apps/shared/components/layout/AppShell.d.ts +2 -2
  408. package/dist/apps/shared/components/layout/AppShell.js +14 -10
  409. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +1 -1
  410. package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +1 -1
  411. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +9 -9
  412. package/dist/apps/shared/components/layout/MailboxProvisioning.js +8 -6
  413. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +1 -1
  414. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +9 -9
  415. package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +2 -2
  416. package/dist/apps/shared/components/layout/UnlockPromptProvider.js +5 -5
  417. package/dist/apps/shared/components/layout/UserMenu.js +6 -4
  418. package/dist/apps/shared/components/mail/ConversationList.d.ts +2 -2
  419. package/dist/apps/shared/components/mail/ConversationList.js +6 -4
  420. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +3 -3
  421. package/dist/apps/shared/components/mail/ConversationThreadPane.js +13 -11
  422. package/dist/apps/shared/components/mail/InviteCard.js +10 -8
  423. package/dist/apps/shared/components/mail/LazyReadingPane.js +2 -2
  424. package/dist/apps/shared/components/mail/MailAddress.d.ts +1 -1
  425. package/dist/apps/shared/components/mail/MailAddress.js +1 -1
  426. package/dist/apps/shared/components/mail/MailListToolbar.d.ts +2 -2
  427. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +2 -2
  428. package/dist/apps/shared/components/mail/MenuButton.js +1 -1
  429. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +3 -3
  430. package/dist/apps/shared/components/mail/MessageDetailPane.js +30 -28
  431. package/dist/apps/shared/components/mail/MoveToFolderDialog.d.ts +1 -1
  432. package/dist/apps/shared/components/mail/MoveToFolderDialog.js +8 -6
  433. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +1 -1
  434. package/dist/apps/shared/components/mail/PermanentDeleteDialog.js +2 -2
  435. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +10 -1
  436. package/dist/apps/shared/components/mail/compose/ComposeContext.js +4 -2
  437. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +7 -1
  438. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +54 -31
  439. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +4 -4
  440. package/dist/apps/shared/components/mail/compose/EmojiPicker.js +2 -2
  441. package/dist/apps/shared/components/mail/compose/GifPicker.js +3 -3
  442. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +1 -1
  443. package/dist/apps/shared/components/mail/compose/RecipientInput.js +1 -1
  444. package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.js +1 -1
  445. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +2 -2
  446. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +1 -1
  447. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +3 -3
  448. package/dist/apps/shared/components/mail/compose/quotedBody.js +3 -3
  449. package/dist/apps/shared/components/mail/compose/recipients.d.ts +1 -1
  450. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +1 -1
  451. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +1 -1
  452. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +9 -7
  453. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +1 -1
  454. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +1 -1
  455. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +1 -1
  456. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +2 -2
  457. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +9 -3
  458. package/dist/apps/shared/components/mail/invite/inviteStore.js +13 -6
  459. package/dist/apps/shared/components/mail/labelMenu.d.ts +1 -1
  460. package/dist/apps/shared/components/mail/labelMenu.js +9 -7
  461. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +1 -1
  462. package/dist/apps/shared/components/mail/layout/MailShell.js +3 -3
  463. package/dist/apps/shared/components/mail/listPreferences.d.ts +2 -2
  464. package/dist/apps/shared/components/mail/listPreferences.js +1 -1
  465. package/dist/apps/shared/components/mail/pinnedSigners.d.ts +12 -5
  466. package/dist/apps/shared/components/mail/pinnedSigners.js +37 -18
  467. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +2 -2
  468. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +2 -2
  469. package/dist/apps/shared/components/mail/reading/MessageBody.js +4 -4
  470. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +1 -1
  471. package/dist/apps/shared/components/mail/reading/MessageCard.js +2 -2
  472. package/dist/apps/shared/components/mail/reading/MessageSourceDialog.js +3 -3
  473. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +1 -1
  474. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +2 -2
  475. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -2
  476. package/dist/apps/shared/components/mail/reading/bodyHtml.js +1 -1
  477. package/dist/apps/shared/components/mail/reading/messageExport.js +1 -1
  478. package/dist/apps/shared/components/mail/reading/printMessage.d.ts +2 -2
  479. package/dist/apps/shared/components/mail/reading/printMessage.js +11 -11
  480. package/dist/apps/shared/components/mail/reading/useMessageActions.d.ts +1 -1
  481. package/dist/apps/shared/components/mail/reading/useMessageActions.js +18 -16
  482. package/dist/apps/shared/components/mail/unreadStyle.d.ts +1 -1
  483. package/dist/apps/shared/components/mail/verificationSeals.d.ts +27 -6
  484. package/dist/apps/shared/components/mail/verificationSeals.js +15 -11
  485. package/dist/apps/shared/components/mail/writableMailboxes.d.ts +8 -3
  486. package/dist/apps/shared/components/mail/writableMailboxes.js +12 -6
  487. package/dist/apps/shared/components/rules/RuleBuilder.js +1 -1
  488. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +1 -1
  489. package/dist/apps/shared/components/settings/SigningCertificateCard.js +3 -3
  490. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  491. package/dist/apps/shared/components/settings/layout/SettingsShell.js +8 -6
  492. package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +1 -1
  493. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +1 -1
  494. package/dist/apps/shared/components/sharing/PrincipalPicker.js +1 -1
  495. package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +1 -1
  496. package/dist/apps/shared/components/sharing/PrincipalResolver.js +3 -3
  497. package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +1 -1
  498. package/dist/apps/shared/components/tasks/TasksSidebar.js +7 -5
  499. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  500. package/dist/apps/shared/components/tasks/layout/TasksShell.js +9 -7
  501. package/dist/apps/shared/keyboard/ShortcutsDialog.js +1 -1
  502. package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -3
  503. package/dist/apps/shared/mail/findWellKnownFolderUid.js +5 -4
  504. package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
  505. package/dist/apps/shared/mail/folderCounts.js +6 -4
  506. package/dist/apps/shared/mail/folderOfType.d.ts +3 -2
  507. package/dist/apps/shared/mail/folderOfType.js +4 -4
  508. package/dist/apps/shared/mail/folderTree.d.ts +1 -1
  509. package/dist/apps/shared/mail/listSnapshots.d.ts +2 -2
  510. package/dist/apps/shared/mail/messageReadState.d.ts +4 -3
  511. package/dist/apps/shared/mail/messageReadState.js +5 -5
  512. package/dist/apps/shared/mail/newMailNotifications.d.ts +1 -1
  513. package/dist/apps/shared/mail/newMailNotifications.js +1 -1
  514. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +2 -2
  515. package/dist/apps/shared/mail/outbox/outboxState.d.ts +1 -1
  516. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +2 -2
  517. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +2 -2
  518. package/dist/apps/shared/mail/outbox/sendDecision.js +1 -1
  519. package/dist/apps/shared/mail/outbox/sendJob.d.ts +10 -6
  520. package/dist/apps/shared/mail/outbox/sendJob.js +39 -35
  521. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +6 -3
  522. package/dist/apps/shared/mail/outbox/sendOutcomes.js +19 -15
  523. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +5 -3
  524. package/dist/apps/shared/mail/permanentDelete.d.ts +4 -3
  525. package/dist/apps/shared/mail/permanentDelete.js +8 -8
  526. package/dist/apps/shared/mail/primaryMailbox.d.ts +1 -1
  527. package/dist/apps/shared/mail/primaryMailbox.js +1 -1
  528. package/dist/apps/shared/mail/reportNotices.d.ts +1 -1
  529. package/dist/apps/shared/mail/senderBlocking.d.ts +5 -4
  530. package/dist/apps/shared/mail/senderBlocking.js +19 -16
  531. package/dist/apps/shared/mail/useMailConnection.d.ts +1 -1
  532. package/dist/apps/shared/mail/useMailConnection.js +12 -10
  533. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +2 -2
  534. package/dist/apps/shared/mail/useMailLiveUpdates.js +2 -2
  535. package/dist/apps/shared/mail/useMailboxUpdateAccess.d.ts +3 -2
  536. package/dist/apps/shared/mail/useMailboxUpdateAccess.js +31 -10
  537. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +1 -1
  538. package/dist/apps/shared/mail/useMarkMessageRead.js +5 -3
  539. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +1 -1
  540. package/dist/apps/shared/mail/usePermanentDelete.d.ts +1 -1
  541. package/dist/apps/shared/mail/usePermanentDelete.js +4 -2
  542. package/dist/apps/shared/navigation/pageTitle.d.ts +1 -1
  543. package/dist/apps/shared/notifications/NotificationCenter.js +1 -1
  544. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +2 -2
  545. package/dist/apps/shared/notifications/apiErrors.js +2 -2
  546. package/dist/apps/shared/notifications/pushStatus.js +1 -1
  547. package/dist/apps/shared/notifications/systemErrors.js +1 -1
  548. package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -2
  549. package/dist/apps/shared/search/LocalIndexLifecycle.js +15 -10
  550. package/dist/apps/shared/search/crossMailboxSearch.d.ts +1 -1
  551. package/dist/apps/shared/search/crossMailboxSearch.js +2 -2
  552. package/dist/apps/shared/search/localIndexBuilder.d.ts +4 -3
  553. package/dist/apps/shared/search/localIndexBuilder.js +15 -12
  554. package/dist/apps/shared/search/localIndexKey.js +1 -1
  555. package/dist/apps/shared/search/localIndexRpcClient.d.ts +43 -31
  556. package/dist/apps/shared/search/localIndexRpcClient.js +113 -20
  557. package/dist/apps/shared/search/localIndexTransport.d.ts +77 -0
  558. package/dist/apps/shared/search/localIndexTransport.js +1 -0
  559. package/dist/apps/shared/search/localIndexWorker.d.ts +1 -1
  560. package/dist/apps/shared/search/searchTier2.d.ts +3 -3
  561. package/dist/apps/shared/signing/enrollmentTracker.d.ts +1 -1
  562. package/dist/apps/shared/signing/enrollmentTracker.js +3 -3
  563. package/dist/apps/shared/signing/enrollmentView.d.ts +1 -1
  564. package/dist/apps/shared/signing/signingInfo.d.ts +1 -1
  565. package/dist/apps/shared/signing/signingInfo.js +1 -1
  566. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +1 -1
  567. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +5 -3
  568. package/dist/apps/shared/styles/app.css +387 -396
  569. package/dist/apps/www/_layout.d.ts +1 -1
  570. package/dist/apps/www/_layout.js +1 -1
  571. package/dist/apps/www/calendar/index.d.ts +1 -1
  572. package/dist/apps/www/calendar/index.js +20 -18
  573. package/dist/apps/www/contacts/[uid].d.ts +1 -1
  574. package/dist/apps/www/contacts/[uid].js +8 -6
  575. package/dist/apps/www/contacts/index.d.ts +1 -1
  576. package/dist/apps/www/contacts/index.js +19 -17
  577. package/dist/apps/www/index.d.ts +1 -1
  578. package/dist/apps/www/index.js +54 -50
  579. package/dist/apps/www/messages/[uid].d.ts +1 -1
  580. package/dist/apps/www/messages/[uid].js +11 -9
  581. package/dist/apps/www/settings/appearance/index.d.ts +1 -1
  582. package/dist/apps/www/settings/auto-reply/index.d.ts +1 -1
  583. package/dist/apps/www/settings/auto-reply/index.js +6 -4
  584. package/dist/apps/www/settings/blocked-senders/index.d.ts +1 -1
  585. package/dist/apps/www/settings/blocked-senders/index.js +6 -4
  586. package/dist/apps/www/settings/encryption/index.d.ts +1 -1
  587. package/dist/apps/www/settings/encryption/index.js +31 -29
  588. package/dist/apps/www/settings/filters/[uid].d.ts +1 -1
  589. package/dist/apps/www/settings/filters/[uid].js +11 -9
  590. package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +2 -2
  591. package/dist/apps/www/settings/filters/index.d.ts +1 -1
  592. package/dist/apps/www/settings/filters/index.js +4 -4
  593. package/dist/apps/www/settings/filters/new/index.d.ts +1 -1
  594. package/dist/apps/www/settings/filters/new/index.js +10 -8
  595. package/dist/apps/www/settings/labels/index.d.ts +1 -1
  596. package/dist/apps/www/settings/labels/index.js +12 -10
  597. package/dist/apps/www/settings/privacy/index.d.ts +1 -1
  598. package/dist/apps/www/settings/privacy/index.js +18 -14
  599. package/dist/apps/www/settings/profile/index.d.ts +1 -1
  600. package/dist/apps/www/settings/profile/index.js +7 -5
  601. package/dist/apps/www/settings/read-receipts/index.d.ts +1 -1
  602. package/dist/apps/www/settings/read-receipts/index.js +5 -3
  603. package/dist/apps/www/settings/sharing/index.d.ts +1 -1
  604. package/dist/apps/www/settings/sharing/index.js +6 -6
  605. package/dist/apps/www/settings/signatures/[uid].d.ts +1 -1
  606. package/dist/apps/www/settings/signatures/[uid].js +5 -5
  607. package/dist/apps/www/settings/signatures/index.d.ts +1 -1
  608. package/dist/apps/www/settings/signatures/index.js +4 -4
  609. package/dist/apps/www/settings/signatures/new/index.d.ts +1 -1
  610. package/dist/apps/www/settings/signatures/new/index.js +4 -4
  611. package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +1 -1
  612. package/dist/apps/www/settings/signatures/signatureDefaults.js +1 -1
  613. package/dist/apps/www/tasks/index.d.ts +1 -1
  614. package/dist/apps/www/tasks/index.js +17 -15
  615. package/dist/lib/admin/auditLogApi.d.ts +32 -0
  616. package/dist/lib/admin/auditLogApi.js +27 -0
  617. package/dist/lib/admin/distributionListsApi.d.ts +47 -0
  618. package/dist/lib/admin/distributionListsApi.js +34 -0
  619. package/dist/lib/admin/domainsApi.d.ts +75 -0
  620. package/dist/lib/admin/domainsApi.js +44 -0
  621. package/dist/lib/admin/escrowAccessRequestsApi.d.ts +79 -0
  622. package/dist/lib/admin/escrowAccessRequestsApi.js +46 -0
  623. package/dist/lib/admin/escrowAuditLogApi.d.ts +66 -0
  624. package/dist/lib/admin/escrowAuditLogApi.js +31 -0
  625. package/dist/lib/admin/escrowScopesApi.d.ts +78 -0
  626. package/dist/lib/admin/escrowScopesApi.js +50 -0
  627. package/dist/lib/admin/leftoverMailboxApi.d.ts +73 -0
  628. package/dist/lib/admin/leftoverMailboxApi.js +67 -0
  629. package/dist/lib/admin/mailboxPolicyApi.d.ts +27 -0
  630. package/dist/lib/admin/mailboxPolicyApi.js +21 -0
  631. package/dist/lib/admin/matterExportApi.d.ts +40 -0
  632. package/dist/lib/admin/matterExportApi.js +39 -0
  633. package/dist/lib/admin/matterSearchApi.d.ts +20 -0
  634. package/dist/lib/admin/matterSearchApi.js +27 -0
  635. package/dist/lib/admin/mattersApi.d.ts +56 -0
  636. package/dist/lib/admin/mattersApi.js +40 -0
  637. package/dist/lib/admin/pluginsApi.d.ts +265 -0
  638. package/dist/lib/admin/pluginsApi.js +102 -0
  639. package/dist/lib/admin/retentionPolicyApi.d.ts +32 -0
  640. package/dist/lib/admin/retentionPolicyApi.js +29 -0
  641. package/dist/lib/admin/setupApi.d.ts +23 -0
  642. package/dist/lib/admin/setupApi.js +26 -0
  643. package/dist/lib/admin/transportRulesApi.d.ts +66 -0
  644. package/dist/lib/admin/transportRulesApi.js +43 -0
  645. package/dist/lib/appearance/preferencesApi.d.ts +128 -0
  646. package/dist/lib/appearance/preferencesApi.js +257 -0
  647. package/dist/lib/auth/profileApi.d.ts +33 -0
  648. package/dist/lib/auth/profileApi.js +52 -0
  649. package/dist/lib/auth/session.d.ts +70 -0
  650. package/dist/lib/auth/session.js +210 -0
  651. package/dist/lib/branding/brandingApi.d.ts +47 -0
  652. package/dist/lib/branding/brandingApi.js +61 -0
  653. package/dist/lib/branding/useBranding.d.ts +23 -0
  654. package/dist/lib/branding/useBranding.js +65 -0
  655. package/dist/lib/calendar/calendarApi.d.ts +230 -0
  656. package/dist/lib/calendar/calendarApi.js +106 -0
  657. package/dist/lib/calendar/calendarColors.d.ts +18 -0
  658. package/dist/lib/calendar/calendarColors.js +39 -0
  659. package/dist/lib/calendar/calendarDragIds.d.ts +42 -0
  660. package/dist/lib/calendar/calendarDragIds.js +92 -0
  661. package/dist/lib/calendar/calendarMutations.d.ts +68 -0
  662. package/dist/lib/calendar/calendarMutations.js +190 -0
  663. package/dist/lib/calendar/eventDescription.d.ts +43 -0
  664. package/dist/lib/calendar/eventDescription.js +195 -0
  665. package/dist/lib/calendar/freeBusyApi.d.ts +100 -0
  666. package/dist/lib/calendar/freeBusyApi.js +119 -0
  667. package/dist/lib/calendar/inviteApi.d.ts +121 -0
  668. package/dist/lib/calendar/inviteApi.js +54 -0
  669. package/dist/lib/calendar/recurrence.d.ts +60 -0
  670. package/dist/lib/calendar/recurrence.js +225 -0
  671. package/dist/lib/components/avatar/ContactAvatar.d.ts +8 -0
  672. package/dist/lib/components/avatar/ContactAvatar.js +26 -0
  673. package/dist/lib/components/buttons/Button.d.ts +9 -0
  674. package/dist/lib/components/buttons/Button.js +14 -0
  675. package/dist/lib/components/buttons/CopyButton.d.ts +18 -0
  676. package/dist/lib/components/buttons/CopyButton.js +14 -0
  677. package/dist/lib/components/feedback/Alert.d.ts +4 -0
  678. package/dist/lib/components/feedback/Alert.js +4 -0
  679. package/dist/lib/components/feedback/Skeleton.d.ts +21 -0
  680. package/dist/lib/components/feedback/Skeleton.js +14 -0
  681. package/dist/lib/components/forms/FormField.d.ts +8 -0
  682. package/dist/lib/components/forms/FormField.js +5 -0
  683. package/dist/lib/components/navigation/BottomTabBar.d.ts +27 -0
  684. package/dist/lib/components/navigation/BottomTabBar.js +32 -0
  685. package/dist/lib/components/overlays/Drawer.d.ts +20 -0
  686. package/dist/lib/components/overlays/Drawer.js +27 -0
  687. package/dist/lib/components/overlays/Modal.d.ts +17 -0
  688. package/dist/lib/components/overlays/Modal.js +25 -0
  689. package/dist/lib/components/overlays/PopoverPortal.d.ts +23 -0
  690. package/dist/lib/components/overlays/PopoverPortal.js +53 -0
  691. package/dist/lib/components/overlays/overlayStack.d.ts +31 -0
  692. package/dist/lib/components/overlays/overlayStack.js +143 -0
  693. package/dist/lib/components/pickers/MiniDatePicker.d.ts +13 -0
  694. package/dist/lib/components/pickers/MiniDatePicker.js +42 -0
  695. package/dist/lib/contacts/contactsApi.d.ts +174 -0
  696. package/dist/lib/contacts/contactsApi.js +139 -0
  697. package/dist/lib/contacts/vcard.d.ts +23 -0
  698. package/dist/lib/contacts/vcard.js +179 -0
  699. package/dist/lib/crypto/composeSecurity.d.ts +67 -0
  700. package/dist/lib/crypto/composeSecurity.js +93 -0
  701. package/dist/lib/crypto/encoding.d.ts +9 -0
  702. package/dist/lib/crypto/encoding.js +32 -0
  703. package/dist/lib/crypto/escrowKeys.d.ts +25 -0
  704. package/dist/lib/crypto/escrowKeys.js +59 -0
  705. package/dist/lib/crypto/idleTimeout.d.ts +16 -0
  706. package/dist/lib/crypto/idleTimeout.js +47 -0
  707. package/dist/lib/crypto/keySession.d.ts +159 -0
  708. package/dist/lib/crypto/keySession.js +367 -0
  709. package/dist/lib/crypto/keys.d.ts +50 -0
  710. package/dist/lib/crypto/keys.js +89 -0
  711. package/dist/lib/crypto/keyvaultApi.d.ts +394 -0
  712. package/dist/lib/crypto/keyvaultApi.js +350 -0
  713. package/dist/lib/crypto/masterKey.d.ts +59 -0
  714. package/dist/lib/crypto/masterKey.js +104 -0
  715. package/dist/lib/crypto/masterKeyWraps.d.ts +108 -0
  716. package/dist/lib/crypto/masterKeyWraps.js +228 -0
  717. package/dist/lib/crypto/messageSecurity.d.ts +248 -0
  718. package/dist/lib/crypto/messageSecurity.js +367 -0
  719. package/dist/lib/crypto/mime.d.ts +143 -0
  720. package/dist/lib/crypto/mime.js +504 -0
  721. package/dist/lib/crypto/passkeyUnlock.d.ts +20 -0
  722. package/dist/lib/crypto/passkeyUnlock.js +79 -0
  723. package/dist/lib/crypto/passwordUnlock.d.ts +39 -0
  724. package/dist/lib/crypto/passwordUnlock.js +73 -0
  725. package/dist/lib/crypto/recoveryCode.d.ts +28 -0
  726. package/dist/lib/crypto/recoveryCode.js +86 -0
  727. package/dist/lib/crypto/signingProviderApi.d.ts +95 -0
  728. package/dist/lib/crypto/signingProviderApi.js +60 -0
  729. package/dist/lib/crypto/smime.d.ts +128 -0
  730. package/dist/lib/crypto/smime.js +402 -0
  731. package/dist/lib/crypto/smimeMessage.d.ts +132 -0
  732. package/dist/lib/crypto/smimeMessage.js +341 -0
  733. package/dist/lib/crypto/useIdleKeyTimeout.d.ts +10 -0
  734. package/dist/lib/crypto/useIdleKeyTimeout.js +163 -0
  735. package/dist/lib/crypto/verificationSeal.d.ts +56 -0
  736. package/dist/lib/crypto/verificationSeal.js +220 -0
  737. package/dist/lib/mail/compose/composeQuoting.d.ts +88 -0
  738. package/dist/lib/mail/compose/composeQuoting.js +204 -0
  739. package/dist/lib/mail/compose/emojiData.d.ts +9 -0
  740. package/dist/lib/mail/compose/emojiData.js +18 -0
  741. package/dist/lib/mail/compose/giphyApi.d.ts +17 -0
  742. package/dist/lib/mail/compose/giphyApi.js +20 -0
  743. package/dist/lib/mail/conversationsApi.d.ts +80 -0
  744. package/dist/lib/mail/conversationsApi.js +44 -0
  745. package/dist/lib/mail/dataExportApi.d.ts +50 -0
  746. package/dist/lib/mail/dataExportApi.js +40 -0
  747. package/dist/lib/mail/directoryApi.d.ts +49 -0
  748. package/dist/lib/mail/directoryApi.js +101 -0
  749. package/dist/lib/mail/erasureRequestApi.d.ts +45 -0
  750. package/dist/lib/mail/erasureRequestApi.js +43 -0
  751. package/dist/lib/mail/flaggedMessages.d.ts +22 -0
  752. package/dist/lib/mail/flaggedMessages.js +73 -0
  753. package/dist/lib/mail/focusedInboxOverridesApi.d.ts +37 -0
  754. package/dist/lib/mail/focusedInboxOverridesApi.js +34 -0
  755. package/dist/lib/mail/labelsApi.d.ts +47 -0
  756. package/dist/lib/mail/labelsApi.js +38 -0
  757. package/dist/lib/mail/mailAddress.d.ts +27 -0
  758. package/dist/lib/mail/mailAddress.js +54 -0
  759. package/dist/lib/mail/mailApi.d.ts +850 -0
  760. package/dist/lib/mail/mailApi.js +602 -0
  761. package/dist/lib/mail/mailDetailHooks.d.ts +10 -0
  762. package/dist/lib/mail/mailDetailHooks.js +53 -0
  763. package/dist/lib/mail/mailFilterRulesApi.d.ts +76 -0
  764. package/dist/lib/mail/mailFilterRulesApi.js +45 -0
  765. package/dist/lib/mail/mailSignaturesApi.d.ts +49 -0
  766. package/dist/lib/mail/mailSignaturesApi.js +42 -0
  767. package/dist/lib/mail/mailboxAccessApi.d.ts +74 -0
  768. package/dist/lib/mail/mailboxAccessApi.js +56 -0
  769. package/dist/lib/mail/mailboxImportApi.d.ts +53 -0
  770. package/dist/lib/mail/mailboxImportApi.js +51 -0
  771. package/dist/lib/mail/messageBodySanitizer.d.ts +16 -0
  772. package/dist/lib/mail/messageBodySanitizer.js +118 -0
  773. package/dist/lib/mail/pushClient.d.ts +106 -0
  774. package/dist/lib/mail/pushClient.js +317 -0
  775. package/dist/lib/mail/sendEvents.d.ts +33 -0
  776. package/dist/lib/mail/sendEvents.js +44 -0
  777. package/dist/lib/mail/sendFailure.d.ts +16 -0
  778. package/dist/lib/mail/sendFailure.js +86 -0
  779. package/dist/lib/mail/senderListsApi.d.ts +69 -0
  780. package/dist/lib/mail/senderListsApi.js +126 -0
  781. package/dist/lib/search/queryGrammar.d.ts +51 -0
  782. package/dist/lib/search/queryGrammar.js +111 -0
  783. package/dist/lib/search/searchApi.d.ts +100 -0
  784. package/dist/lib/search/searchApi.js +107 -0
  785. package/dist/lib/search/searchScoring.d.ts +34 -0
  786. package/dist/lib/search/searchScoring.js +44 -0
  787. package/dist/lib/search/searchTier3.d.ts +64 -0
  788. package/dist/lib/search/searchTier3.js +335 -0
  789. package/dist/lib/tasks/tasksApi.d.ts +85 -0
  790. package/dist/lib/tasks/tasksApi.js +55 -0
  791. package/dist/lib/util/api.d.ts +160 -0
  792. package/dist/lib/util/api.js +251 -0
  793. package/dist/lib/util/apiClientContext.d.ts +7 -0
  794. package/dist/lib/util/apiClientContext.js +32 -0
  795. package/dist/lib/util/apiQuery.d.ts +32 -0
  796. package/dist/lib/util/apiQuery.js +42 -0
  797. package/dist/lib/util/clipboard.d.ts +7 -0
  798. package/dist/lib/util/clipboard.js +56 -0
  799. package/dist/lib/util/dateInput.d.ts +6 -0
  800. package/dist/lib/util/dateInput.js +14 -0
  801. package/dist/lib/util/timeZone.d.ts +12 -0
  802. package/dist/lib/util/timeZone.js +32 -0
  803. package/dist/lib/util/useCopyToClipboard.d.ts +12 -0
  804. package/dist/lib/util/useCopyToClipboard.js +34 -0
  805. package/dist/lib/util/useIsMobile.d.ts +10 -0
  806. package/dist/lib/util/useIsMobile.js +29 -0
  807. package/dist/lib/videoconf/videoMeetingsApi.d.ts +116 -0
  808. package/dist/lib/videoconf/videoMeetingsApi.js +49 -0
  809. package/lib/admin/auditLogApi.ts +48 -0
  810. package/lib/admin/distributionListsApi.ts +77 -0
  811. package/lib/admin/domainsApi.ts +113 -0
  812. package/lib/admin/escrowAccessRequestsApi.ts +110 -0
  813. package/lib/admin/escrowAuditLogApi.ts +101 -0
  814. package/lib/admin/escrowScopesApi.ts +112 -0
  815. package/lib/admin/leftoverMailboxApi.ts +120 -0
  816. package/lib/admin/mailboxPolicyApi.ts +38 -0
  817. package/lib/admin/matterExportApi.ts +62 -0
  818. package/lib/admin/matterSearchApi.ts +35 -0
  819. package/lib/admin/mattersApi.ts +86 -0
  820. package/lib/admin/pluginsApi.ts +339 -0
  821. package/lib/admin/retentionPolicyApi.ts +46 -0
  822. package/lib/admin/setupApi.ts +40 -0
  823. package/lib/admin/transportRulesApi.ts +106 -0
  824. package/lib/appearance/preferencesApi.ts +335 -0
  825. package/lib/auth/profileApi.ts +77 -0
  826. package/lib/auth/session.ts +238 -0
  827. package/lib/branding/brandingApi.ts +97 -0
  828. package/lib/branding/useBranding.ts +81 -0
  829. package/lib/calendar/calendarApi.ts +309 -0
  830. package/lib/calendar/calendarColors.ts +45 -0
  831. package/lib/calendar/calendarDragIds.ts +112 -0
  832. package/lib/calendar/calendarMutations.ts +236 -0
  833. package/lib/calendar/eventDescription.ts +211 -0
  834. package/lib/calendar/freeBusyApi.ts +197 -0
  835. package/lib/calendar/inviteApi.ts +154 -0
  836. package/lib/calendar/recurrence.ts +285 -0
  837. package/lib/components/avatar/ContactAvatar.tsx +47 -0
  838. package/lib/components/buttons/Button.tsx +40 -0
  839. package/lib/components/buttons/CopyButton.tsx +45 -0
  840. package/lib/components/feedback/Alert.tsx +13 -0
  841. package/lib/components/feedback/Skeleton.tsx +38 -0
  842. package/lib/components/forms/FormField.tsx +23 -0
  843. package/lib/components/navigation/BottomTabBar.tsx +66 -0
  844. package/lib/components/overlays/Drawer.tsx +72 -0
  845. package/lib/components/overlays/Modal.tsx +64 -0
  846. package/lib/components/overlays/PopoverPortal.tsx +74 -0
  847. package/lib/components/overlays/overlayStack.ts +165 -0
  848. package/lib/components/pickers/MiniDatePicker.tsx +90 -0
  849. package/lib/contacts/contactsApi.ts +289 -0
  850. package/lib/contacts/vcard.ts +197 -0
  851. package/lib/crypto/composeSecurity.ts +140 -0
  852. package/lib/crypto/encoding.ts +36 -0
  853. package/lib/crypto/escrowKeys.ts +74 -0
  854. package/lib/crypto/idleTimeout.ts +49 -0
  855. package/lib/crypto/keySession.ts +483 -0
  856. package/lib/crypto/keys.ts +105 -0
  857. package/lib/crypto/keyvaultApi.ts +674 -0
  858. package/lib/crypto/masterKey.ts +134 -0
  859. package/lib/crypto/masterKeyWraps.ts +269 -0
  860. package/lib/crypto/messageSecurity.ts +600 -0
  861. package/lib/crypto/mime.ts +568 -0
  862. package/lib/crypto/passkeyUnlock.ts +103 -0
  863. package/lib/crypto/passwordUnlock.ts +98 -0
  864. package/lib/crypto/recoveryCode.ts +93 -0
  865. package/lib/crypto/signingProviderApi.ts +135 -0
  866. package/lib/crypto/smime.ts +459 -0
  867. package/lib/crypto/smimeMessage.ts +491 -0
  868. package/lib/crypto/useIdleKeyTimeout.ts +176 -0
  869. package/lib/crypto/verificationSeal.ts +288 -0
  870. package/lib/mail/compose/composeQuoting.ts +243 -0
  871. package/lib/mail/compose/emojiData.ts +34 -0
  872. package/lib/mail/compose/giphyApi.ts +31 -0
  873. package/lib/mail/conversationsApi.ts +123 -0
  874. package/lib/mail/dataExportApi.ts +73 -0
  875. package/lib/mail/directoryApi.ts +161 -0
  876. package/lib/mail/erasureRequestApi.ts +71 -0
  877. package/lib/mail/flaggedMessages.ts +80 -0
  878. package/lib/mail/focusedInboxOverridesApi.ts +64 -0
  879. package/lib/mail/labelsApi.ts +71 -0
  880. package/lib/mail/mailAddress.ts +76 -0
  881. package/lib/mail/mailApi.ts +1286 -0
  882. package/lib/mail/mailDetailHooks.ts +57 -0
  883. package/lib/mail/mailFilterRulesApi.ts +116 -0
  884. package/lib/mail/mailSignaturesApi.ts +84 -0
  885. package/lib/mail/mailboxAccessApi.ts +106 -0
  886. package/lib/mail/mailboxImportApi.ts +87 -0
  887. package/lib/mail/messageBodySanitizer.ts +129 -0
  888. package/lib/mail/pushClient.ts +375 -0
  889. package/lib/mail/sendEvents.ts +72 -0
  890. package/lib/mail/sendFailure.ts +98 -0
  891. package/lib/mail/senderListsApi.ts +161 -0
  892. package/lib/search/queryGrammar.ts +179 -0
  893. package/lib/search/searchApi.ts +199 -0
  894. package/lib/search/searchScoring.ts +46 -0
  895. package/lib/search/searchTier3.ts +406 -0
  896. package/lib/tasks/tasksApi.ts +128 -0
  897. package/lib/util/api.ts +318 -0
  898. package/lib/util/apiClientContext.ts +35 -0
  899. package/lib/util/apiQuery.ts +61 -0
  900. package/lib/util/clipboard.ts +55 -0
  901. package/lib/util/dateInput.ts +15 -0
  902. package/lib/util/timeZone.ts +33 -0
  903. package/lib/util/useCopyToClipboard.ts +48 -0
  904. package/lib/util/useIsMobile.ts +34 -0
  905. package/lib/videoconf/videoMeetingsApi.ts +144 -0
  906. package/package.json +10 -4
@@ -1,654 +1,659 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import { pageTitle } from "../../shared/navigation/pageTitle.js";
6
- import React, { FormEvent, useEffect, useMemo, useRef, useState } from "react";
7
- import { endOfWeek, isAfter, isBefore, isToday, parseISO, startOfDay } from "date-fns";
8
- import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
9
- import {
10
- Task,
11
- TaskPriority,
12
- createTask,
13
- deleteTask,
14
- listTasks,
15
- setTaskCompleted,
16
- setTaskMyDay,
17
- updateTask,
18
- } from "@rapidmx/react-shared/tasks/tasksApi.js";
19
- import { listFlaggedMessages } from "@rapidmx/react-shared/mail/flaggedMessages.js";
20
- import { Message } from "@rapidmx/react-shared/mail/mailApi.js";
21
- import TasksShell, { TasksShellProps, useTasksShell } from "../../shared/components/tasks/layout/TasksShell.js";
22
- import TasksSidebar, { TasksView } from "../../shared/components/tasks/TasksSidebar.js";
23
- import TasksToolbar, { TasksViewMode } from "../../shared/components/tasks/TasksToolbar.js";
24
- import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
25
- import Button from "@rapidmx/react-shared/components/buttons/Button.js";
26
- import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
27
- import { findWellKnownFolderUid } from "../../shared/mail/findWellKnownFolderUid.js";
28
- import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
29
- import { LIST_PAGE_SIZE, MAX_LIST_PAGES, listAllPages } from "../../shared/mail/listAllPages.js";
30
- import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
31
- import { useShortcut } from "../../shared/keyboard/useShortcut.js";
32
- import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
33
- import { notifyApiError } from "../../shared/notifications/apiErrors.js";
34
-
35
- const INPUT_CLASS =
36
- "text-sm py-1.5 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
37
-
38
- function TasksPage(props: TasksShellProps) {
39
- return (
40
- <TasksShell {...props}>
41
- <TasksContent />
42
- </TasksShell>
43
- );
44
- }
45
-
46
- /**
47
- * Parses a task's `dueDate` - `parseISO()` rather than `new Date()`, since `new Date("2026-06-16")` reads a
48
- * date-only value as UTC midnight, which is the *previous* local day anywhere west of UTC. A full
49
- * timestamp parses identically either way.
50
- */
51
- function parseDueDate(value: string): Date {
52
- return parseISO(value);
53
- }
54
-
55
- type Bucket = "Overdue" | "Today" | "This Week" | "Later" | "No due date";
56
- const BUCKET_ORDER: Bucket[] = ["Overdue", "Today", "This Week", "Later", "No due date"];
57
-
58
- /** Buckets a task by its due date relative to `now`, matching the Outlook/To-Do convention this list mirrors. */
59
- function bucketFor(task: Task, now: Date): Bucket {
60
- if (!task.dueDate) {
61
- return "No due date";
62
- }
63
- const due = parseDueDate(task.dueDate);
64
- if (isBefore(due, startOfDay(now))) {
65
- return "Overdue";
66
- }
67
- if (isToday(due)) {
68
- return "Today";
69
- }
70
- if (!isAfter(due, endOfWeek(now, { weekStartsOn: 1 }))) {
71
- return "This Week";
72
- }
73
- return "Later";
74
- }
75
-
76
- const PRIORITY_LABEL: Record<TaskPriority, string> = { low: "Low", normal: "Normal", high: "High" };
77
- const PRIORITY_CLASS: Record<TaskPriority, string> = {
78
- low: "text-text-muted",
79
- normal: "text-text-muted",
80
- high: "text-danger",
81
- };
82
-
83
- function TasksContent() {
84
- const { folderUid, mailboxUid, userUid, mailboxes } = useTasksShell();
85
- // The quick-add form's Mailbox choices - view-only shares are left out (see writableMailboxes.ts).
86
- const writableMailboxes = useWritableMailboxes(mailboxes, userUid, mailboxUid);
87
- // The quick-add form's chosen mailbox - `undefined` means "the mailbox being viewed".
88
- const [targetMailboxUid, setTargetMailboxUid] = useState<string | undefined>(undefined);
89
- // Set after a task is added to a different mailbox than the one this list shows.
90
- const [addedElsewhere, setAddedElsewhere] = useState<{ mailboxUid: string; title: string } | null>(null);
91
- const [tasks, setTasks] = useState<Task[]>([]);
92
- const [loading, setLoading] = useState(true);
93
- const [error, setError] = useState<string | null>(null);
94
- // Set when `listAllPages()` stopped at its page cap - the list shown isn't every task.
95
- const [truncated, setTruncated] = useState(false);
96
- const [title, setTitle] = useState("");
97
- const [dueDate, setDueDate] = useState("");
98
- const [priority, setPriority] = useState<TaskPriority>("normal");
99
- const [createError, setCreateError] = useState<string | null>(null);
100
- const [creating, setCreating] = useState(false);
101
- const [view, setView] = useState<TasksView>({ type: "all" });
102
- const [viewMode, setViewMode] = useState<TasksViewMode>("list");
103
- const [checkedUids, setCheckedUids] = useState<Set<string>>(new Set());
104
- const [flaggedMessages, setFlaggedMessages] = useState<Message[]>([]);
105
- const [flaggedLoading, setFlaggedLoading] = useState(false);
106
- const [flaggedError, setFlaggedError] = useState<string | null>(null);
107
- // What the delete confirmation dialog is asking about - one task (a row's delete button), or every
108
- // checked task (the toolbar's Delete).
109
- const [pendingDelete, setPendingDelete] = useState<{ type: "single"; task: Task } | { type: "bulk" } | null>(null);
110
-
111
- // "New task" from the keyboard: a task is made in the quick-add form, so this moves the caret to its title field (which the Flagged view,
112
- // a list of flagged mail rather than tasks, doesn't have). A dialog open - the delete confirmation - silences it.
113
- const addTaskRef = useRef<HTMLInputElement | null>(null);
114
- const canAddTask = view.type !== "flagged";
115
- // Registered only while the form is rendered (`canAddTask`), so its field is there.
116
- useShortcut(SHORTCUTS.tasks.create, () => addTaskRef.current!.focus(), { enabled: canAddTask });
117
- const addTaskHint = useShortcutProps("Add a task", SHORTCUTS.tasks.create, canAddTask);
118
-
119
- function reload(): Promise<void> {
120
- if (!folderUid) {
121
- setTasks([]);
122
- setTruncated(false);
123
- setLoading(false);
124
- return Promise.resolve();
125
- }
126
- setLoading(true);
127
- setError(null);
128
- return listAllPages((page) => listTasks(folderUid, { limit: LIST_PAGE_SIZE, page }))
129
- .then((result) => {
130
- setTasks(result.items);
131
- setTruncated(result.truncated);
132
- })
133
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load tasks."))
134
- .finally(() => setLoading(false));
135
- }
136
-
137
- useEffect(() => {
138
- void reload();
139
- }, [folderUid]);
140
-
141
- useEffect(() => {
142
- setTargetMailboxUid(undefined);
143
- setAddedElsewhere(null);
144
- }, [mailboxUid]);
145
-
146
- useEffect(() => {
147
- if (view.type !== "flagged" || !mailboxUid) {
148
- return;
149
- }
150
- setFlaggedLoading(true);
151
- setFlaggedError(null);
152
- listFlaggedMessages(mailboxUid)
153
- .then(setFlaggedMessages)
154
- .catch((err) => setFlaggedError(err instanceof ApiRequestError ? err.message : "Could not load flagged email."))
155
- .finally(() => setFlaggedLoading(false));
156
- }, [view, mailboxUid]);
157
-
158
- async function handleCreate(e: FormEvent) {
159
- e.preventDefault();
160
- setCreateError(null);
161
- if (!title.trim()) {
162
- setCreateError("A title is required.");
163
- return;
164
- }
165
- if (!mailboxUid || !folderUid) {
166
- return;
167
- }
168
- const target = targetMailboxUid ?? mailboxUid;
169
- setCreating(true);
170
- try {
171
- const targetFolderUid = target === mailboxUid ? folderUid : await findWellKnownFolderUid(target, "tasks");
172
- if (!targetFolderUid) {
173
- setCreateError("That mailbox has no Tasks folder.");
174
- return;
175
- }
176
- const created = await createTask({
177
- mailboxUid: target,
178
- folderUid: targetFolderUid,
179
- title: title.trim(),
180
- // The date input's "yyyy-MM-dd" is the user's *local* day - see `parseDueDate()`.
181
- dueDate: dueDate ? parseDueDate(dueDate).toISOString() : undefined,
182
- priority,
183
- });
184
- if (target === mailboxUid) {
185
- setAddedElsewhere(null);
186
- setTasks((prev) => [...prev, created]);
187
- } else {
188
- // Created in another mailbox - it doesn't belong in this mailbox's list.
189
- setAddedElsewhere({ mailboxUid: target, title: created.title });
190
- }
191
- setTitle("");
192
- setDueDate("");
193
- setPriority("normal");
194
- } catch (err) {
195
- setCreateError(err instanceof ApiRequestError ? err.message : "Could not create this task.");
196
- } finally {
197
- setCreating(false);
198
- }
199
- }
200
-
201
- async function handleToggle(task: Task) {
202
- const nextCompleted = !task.completed;
203
- setTasks((prev) => prev.map((t) => (t.uid === task.uid ? { ...t, completed: nextCompleted } : t)));
204
- try {
205
- const updated = await setTaskCompleted(task, nextCompleted);
206
- setTasks((prev) => prev.map((t) => (t.uid === task.uid ? updated : t)));
207
- } catch (err) {
208
- setTasks((prev) => prev.map((t) => (t.uid === task.uid ? task : t)));
209
- notifyApiError(err, "Couldn't update the task");
210
- }
211
- }
212
-
213
- async function handleDelete(task: Task) {
214
- try {
215
- await deleteTask(task.uid, task.version);
216
- setTasks((prev) => prev.filter((t) => t.uid !== task.uid));
217
- } catch (err) {
218
- notifyApiError(err, "Couldn't delete the task");
219
- }
220
- }
221
-
222
- function handleSelectView(next: TasksView) {
223
- setView(next);
224
- setCheckedUids(new Set());
225
- }
226
-
227
- function toggleChecked(uid: string) {
228
- setCheckedUids((prev) => {
229
- const next = new Set(prev);
230
- if (next.has(uid)) {
231
- next.delete(uid);
232
- } else {
233
- next.add(uid);
234
- }
235
- return next;
236
- });
237
- }
238
-
239
- const viewFiltered = useMemo(() => {
240
- switch (view.type) {
241
- case "myDay":
242
- return tasks.filter((t) => t.myDay);
243
- case "important":
244
- return tasks.filter((t) => t.priority === "high");
245
- case "planned":
246
- return tasks.filter((t) => t.dueDate != null);
247
- case "assignedToMe":
248
- return tasks.filter((t) => t.assignedTo === userUid);
249
- case "list":
250
- return tasks.filter((t) => t.taskListUid === view.uid);
251
- case "flagged":
252
- return [];
253
- case "all":
254
- return tasks;
255
- }
256
- }, [tasks, view, userUid]);
257
-
258
- const checkedTasks = viewFiltered.filter((t) => checkedUids.has(t.uid));
259
-
260
- // The bulk handlers try every task and report each failure: the same failure repeated is one pop-up with a count.
261
- async function handleBulkComplete() {
262
- for (const task of checkedTasks) {
263
- try {
264
- await setTaskCompleted(task, true);
265
- } catch (err) {
266
- notifyApiError(err, "Couldn't update some of the tasks");
267
- }
268
- }
269
- setCheckedUids(new Set());
270
- await reload();
271
- }
272
-
273
- async function handleBulkAddToMyDay() {
274
- for (const task of checkedTasks) {
275
- try {
276
- await setTaskMyDay(task, true);
277
- } catch (err) {
278
- notifyApiError(err, "Couldn't update some of the tasks");
279
- }
280
- }
281
- setCheckedUids(new Set());
282
- await reload();
283
- }
284
-
285
- async function handleBulkDelete() {
286
- for (const task of checkedTasks) {
287
- try {
288
- await deleteTask(task.uid, task.version);
289
- } catch (err) {
290
- notifyApiError(err, "Couldn't delete some of the tasks");
291
- }
292
- }
293
- setCheckedUids(new Set());
294
- await reload();
295
- }
296
-
297
- function requestDelete(task: Task) {
298
- setPendingDelete({ type: "single", task });
299
- }
300
-
301
- function handleConfirmDelete() {
302
- const pending = pendingDelete!;
303
- setPendingDelete(null);
304
- if (pending.type === "single") {
305
- void handleDelete(pending.task);
306
- } else {
307
- void handleBulkDelete();
308
- }
309
- }
310
-
311
- const grouped = useMemo(() => {
312
- const now = new Date();
313
- const map = new Map<Bucket, Task[]>(BUCKET_ORDER.map((b) => [b, []]));
314
- for (const task of viewFiltered) {
315
- if (task.completed) {
316
- continue;
317
- }
318
- map.get(bucketFor(task, now))!.push(task);
319
- }
320
- return map;
321
- }, [viewFiltered]);
322
-
323
- const completedTasks = viewFiltered.filter((t) => t.completed);
324
-
325
- return (
326
- // Below `lg` the tasks menu is a drawer (its button sits above the list), so the list keeps the whole width.
327
- <div className="flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row">
328
- <TasksSidebar mailboxUid={mailboxUid} tasks={tasks} userUid={userUid} active={view} onSelect={handleSelectView} />
329
- <div className="flex-1 min-w-0 flex flex-col overflow-y-auto">
330
- <TasksToolbar
331
- viewMode={viewMode}
332
- onViewModeChange={setViewMode}
333
- selectedCount={checkedTasks.length}
334
- onComplete={handleBulkComplete}
335
- onDelete={() => setPendingDelete({ type: "bulk" })}
336
- onAddToMyDay={handleBulkAddToMyDay}
337
- />
338
- <div role="region" aria-label="Tasks list" className="max-w-2xl mx-auto w-full flex flex-col gap-6 p-6">
339
- <h1 className="text-xl font-bold uppercase tracking-wide">Tasks</h1>
340
-
341
- {error && <Alert>{error}</Alert>}
342
- {truncated && view.type !== "flagged" && (
343
- <Alert>
344
- This folder has more tasks than can be shown at once - only the first {LIST_PAGE_SIZE * MAX_LIST_PAGES} are listed.
345
- </Alert>
346
- )}
347
-
348
- {view.type === "flagged" ? (
349
- <FlaggedEmailList loading={flaggedLoading} error={flaggedError} messages={flaggedMessages} />
350
- ) : (
351
- <>
352
- <form onSubmit={handleCreate} className="flex flex-wrap items-center gap-2 bg-surface border border-border rounded-md p-2">
353
- <input
354
- ref={addTaskRef}
355
- type="text"
356
- aria-label="Add a task"
357
- {...addTaskHint}
358
- className={`${INPUT_CLASS} min-w-40 basis-40 flex-1`}
359
- value={title}
360
- onChange={(e) => setTitle(e.target.value)}
361
- placeholder="Add a task"
362
- />
363
- <input
364
- type="date"
365
- aria-label="Due date"
366
- className={INPUT_CLASS}
367
- value={dueDate}
368
- onChange={(e) => setDueDate(e.target.value)}
369
- />
370
- <select
371
- aria-label="Priority"
372
- className={INPUT_CLASS}
373
- value={priority}
374
- onChange={(e) => setPriority(e.target.value as TaskPriority)}
375
- >
376
- <option value="low">Low</option>
377
- <option value="normal">Normal</option>
378
- <option value="high">High</option>
379
- </select>
380
- {writableMailboxes.length > 1 && (
381
- <select
382
- aria-label="Task mailbox"
383
- className={`${INPUT_CLASS} max-w-40`}
384
- // `writableMailboxes` only has entries once `mailboxUid` has resolved.
385
- value={targetMailboxUid ?? mailboxUid}
386
- onChange={(e) => setTargetMailboxUid(e.target.value)}
387
- >
388
- {writableMailboxes.map((mb) => (
389
- <option key={mb.uid} value={mb.uid}>
390
- {mb.displayName}
391
- {mb.ownerUserUid === userUid ? "" : " (shared)"}
392
- </option>
393
- ))}
394
- </select>
395
- )}
396
- <Button type="submit" loading={creating} disabled={creating} className="!w-auto shrink-0">
397
- Add
398
- </Button>
399
- </form>
400
- {createError && <Alert>{createError}</Alert>}
401
- {addedElsewhere && (
402
- <p role="status" className="text-sm py-2 px-3 rounded-sm bg-surface-alt text-text">
403
- &ldquo;{addedElsewhere.title}&rdquo; was added to{" "}
404
- {mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox"}.{" "}
405
- <a
406
- href={`/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`}
407
- data-router-shallow=""
408
- data-router-prefetch="false"
409
- className="font-medium text-primary-dark hover:underline"
410
- >
411
- View that mailbox&rsquo;s tasks
412
- </a>
413
- </p>
414
- )}
415
-
416
- {loading ? (
417
- <p className="text-sm text-text-muted">Loading&hellip;</p>
418
- ) : viewFiltered.length === 0 ? (
419
- <p className="text-sm text-text-muted">No tasks yet.</p>
420
- ) : viewMode === "grid" ? (
421
- <TaskTable
422
- tasks={viewFiltered}
423
- checkedUids={checkedUids}
424
- onToggleChecked={toggleChecked}
425
- onToggle={handleToggle}
426
- onDelete={requestDelete}
427
- />
428
- ) : (
429
- <>
430
- {BUCKET_ORDER.map((bucket) => {
431
- const items = grouped.get(bucket)!;
432
- if (items.length === 0) {
433
- return null;
434
- }
435
- return (
436
- <TaskGroup
437
- key={bucket}
438
- label={bucket}
439
- tasks={items}
440
- checkedUids={checkedUids}
441
- onToggleChecked={toggleChecked}
442
- onToggle={handleToggle}
443
- onDelete={requestDelete}
444
- />
445
- );
446
- })}
447
- {completedTasks.length > 0 && (
448
- <TaskGroup
449
- label="Completed"
450
- tasks={completedTasks}
451
- checkedUids={checkedUids}
452
- onToggleChecked={toggleChecked}
453
- onToggle={handleToggle}
454
- onDelete={requestDelete}
455
- />
456
- )}
457
- </>
458
- )}
459
- </>
460
- )}
461
- </div>
462
- </div>
463
- <Modal open={pendingDelete !== null} onClose={() => setPendingDelete(null)} title="Delete tasks">
464
- <p className="text-sm mb-5">
465
- {pendingDelete?.type === "single"
466
- ? `Delete "${pendingDelete.task.title}"?`
467
- : `Delete ${checkedTasks.length} selected ${checkedTasks.length === 1 ? "task" : "tasks"}?`}
468
- </p>
469
- <div className="flex gap-3 justify-end">
470
- <Button type="button" variant="secondary" className="!w-auto" onClick={() => setPendingDelete(null)}>
471
- Cancel
472
- </Button>
473
- <Button type="button" className="!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger" onClick={handleConfirmDelete}>
474
- Delete
475
- </Button>
476
- </div>
477
- </Modal>
478
- </div>
479
- );
480
- }
481
-
482
- function FlaggedEmailList({ loading, error, messages }: { loading: boolean; error: string | null; messages: Message[] }) {
483
- if (error) {
484
- return <Alert>{error}</Alert>;
485
- }
486
- if (loading) {
487
- return <p className="text-sm text-text-muted">Loading&hellip;</p>;
488
- }
489
- if (messages.length === 0) {
490
- return <p className="text-sm text-text-muted">No flagged email.</p>;
491
- }
492
- return (
493
- <ul className="bg-surface border border-border rounded-md divide-y divide-border">
494
- {messages.map((m) => (
495
- <li key={m.uid} className="flex items-center gap-3 py-2.5 px-3 text-sm">
496
- <span className="flex-1 truncate">{m.subject || "(no subject)"}</span>
497
- <span className="text-xs text-text-muted shrink-0">{m.from.address}</span>
498
- <span className="text-xs text-text-muted shrink-0">{new Date(m.receivedDate).toLocaleDateString()}</span>
499
- </li>
500
- ))}
501
- </ul>
502
- );
503
- }
504
-
505
- /** A circular completion-toggle control matching Outlook's visual language (not a native checkbox), while
506
- * staying a real accessible toggle. */
507
- function CompletionToggle({ task, onToggle }: { task: Task; onToggle: (task: Task) => void }) {
508
- return (
509
- <button
510
- type="button"
511
- role="checkbox"
512
- aria-checked={task.completed}
513
- aria-label={`Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`}
514
- onClick={() => onToggle(task)}
515
- className={[
516
- "w-4 h-4 rounded-full border shrink-0 flex items-center justify-center text-[10px]",
517
- task.completed ? "bg-primary border-primary text-white" : "border-border text-transparent",
518
- ].join(" ")}
519
- >
520
- &#10003;
521
- </button>
522
- );
523
- }
524
-
525
- interface TaskGroupProps {
526
- label: string;
527
- tasks: Task[];
528
- checkedUids: Set<string>;
529
- onToggleChecked: (uid: string) => void;
530
- onToggle: (task: Task) => void;
531
- onDelete: (task: Task) => void;
532
- }
533
-
534
- function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDelete }: TaskGroupProps) {
535
- return (
536
- <div>
537
- <h2 className="text-xs font-bold uppercase tracking-wide text-text-muted mb-2">{label}</h2>
538
- <ul className="bg-surface border border-border rounded-md divide-y divide-border">
539
- {tasks.map((task) => (
540
- <li key={task.uid} className="flex items-center gap-3 py-2.5 px-3">
541
- <input
542
- type="checkbox"
543
- aria-label={`Select ${task.title}`}
544
- checked={checkedUids.has(task.uid)}
545
- onChange={() => onToggleChecked(task.uid)}
546
- />
547
- <CompletionToggle task={task} onToggle={onToggle} />
548
- <span className={["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" ")}>
549
- {task.title}
550
- </span>
551
- {task.priority !== "normal" && (
552
- <span className={["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" ")}>
553
- {PRIORITY_LABEL[task.priority]}
554
- </span>
555
- )}
556
- {task.dueDate && (
557
- <span className="text-xs text-text-muted shrink-0">{parseDueDate(task.dueDate).toLocaleDateString()}</span>
558
- )}
559
- <button
560
- type="button"
561
- onClick={() => onDelete(task)}
562
- aria-label={`Delete "${task.title}"`}
563
- className="text-text-muted hover:text-danger text-sm px-1"
564
- >
565
- &times;
566
- </button>
567
- </li>
568
- ))}
569
- </ul>
570
- </div>
571
- );
572
- }
573
-
574
- interface TaskTableProps {
575
- tasks: Task[];
576
- checkedUids: Set<string>;
577
- onToggleChecked: (uid: string) => void;
578
- onToggle: (task: Task) => void;
579
- onDelete: (task: Task) => void;
580
- }
581
-
582
- function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }: TaskTableProps) {
583
- const allChecked = tasks.length > 0 && tasks.every((t) => checkedUids.has(t.uid));
584
-
585
- function toggleAll() {
586
- if (allChecked) {
587
- // Every task is already checked when this branch runs (that's what `allChecked` means) — no
588
- // per-task guard needed, unlike the `else` branch below where only *some* tasks may be checked.
589
- for (const t of tasks) {
590
- onToggleChecked(t.uid);
591
- }
592
- } else {
593
- for (const t of tasks) {
594
- if (!checkedUids.has(t.uid)) {
595
- onToggleChecked(t.uid);
596
- }
597
- }
598
- }
599
- }
600
-
601
- return (
602
- <div className="overflow-x-auto">
603
- <table className="w-full text-sm bg-surface border border-border rounded-md">
604
- <thead>
605
- <tr className="border-b border-border text-left">
606
- <th className="w-8 px-3 py-2">
607
- <input type="checkbox" aria-label="Select all tasks" checked={allChecked} onChange={toggleAll} />
608
- </th>
609
- <th className="w-8 px-3 py-2" />
610
- <th className="px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted">Title</th>
611
- <th className="px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted">Due Date</th>
612
- <th className="px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted">Importance</th>
613
- <th className="w-8 px-3 py-2" />
614
- </tr>
615
- </thead>
616
- <tbody>
617
- {tasks.map((task) => (
618
- <tr key={task.uid} className="border-b border-border last:border-0">
619
- <td className="px-3 py-2">
620
- <input
621
- type="checkbox"
622
- aria-label={`Select ${task.title}`}
623
- checked={checkedUids.has(task.uid)}
624
- onChange={() => onToggleChecked(task.uid)}
625
- />
626
- </td>
627
- <td className="px-3 py-2">
628
- <CompletionToggle task={task} onToggle={onToggle} />
629
- </td>
630
- <td className={["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" ")}>{task.title}</td>
631
- <td className="px-3 py-2 text-text-muted">{task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : ""}</td>
632
- <td className={["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" ")}>{PRIORITY_LABEL[task.priority]}</td>
633
- <td className="px-3 py-2">
634
- <button
635
- type="button"
636
- onClick={() => onDelete(task)}
637
- aria-label={`Delete "${task.title}"`}
638
- className="text-text-muted hover:text-danger text-sm px-1"
639
- >
640
- &times;
641
- </button>
642
- </td>
643
- </tr>
644
- ))}
645
- </tbody>
646
- </table>
647
- </div>
648
- );
649
- }
650
-
651
- export default TasksPage;
652
-
653
- /** The tab's title: `Brand: Tasks` (see `pageTitle()`). */
654
- export const title = pageTitle("Tasks");
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { pageTitle } from "../../shared/navigation/pageTitle.js";
6
+ import React, { FormEvent, useEffect, useMemo, useRef, useState } from "react";
7
+ import { endOfWeek, isAfter, isBefore, isToday, parseISO, startOfDay } from "date-fns";
8
+ import { ApiRequestError } from "../../../lib/util/api.js";
9
+ import {
10
+ Task,
11
+ TaskPriority,
12
+ createTask,
13
+ deleteTask,
14
+ listTasks,
15
+ setTaskCompleted,
16
+ setTaskMyDay,
17
+ updateTask,
18
+ } from "../../../lib/tasks/tasksApi.js";
19
+ import { listFlaggedMessages } from "../../../lib/mail/flaggedMessages.js";
20
+ import { Message } from "../../../lib/mail/mailApi.js";
21
+ import { useApiClient } from "../../../lib/util/apiClientContext.js";
22
+ import TasksShell, { TasksShellProps, useTasksShell } from "../../shared/components/tasks/layout/TasksShell.js";
23
+ import TasksSidebar, { TasksView } from "../../shared/components/tasks/TasksSidebar.js";
24
+ import TasksToolbar, { TasksViewMode } from "../../shared/components/tasks/TasksToolbar.js";
25
+ import Alert from "../../../lib/components/feedback/Alert.js";
26
+ import Button from "../../../lib/components/buttons/Button.js";
27
+ import Modal from "../../../lib/components/overlays/Modal.js";
28
+ import { findWellKnownFolderUid } from "../../shared/mail/findWellKnownFolderUid.js";
29
+ import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
30
+ import { LIST_PAGE_SIZE, MAX_LIST_PAGES, listAllPages } from "../../shared/mail/listAllPages.js";
31
+ import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
32
+ import { useShortcut } from "../../shared/keyboard/useShortcut.js";
33
+ import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
34
+ import { notifyApiError } from "../../shared/notifications/apiErrors.js";
35
+
36
+ const INPUT_CLASS =
37
+ "text-sm py-1.5 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
38
+
39
+ function TasksPage(props: TasksShellProps) {
40
+ return (
41
+ <TasksShell {...props}>
42
+ <TasksContent />
43
+ </TasksShell>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Parses a task's `dueDate` - `parseISO()` rather than `new Date()`, since `new Date("2026-06-16")` reads a
49
+ * date-only value as UTC midnight, which is the *previous* local day anywhere west of UTC. A full
50
+ * timestamp parses identically either way.
51
+ */
52
+ function parseDueDate(value: string): Date {
53
+ return parseISO(value);
54
+ }
55
+
56
+ type Bucket = "Overdue" | "Today" | "This Week" | "Later" | "No due date";
57
+ const BUCKET_ORDER: Bucket[] = ["Overdue", "Today", "This Week", "Later", "No due date"];
58
+
59
+ /** Buckets a task by its due date relative to `now`, matching the Outlook/To-Do convention this list mirrors. */
60
+ function bucketFor(task: Task, now: Date): Bucket {
61
+ if (!task.dueDate) {
62
+ return "No due date";
63
+ }
64
+ const due = parseDueDate(task.dueDate);
65
+ if (isBefore(due, startOfDay(now))) {
66
+ return "Overdue";
67
+ }
68
+ if (isToday(due)) {
69
+ return "Today";
70
+ }
71
+ if (!isAfter(due, endOfWeek(now, { weekStartsOn: 1 }))) {
72
+ return "This Week";
73
+ }
74
+ return "Later";
75
+ }
76
+
77
+ const PRIORITY_LABEL: Record<TaskPriority, string> = { low: "Low", normal: "Normal", high: "High" };
78
+ const PRIORITY_CLASS: Record<TaskPriority, string> = {
79
+ low: "text-text-muted",
80
+ normal: "text-text-muted",
81
+ high: "text-danger",
82
+ };
83
+
84
+ function TasksContent() {
85
+ const { folderUid, mailboxUid, userUid, mailboxes } = useTasksShell();
86
+ const client = useApiClient();
87
+ // The quick-add form's Mailbox choices - view-only shares are left out (see writableMailboxes.ts).
88
+ const writableMailboxes = useWritableMailboxes(mailboxes, userUid, mailboxUid);
89
+ // The quick-add form's chosen mailbox - `undefined` means "the mailbox being viewed".
90
+ const [targetMailboxUid, setTargetMailboxUid] = useState<string | undefined>(undefined);
91
+ // Set after a task is added to a different mailbox than the one this list shows.
92
+ const [addedElsewhere, setAddedElsewhere] = useState<{ mailboxUid: string; title: string } | null>(null);
93
+ const [tasks, setTasks] = useState<Task[]>([]);
94
+ const [loading, setLoading] = useState(true);
95
+ const [error, setError] = useState<string | null>(null);
96
+ // Set when `listAllPages()` stopped at its page cap - the list shown isn't every task.
97
+ const [truncated, setTruncated] = useState(false);
98
+ const [title, setTitle] = useState("");
99
+ const [dueDate, setDueDate] = useState("");
100
+ const [priority, setPriority] = useState<TaskPriority>("normal");
101
+ const [createError, setCreateError] = useState<string | null>(null);
102
+ const [creating, setCreating] = useState(false);
103
+ const [view, setView] = useState<TasksView>({ type: "all" });
104
+ const [viewMode, setViewMode] = useState<TasksViewMode>("list");
105
+ const [checkedUids, setCheckedUids] = useState<Set<string>>(new Set());
106
+ const [flaggedMessages, setFlaggedMessages] = useState<Message[]>([]);
107
+ const [flaggedLoading, setFlaggedLoading] = useState(false);
108
+ const [flaggedError, setFlaggedError] = useState<string | null>(null);
109
+ // What the delete confirmation dialog is asking about - one task (a row's delete button), or every
110
+ // checked task (the toolbar's Delete).
111
+ const [pendingDelete, setPendingDelete] = useState<{ type: "single"; task: Task } | { type: "bulk" } | null>(null);
112
+
113
+ // "New task" from the keyboard: a task is made in the quick-add form, so this moves the caret to its title field (which the Flagged view,
114
+ // a list of flagged mail rather than tasks, doesn't have). A dialog open - the delete confirmation - silences it.
115
+ const addTaskRef = useRef<HTMLInputElement | null>(null);
116
+ const canAddTask = view.type !== "flagged";
117
+ // Registered only while the form is rendered (`canAddTask`), so its field is there.
118
+ useShortcut(SHORTCUTS.tasks.create, () => addTaskRef.current!.focus(), { enabled: canAddTask });
119
+ const addTaskHint = useShortcutProps("Add a task", SHORTCUTS.tasks.create, canAddTask);
120
+
121
+ function reload(): Promise<void> {
122
+ if (!folderUid) {
123
+ setTasks([]);
124
+ setTruncated(false);
125
+ setLoading(false);
126
+ return Promise.resolve();
127
+ }
128
+ setLoading(true);
129
+ setError(null);
130
+ return listAllPages((page) => listTasks(folderUid, { limit: LIST_PAGE_SIZE, page }, client))
131
+ .then((result) => {
132
+ setTasks(result.items);
133
+ setTruncated(result.truncated);
134
+ })
135
+ .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load tasks."))
136
+ .finally(() => setLoading(false));
137
+ }
138
+
139
+ useEffect(() => {
140
+ void reload();
141
+ }, [folderUid]);
142
+
143
+ useEffect(() => {
144
+ setTargetMailboxUid(undefined);
145
+ setAddedElsewhere(null);
146
+ }, [mailboxUid]);
147
+
148
+ useEffect(() => {
149
+ if (view.type !== "flagged" || !mailboxUid) {
150
+ return;
151
+ }
152
+ setFlaggedLoading(true);
153
+ setFlaggedError(null);
154
+ listFlaggedMessages(mailboxUid, client)
155
+ .then(setFlaggedMessages)
156
+ .catch((err) => setFlaggedError(err instanceof ApiRequestError ? err.message : "Could not load flagged email."))
157
+ .finally(() => setFlaggedLoading(false));
158
+ }, [view, mailboxUid]);
159
+
160
+ async function handleCreate(e: FormEvent) {
161
+ e.preventDefault();
162
+ setCreateError(null);
163
+ if (!title.trim()) {
164
+ setCreateError("A title is required.");
165
+ return;
166
+ }
167
+ if (!mailboxUid || !folderUid) {
168
+ return;
169
+ }
170
+ const target = targetMailboxUid ?? mailboxUid;
171
+ setCreating(true);
172
+ try {
173
+ const targetFolderUid = target === mailboxUid ? folderUid : await findWellKnownFolderUid(target, "tasks", client);
174
+ if (!targetFolderUid) {
175
+ setCreateError("That mailbox has no Tasks folder.");
176
+ return;
177
+ }
178
+ const created = await createTask(
179
+ {
180
+ mailboxUid: target,
181
+ folderUid: targetFolderUid,
182
+ title: title.trim(),
183
+ // The date input's "yyyy-MM-dd" is the user's *local* day - see `parseDueDate()`.
184
+ dueDate: dueDate ? parseDueDate(dueDate).toISOString() : undefined,
185
+ priority,
186
+ },
187
+ client,
188
+ );
189
+ if (target === mailboxUid) {
190
+ setAddedElsewhere(null);
191
+ setTasks((prev) => [...prev, created]);
192
+ } else {
193
+ // Created in another mailbox - it doesn't belong in this mailbox's list.
194
+ setAddedElsewhere({ mailboxUid: target, title: created.title });
195
+ }
196
+ setTitle("");
197
+ setDueDate("");
198
+ setPriority("normal");
199
+ } catch (err) {
200
+ setCreateError(err instanceof ApiRequestError ? err.message : "Could not create this task.");
201
+ } finally {
202
+ setCreating(false);
203
+ }
204
+ }
205
+
206
+ async function handleToggle(task: Task) {
207
+ const nextCompleted = !task.completed;
208
+ setTasks((prev) => prev.map((t) => (t.uid === task.uid ? { ...t, completed: nextCompleted } : t)));
209
+ try {
210
+ const updated = await setTaskCompleted(task, nextCompleted, client);
211
+ setTasks((prev) => prev.map((t) => (t.uid === task.uid ? updated : t)));
212
+ } catch (err) {
213
+ setTasks((prev) => prev.map((t) => (t.uid === task.uid ? task : t)));
214
+ notifyApiError(err, "Couldn't update the task");
215
+ }
216
+ }
217
+
218
+ async function handleDelete(task: Task) {
219
+ try {
220
+ await deleteTask(task.uid, task.version, client);
221
+ setTasks((prev) => prev.filter((t) => t.uid !== task.uid));
222
+ } catch (err) {
223
+ notifyApiError(err, "Couldn't delete the task");
224
+ }
225
+ }
226
+
227
+ function handleSelectView(next: TasksView) {
228
+ setView(next);
229
+ setCheckedUids(new Set());
230
+ }
231
+
232
+ function toggleChecked(uid: string) {
233
+ setCheckedUids((prev) => {
234
+ const next = new Set(prev);
235
+ if (next.has(uid)) {
236
+ next.delete(uid);
237
+ } else {
238
+ next.add(uid);
239
+ }
240
+ return next;
241
+ });
242
+ }
243
+
244
+ const viewFiltered = useMemo(() => {
245
+ switch (view.type) {
246
+ case "myDay":
247
+ return tasks.filter((t) => t.myDay);
248
+ case "important":
249
+ return tasks.filter((t) => t.priority === "high");
250
+ case "planned":
251
+ return tasks.filter((t) => t.dueDate != null);
252
+ case "assignedToMe":
253
+ return tasks.filter((t) => t.assignedTo === userUid);
254
+ case "list":
255
+ return tasks.filter((t) => t.taskListUid === view.uid);
256
+ case "flagged":
257
+ return [];
258
+ case "all":
259
+ return tasks;
260
+ }
261
+ }, [tasks, view, userUid]);
262
+
263
+ const checkedTasks = viewFiltered.filter((t) => checkedUids.has(t.uid));
264
+
265
+ // The bulk handlers try every task and report each failure: the same failure repeated is one pop-up with a count.
266
+ async function handleBulkComplete() {
267
+ for (const task of checkedTasks) {
268
+ try {
269
+ await setTaskCompleted(task, true, client);
270
+ } catch (err) {
271
+ notifyApiError(err, "Couldn't update some of the tasks");
272
+ }
273
+ }
274
+ setCheckedUids(new Set());
275
+ await reload();
276
+ }
277
+
278
+ async function handleBulkAddToMyDay() {
279
+ for (const task of checkedTasks) {
280
+ try {
281
+ await setTaskMyDay(task, true, client);
282
+ } catch (err) {
283
+ notifyApiError(err, "Couldn't update some of the tasks");
284
+ }
285
+ }
286
+ setCheckedUids(new Set());
287
+ await reload();
288
+ }
289
+
290
+ async function handleBulkDelete() {
291
+ for (const task of checkedTasks) {
292
+ try {
293
+ await deleteTask(task.uid, task.version, client);
294
+ } catch (err) {
295
+ notifyApiError(err, "Couldn't delete some of the tasks");
296
+ }
297
+ }
298
+ setCheckedUids(new Set());
299
+ await reload();
300
+ }
301
+
302
+ function requestDelete(task: Task) {
303
+ setPendingDelete({ type: "single", task });
304
+ }
305
+
306
+ function handleConfirmDelete() {
307
+ const pending = pendingDelete!;
308
+ setPendingDelete(null);
309
+ if (pending.type === "single") {
310
+ void handleDelete(pending.task);
311
+ } else {
312
+ void handleBulkDelete();
313
+ }
314
+ }
315
+
316
+ const grouped = useMemo(() => {
317
+ const now = new Date();
318
+ const map = new Map<Bucket, Task[]>(BUCKET_ORDER.map((b) => [b, []]));
319
+ for (const task of viewFiltered) {
320
+ if (task.completed) {
321
+ continue;
322
+ }
323
+ map.get(bucketFor(task, now))!.push(task);
324
+ }
325
+ return map;
326
+ }, [viewFiltered]);
327
+
328
+ const completedTasks = viewFiltered.filter((t) => t.completed);
329
+
330
+ return (
331
+ // Below `lg` the tasks menu is a drawer (its button sits above the list), so the list keeps the whole width.
332
+ <div className="flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row">
333
+ <TasksSidebar mailboxUid={mailboxUid} tasks={tasks} userUid={userUid} active={view} onSelect={handleSelectView} />
334
+ <div className="flex-1 min-w-0 flex flex-col overflow-y-auto">
335
+ <TasksToolbar
336
+ viewMode={viewMode}
337
+ onViewModeChange={setViewMode}
338
+ selectedCount={checkedTasks.length}
339
+ onComplete={handleBulkComplete}
340
+ onDelete={() => setPendingDelete({ type: "bulk" })}
341
+ onAddToMyDay={handleBulkAddToMyDay}
342
+ />
343
+ <div role="region" aria-label="Tasks list" className="max-w-2xl mx-auto w-full flex flex-col gap-6 p-6">
344
+ <h1 className="text-xl font-bold uppercase tracking-wide">Tasks</h1>
345
+
346
+ {error && <Alert>{error}</Alert>}
347
+ {truncated && view.type !== "flagged" && (
348
+ <Alert>
349
+ This folder has more tasks than can be shown at once - only the first {LIST_PAGE_SIZE * MAX_LIST_PAGES} are listed.
350
+ </Alert>
351
+ )}
352
+
353
+ {view.type === "flagged" ? (
354
+ <FlaggedEmailList loading={flaggedLoading} error={flaggedError} messages={flaggedMessages} />
355
+ ) : (
356
+ <>
357
+ <form onSubmit={handleCreate} className="flex flex-wrap items-center gap-2 bg-surface border border-border rounded-md p-2">
358
+ <input
359
+ ref={addTaskRef}
360
+ type="text"
361
+ aria-label="Add a task"
362
+ {...addTaskHint}
363
+ className={`${INPUT_CLASS} min-w-40 basis-40 flex-1`}
364
+ value={title}
365
+ onChange={(e) => setTitle(e.target.value)}
366
+ placeholder="Add a task"
367
+ />
368
+ <input
369
+ type="date"
370
+ aria-label="Due date"
371
+ className={INPUT_CLASS}
372
+ value={dueDate}
373
+ onChange={(e) => setDueDate(e.target.value)}
374
+ />
375
+ <select
376
+ aria-label="Priority"
377
+ className={INPUT_CLASS}
378
+ value={priority}
379
+ onChange={(e) => setPriority(e.target.value as TaskPriority)}
380
+ >
381
+ <option value="low">Low</option>
382
+ <option value="normal">Normal</option>
383
+ <option value="high">High</option>
384
+ </select>
385
+ {writableMailboxes.length > 1 && (
386
+ <select
387
+ aria-label="Task mailbox"
388
+ className={`${INPUT_CLASS} max-w-40`}
389
+ // `writableMailboxes` only has entries once `mailboxUid` has resolved.
390
+ value={targetMailboxUid ?? mailboxUid}
391
+ onChange={(e) => setTargetMailboxUid(e.target.value)}
392
+ >
393
+ {writableMailboxes.map((mb) => (
394
+ <option key={mb.uid} value={mb.uid}>
395
+ {mb.displayName}
396
+ {mb.ownerUserUid === userUid ? "" : " (shared)"}
397
+ </option>
398
+ ))}
399
+ </select>
400
+ )}
401
+ <Button type="submit" loading={creating} disabled={creating} className="!w-auto shrink-0">
402
+ Add
403
+ </Button>
404
+ </form>
405
+ {createError && <Alert>{createError}</Alert>}
406
+ {addedElsewhere && (
407
+ <p role="status" className="text-sm py-2 px-3 rounded-sm bg-surface-alt text-text">
408
+ &ldquo;{addedElsewhere.title}&rdquo; was added to{" "}
409
+ {mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox"}.{" "}
410
+ <a
411
+ href={`/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`}
412
+ data-router-shallow=""
413
+ data-router-prefetch="false"
414
+ className="font-medium text-primary-dark hover:underline"
415
+ >
416
+ View that mailbox&rsquo;s tasks
417
+ </a>
418
+ </p>
419
+ )}
420
+
421
+ {loading ? (
422
+ <p className="text-sm text-text-muted">Loading&hellip;</p>
423
+ ) : viewFiltered.length === 0 ? (
424
+ <p className="text-sm text-text-muted">No tasks yet.</p>
425
+ ) : viewMode === "grid" ? (
426
+ <TaskTable
427
+ tasks={viewFiltered}
428
+ checkedUids={checkedUids}
429
+ onToggleChecked={toggleChecked}
430
+ onToggle={handleToggle}
431
+ onDelete={requestDelete}
432
+ />
433
+ ) : (
434
+ <>
435
+ {BUCKET_ORDER.map((bucket) => {
436
+ const items = grouped.get(bucket)!;
437
+ if (items.length === 0) {
438
+ return null;
439
+ }
440
+ return (
441
+ <TaskGroup
442
+ key={bucket}
443
+ label={bucket}
444
+ tasks={items}
445
+ checkedUids={checkedUids}
446
+ onToggleChecked={toggleChecked}
447
+ onToggle={handleToggle}
448
+ onDelete={requestDelete}
449
+ />
450
+ );
451
+ })}
452
+ {completedTasks.length > 0 && (
453
+ <TaskGroup
454
+ label="Completed"
455
+ tasks={completedTasks}
456
+ checkedUids={checkedUids}
457
+ onToggleChecked={toggleChecked}
458
+ onToggle={handleToggle}
459
+ onDelete={requestDelete}
460
+ />
461
+ )}
462
+ </>
463
+ )}
464
+ </>
465
+ )}
466
+ </div>
467
+ </div>
468
+ <Modal open={pendingDelete !== null} onClose={() => setPendingDelete(null)} title="Delete tasks">
469
+ <p className="text-sm mb-5">
470
+ {pendingDelete?.type === "single"
471
+ ? `Delete "${pendingDelete.task.title}"?`
472
+ : `Delete ${checkedTasks.length} selected ${checkedTasks.length === 1 ? "task" : "tasks"}?`}
473
+ </p>
474
+ <div className="flex gap-3 justify-end">
475
+ <Button type="button" variant="secondary" className="!w-auto" onClick={() => setPendingDelete(null)}>
476
+ Cancel
477
+ </Button>
478
+ <Button type="button" className="!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger" onClick={handleConfirmDelete}>
479
+ Delete
480
+ </Button>
481
+ </div>
482
+ </Modal>
483
+ </div>
484
+ );
485
+ }
486
+
487
+ function FlaggedEmailList({ loading, error, messages }: { loading: boolean; error: string | null; messages: Message[] }) {
488
+ if (error) {
489
+ return <Alert>{error}</Alert>;
490
+ }
491
+ if (loading) {
492
+ return <p className="text-sm text-text-muted">Loading&hellip;</p>;
493
+ }
494
+ if (messages.length === 0) {
495
+ return <p className="text-sm text-text-muted">No flagged email.</p>;
496
+ }
497
+ return (
498
+ <ul className="bg-surface border border-border rounded-md divide-y divide-border">
499
+ {messages.map((m) => (
500
+ <li key={m.uid} className="flex items-center gap-3 py-2.5 px-3 text-sm">
501
+ <span className="flex-1 truncate">{m.subject || "(no subject)"}</span>
502
+ <span className="text-xs text-text-muted shrink-0">{m.from.address}</span>
503
+ <span className="text-xs text-text-muted shrink-0">{new Date(m.receivedDate).toLocaleDateString()}</span>
504
+ </li>
505
+ ))}
506
+ </ul>
507
+ );
508
+ }
509
+
510
+ /** A circular completion-toggle control matching Outlook's visual language (not a native checkbox), while
511
+ * staying a real accessible toggle. */
512
+ function CompletionToggle({ task, onToggle }: { task: Task; onToggle: (task: Task) => void }) {
513
+ return (
514
+ <button
515
+ type="button"
516
+ role="checkbox"
517
+ aria-checked={task.completed}
518
+ aria-label={`Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`}
519
+ onClick={() => onToggle(task)}
520
+ className={[
521
+ "w-4 h-4 rounded-full border shrink-0 flex items-center justify-center text-[10px]",
522
+ task.completed ? "bg-primary border-primary text-white" : "border-border text-transparent",
523
+ ].join(" ")}
524
+ >
525
+ &#10003;
526
+ </button>
527
+ );
528
+ }
529
+
530
+ interface TaskGroupProps {
531
+ label: string;
532
+ tasks: Task[];
533
+ checkedUids: Set<string>;
534
+ onToggleChecked: (uid: string) => void;
535
+ onToggle: (task: Task) => void;
536
+ onDelete: (task: Task) => void;
537
+ }
538
+
539
+ function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDelete }: TaskGroupProps) {
540
+ return (
541
+ <div>
542
+ <h2 className="text-xs font-bold uppercase tracking-wide text-text-muted mb-2">{label}</h2>
543
+ <ul className="bg-surface border border-border rounded-md divide-y divide-border">
544
+ {tasks.map((task) => (
545
+ <li key={task.uid} className="flex items-center gap-3 py-2.5 px-3">
546
+ <input
547
+ type="checkbox"
548
+ aria-label={`Select ${task.title}`}
549
+ checked={checkedUids.has(task.uid)}
550
+ onChange={() => onToggleChecked(task.uid)}
551
+ />
552
+ <CompletionToggle task={task} onToggle={onToggle} />
553
+ <span className={["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" ")}>
554
+ {task.title}
555
+ </span>
556
+ {task.priority !== "normal" && (
557
+ <span className={["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" ")}>
558
+ {PRIORITY_LABEL[task.priority]}
559
+ </span>
560
+ )}
561
+ {task.dueDate && (
562
+ <span className="text-xs text-text-muted shrink-0">{parseDueDate(task.dueDate).toLocaleDateString()}</span>
563
+ )}
564
+ <button
565
+ type="button"
566
+ onClick={() => onDelete(task)}
567
+ aria-label={`Delete "${task.title}"`}
568
+ className="text-text-muted hover:text-danger text-sm px-1"
569
+ >
570
+ &times;
571
+ </button>
572
+ </li>
573
+ ))}
574
+ </ul>
575
+ </div>
576
+ );
577
+ }
578
+
579
+ interface TaskTableProps {
580
+ tasks: Task[];
581
+ checkedUids: Set<string>;
582
+ onToggleChecked: (uid: string) => void;
583
+ onToggle: (task: Task) => void;
584
+ onDelete: (task: Task) => void;
585
+ }
586
+
587
+ function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }: TaskTableProps) {
588
+ const allChecked = tasks.length > 0 && tasks.every((t) => checkedUids.has(t.uid));
589
+
590
+ function toggleAll() {
591
+ if (allChecked) {
592
+ // Every task is already checked when this branch runs (that's what `allChecked` means) — no
593
+ // per-task guard needed, unlike the `else` branch below where only *some* tasks may be checked.
594
+ for (const t of tasks) {
595
+ onToggleChecked(t.uid);
596
+ }
597
+ } else {
598
+ for (const t of tasks) {
599
+ if (!checkedUids.has(t.uid)) {
600
+ onToggleChecked(t.uid);
601
+ }
602
+ }
603
+ }
604
+ }
605
+
606
+ return (
607
+ <div className="overflow-x-auto">
608
+ <table className="w-full text-sm bg-surface border border-border rounded-md">
609
+ <thead>
610
+ <tr className="border-b border-border text-left">
611
+ <th className="w-8 px-3 py-2">
612
+ <input type="checkbox" aria-label="Select all tasks" checked={allChecked} onChange={toggleAll} />
613
+ </th>
614
+ <th className="w-8 px-3 py-2" />
615
+ <th className="px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted">Title</th>
616
+ <th className="px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted">Due Date</th>
617
+ <th className="px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted">Importance</th>
618
+ <th className="w-8 px-3 py-2" />
619
+ </tr>
620
+ </thead>
621
+ <tbody>
622
+ {tasks.map((task) => (
623
+ <tr key={task.uid} className="border-b border-border last:border-0">
624
+ <td className="px-3 py-2">
625
+ <input
626
+ type="checkbox"
627
+ aria-label={`Select ${task.title}`}
628
+ checked={checkedUids.has(task.uid)}
629
+ onChange={() => onToggleChecked(task.uid)}
630
+ />
631
+ </td>
632
+ <td className="px-3 py-2">
633
+ <CompletionToggle task={task} onToggle={onToggle} />
634
+ </td>
635
+ <td className={["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" ")}>{task.title}</td>
636
+ <td className="px-3 py-2 text-text-muted">{task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : ""}</td>
637
+ <td className={["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" ")}>{PRIORITY_LABEL[task.priority]}</td>
638
+ <td className="px-3 py-2">
639
+ <button
640
+ type="button"
641
+ onClick={() => onDelete(task)}
642
+ aria-label={`Delete "${task.title}"`}
643
+ className="text-text-muted hover:text-danger text-sm px-1"
644
+ >
645
+ &times;
646
+ </button>
647
+ </td>
648
+ </tr>
649
+ ))}
650
+ </tbody>
651
+ </table>
652
+ </div>
653
+ );
654
+ }
655
+
656
+ export default TasksPage;
657
+
658
+ /** The tab's title: `Brand: Tasks` (see `pageTitle()`). */
659
+ export const title = pageTitle("Tasks");