@rapidmx/web-client 0.10.0 → 0.12.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 (324) hide show
  1. package/README.md +183 -2
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/mailboxes/new/index.tsx +3 -3
  7. package/apps/admin/quarantine/index.tsx +2 -1
  8. package/apps/admin/signing-certificates/index.tsx +343 -0
  9. package/apps/escrow/_layout.tsx +8 -2
  10. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  11. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  12. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  13. package/apps/shared/appearance/ColorField.tsx +107 -0
  14. package/apps/shared/appearance/appearanceCache.ts +106 -0
  15. package/apps/shared/appearance/appearanceContext.ts +77 -0
  16. package/apps/shared/appearance/bootScript.ts +35 -0
  17. package/apps/shared/appearance/color.ts +169 -0
  18. package/apps/shared/appearance/photo.ts +88 -0
  19. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  20. package/apps/shared/appearance/theme.ts +355 -0
  21. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +4 -4
  22. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  23. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  24. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  25. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +60 -14
  26. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +65 -16
  27. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  28. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  29. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  30. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  31. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  32. package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
  33. package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
  34. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  35. package/apps/shared/components/layout/AppShell.tsx +98 -32
  36. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  37. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  38. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  39. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  40. package/apps/shared/components/layout/UserMenu.tsx +75 -6
  41. package/apps/shared/components/mail/ConversationList.tsx +4 -2
  42. package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
  43. package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
  44. package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
  45. package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
  46. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  47. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  48. package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
  49. package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
  50. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  51. package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
  52. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  53. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  54. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  55. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  56. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  57. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  58. package/apps/shared/components/mail/reading/color.ts +245 -0
  59. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  60. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  61. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  62. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  63. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  64. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  65. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  66. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  67. package/apps/shared/components/sharing/PrincipalListField.tsx +82 -0
  68. package/apps/shared/components/sharing/PrincipalPicker.tsx +76 -0
  69. package/apps/shared/components/sharing/PrincipalResolver.tsx +164 -0
  70. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  71. package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
  72. package/apps/shared/mail/folderCounts.ts +9 -3
  73. package/apps/shared/mail/folderTree.ts +143 -0
  74. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  75. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  76. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  77. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  78. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  79. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  80. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  81. package/apps/shared/mail/outbox/sendState.ts +35 -0
  82. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  83. package/apps/shared/mail/useMailConnection.ts +81 -28
  84. package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
  85. package/apps/shared/mail/useNewMailNotifications.ts +47 -32
  86. package/apps/shared/navigation/AppRouter.tsx +2 -2
  87. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  88. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  89. package/apps/shared/notifications/apiErrors.ts +98 -0
  90. package/apps/shared/notifications/headerOffset.ts +35 -0
  91. package/apps/shared/notifications/pushStatus.ts +56 -0
  92. package/apps/shared/notifications/store.ts +549 -0
  93. package/apps/shared/notifications/systemErrors.ts +63 -0
  94. package/apps/shared/notifications/useNotifications.ts +41 -0
  95. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  96. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  97. package/apps/shared/signing/enrollmentView.ts +251 -0
  98. package/apps/shared/signing/signingInfo.ts +58 -0
  99. package/apps/shared/signing/useNow.ts +19 -0
  100. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  101. package/apps/shared/styles/app.css +80 -0
  102. package/apps/www/_layout.tsx +8 -2
  103. package/apps/www/_routes.ts +1 -0
  104. package/apps/www/calendar/index.tsx +9 -14
  105. package/apps/www/contacts/[uid].tsx +5 -8
  106. package/apps/www/contacts/index.tsx +587 -584
  107. package/apps/www/index.tsx +38 -27
  108. package/apps/www/messages/[uid].tsx +3 -1
  109. package/apps/www/settings/appearance/index.tsx +21 -0
  110. package/apps/www/settings/auto-reply/index.tsx +3 -7
  111. package/apps/www/settings/encryption/index.tsx +151 -116
  112. package/apps/www/settings/filters/[uid].tsx +3 -1
  113. package/apps/www/settings/filters/new/index.tsx +3 -1
  114. package/apps/www/settings/privacy/index.tsx +3 -8
  115. package/apps/www/settings/read-receipts/index.tsx +3 -7
  116. package/apps/www/settings/sharing/index.tsx +44 -65
  117. package/apps/www/settings/signatures/[uid].tsx +3 -1
  118. package/apps/www/settings/signatures/new/index.tsx +3 -2
  119. package/apps/www/tasks/index.tsx +11 -16
  120. package/dist/apps/admin/_layout.d.ts +5 -1
  121. package/dist/apps/admin/_layout.js +3 -2
  122. package/dist/apps/admin/index.js +3 -2
  123. package/dist/apps/admin/ingest-queue/index.js +2 -1
  124. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  125. package/dist/apps/admin/mailboxes/new/index.js +1 -1
  126. package/dist/apps/admin/quarantine/index.js +2 -1
  127. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  128. package/dist/apps/admin/signing-certificates/index.js +161 -0
  129. package/dist/apps/escrow/_layout.d.ts +5 -1
  130. package/dist/apps/escrow/_layout.js +3 -2
  131. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  132. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  133. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  134. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  135. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  136. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  137. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  138. package/dist/apps/shared/appearance/ColorField.js +40 -0
  139. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  140. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  141. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  142. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  143. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  144. package/dist/apps/shared/appearance/bootScript.js +33 -0
  145. package/dist/apps/shared/appearance/color.d.ts +65 -0
  146. package/dist/apps/shared/appearance/color.js +132 -0
  147. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  148. package/dist/apps/shared/appearance/photo.js +79 -0
  149. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  150. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  151. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  152. package/dist/apps/shared/appearance/theme.js +251 -0
  153. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -2
  154. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  155. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  156. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  157. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  158. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  159. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  160. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +25 -5
  161. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +18 -7
  162. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  163. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  164. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  165. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  166. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  167. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  168. package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
  169. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
  170. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  171. package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
  172. package/dist/apps/shared/components/layout/AppShell.js +59 -13
  173. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  174. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  175. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  176. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  177. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  178. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  179. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  180. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  181. package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
  182. package/dist/apps/shared/components/layout/UserMenu.js +41 -7
  183. package/dist/apps/shared/components/mail/ConversationList.js +3 -2
  184. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
  185. package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
  186. package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
  187. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
  188. package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
  189. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
  190. package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
  191. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  192. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  193. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  194. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  195. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
  196. package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
  197. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
  198. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
  199. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  200. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  201. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
  202. package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
  203. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  204. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  205. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  206. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  207. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  208. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  209. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  210. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  211. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  212. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  213. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  214. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  215. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  216. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  217. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  218. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  219. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  220. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  221. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  222. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  223. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  224. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  225. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  226. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  227. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  228. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  229. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  230. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  231. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  232. package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +24 -0
  233. package/dist/apps/shared/components/sharing/PrincipalListField.js +23 -0
  234. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +28 -0
  235. package/dist/apps/shared/components/sharing/PrincipalPicker.js +24 -0
  236. package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +44 -0
  237. package/dist/apps/shared/components/sharing/PrincipalResolver.js +92 -0
  238. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  239. package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
  240. package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
  241. package/dist/apps/shared/mail/folderCounts.js +9 -3
  242. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  243. package/dist/apps/shared/mail/folderTree.js +116 -0
  244. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  245. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  246. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  247. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  248. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  249. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  250. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  251. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  252. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  253. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  254. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  255. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  256. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  257. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  258. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  259. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  260. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  261. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  262. package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
  263. package/dist/apps/shared/mail/useMailConnection.js +64 -22
  264. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
  265. package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
  266. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
  267. package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
  268. package/dist/apps/shared/navigation/AppRouter.js +2 -2
  269. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  270. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  271. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  272. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  273. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  274. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  275. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  276. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  277. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  278. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  279. package/dist/apps/shared/notifications/store.d.ts +136 -0
  280. package/dist/apps/shared/notifications/store.js +416 -0
  281. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  282. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  283. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  284. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  285. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  286. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  287. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  288. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  289. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  290. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  291. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  292. package/dist/apps/shared/signing/signingInfo.js +48 -0
  293. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  294. package/dist/apps/shared/signing/useNow.js +18 -0
  295. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  296. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  297. package/dist/apps/shared/styles/app.css +80 -0
  298. package/dist/apps/www/_layout.d.ts +5 -1
  299. package/dist/apps/www/_layout.js +3 -2
  300. package/dist/apps/www/_routes.js +1 -0
  301. package/dist/apps/www/calendar/index.js +3 -4
  302. package/dist/apps/www/contacts/[uid].js +5 -7
  303. package/dist/apps/www/contacts/index.js +14 -21
  304. package/dist/apps/www/index.js +37 -19
  305. package/dist/apps/www/messages/[uid].js +3 -1
  306. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  307. package/dist/apps/www/settings/appearance/index.js +13 -0
  308. package/dist/apps/www/settings/auto-reply/index.js +3 -6
  309. package/dist/apps/www/settings/encryption/index.js +103 -95
  310. package/dist/apps/www/settings/filters/[uid].js +3 -1
  311. package/dist/apps/www/settings/filters/new/index.js +3 -1
  312. package/dist/apps/www/settings/privacy/index.js +5 -8
  313. package/dist/apps/www/settings/read-receipts/index.js +3 -6
  314. package/dist/apps/www/settings/sharing/index.js +19 -33
  315. package/dist/apps/www/settings/signatures/[uid].js +3 -1
  316. package/dist/apps/www/settings/signatures/new/index.js +3 -2
  317. package/dist/apps/www/tasks/index.js +8 -14
  318. package/package.json +2 -2
  319. package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
  320. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  321. package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
  322. package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
  323. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  324. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -0,0 +1,49 @@
1
+ import { RecipientEncryptionStatus } from "@rapidmx/react-shared/crypto/composeSecurity.js";
2
+ import type { EncryptionPolicy } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
3
+ /**
4
+ * Whether a message must be treated as encrypted - the one place that decides it, as a pure function of what is *known*.
5
+ *
6
+ * **The rule is fail open.** A message is plain, its draft is saved, and nothing about encryption is ever shown, unless encryption is
7
+ * actually in play. Encryption is in play only when
8
+ *
9
+ * 1. the user asked for it ("Encrypt this message", or a reply to encrypted mail, whose quote may carry decrypted text) - that is local
10
+ * knowledge and needs no request to have succeeded; or
11
+ * 2. the system's encryption policy **was loaded successfully**, this sender has an encryption key (enrolled on their mailbox, or unlocked
12
+ * in this tab), the message has recipients, **every** recipient's key lookup succeeded, and the policy auto-applies encryption to all
13
+ * of them (`decideMessageEncryption().autoEncrypt`: policy `automatic` for their tier, both sides preferring "mutual", a key found).
14
+ *
15
+ * Anything that failed to load, is still loading or is slow (the policy, the mailbox, a recipient's lookup) is **unknown**, and unknown
16
+ * counts as "not encrypted": that is what stops a gateway that flaps, a slow request, or a user who has never set encryption up from
17
+ * ever blocking a draft, blocking Close, blocking Send or showing an "encryption can't be checked" message. What was unknown is
18
+ * reported in `unknown`, so a caller that is about to send can give the loads a moment - and only a caller that finds out later that the
19
+ * message *does* need encryption acts on it (see `ComposeWindow`'s transition rules).
20
+ *
21
+ * Why this is safe enough: the requirement only ever protects *this client's* plaintext drafts and sends from being unencrypted when the
22
+ * server's policy says they should be; it is not the enforcement point (the keys are the user's, the server never sees them). A missing
23
+ * policy therefore does not mean "encrypt or refuse" - it means the client cannot tell, and the honest default is the one that works.
24
+ */
25
+ export interface EncryptionFacts {
26
+ /** The user turned encryption on, or this compose is a reply to / forward of an encrypted message. */
27
+ encryptRequested: boolean;
28
+ /** The policy, once it has loaded; `undefined` while loading, failed or slow. */
29
+ policy: EncryptionPolicy | undefined;
30
+ /** The sender's mailbox has loaded (its enrolled keys are known). */
31
+ mailboxLoaded: boolean;
32
+ /** The sender has an encryption key to encrypt with: enrolled on the mailbox, unlocked in this tab, or offered earlier this session. */
33
+ hasEncryptionKey: boolean;
34
+ /** One entry per current recipient (To, Cc and Bcc): the resolved status, or `undefined` while that recipient's lookup is pending or failed. */
35
+ recipients: (RecipientEncryptionStatus | undefined)[];
36
+ }
37
+ export type EncryptionReason = "requested" | "policy";
38
+ /** What was not known, and could still change the answer. */
39
+ export type UnknownFact = "mailbox" | "policy" | "recipients";
40
+ export interface EncryptionRequirement {
41
+ /** The message must be encrypted: it is never stored as a plaintext draft and never sent in the clear. */
42
+ required: boolean;
43
+ reason: EncryptionReason | null;
44
+ /** What was unknown (and so assumed "no encryption"). Empty when the answer cannot change with more loading. Never shown to the user. */
45
+ unknown: UnknownFact[];
46
+ }
47
+ /** Any tier of `policy` auto-encrypts - only then can what is still unknown about the recipients turn a message into an encrypted one. */
48
+ export declare function policyCanAutoEncrypt(policy: EncryptionPolicy): boolean;
49
+ export declare function evaluateEncryptionRequirement(facts: EncryptionFacts): EncryptionRequirement;
@@ -0,0 +1,35 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { decideMessageEncryption } from "@rapidmx/react-shared/crypto/composeSecurity.js";
6
+ /** Any tier of `policy` auto-encrypts - only then can what is still unknown about the recipients turn a message into an encrypted one. */
7
+ export function policyCanAutoEncrypt(policy) {
8
+ return [policy.encryptSameOrg, policy.encryptFederated, policy.encryptExternal].includes("automatic");
9
+ }
10
+ export function evaluateEncryptionRequirement(facts) {
11
+ if (facts.encryptRequested) {
12
+ return { required: true, reason: "requested", unknown: [] };
13
+ }
14
+ const unknown = [];
15
+ if (!facts.hasEncryptionKey) {
16
+ // Without a key nothing can be encrypted: a user who never set encryption up sees nothing at all. Whether the mailbox has one is
17
+ // only *unknown* while it has not loaded.
18
+ return { required: false, reason: null, unknown: facts.mailboxLoaded ? [] : ["mailbox"] };
19
+ }
20
+ if (!facts.policy) {
21
+ return { required: false, reason: null, unknown: ["policy"] };
22
+ }
23
+ if (!policyCanAutoEncrypt(facts.policy)) {
24
+ return { required: false, reason: null, unknown };
25
+ }
26
+ if (facts.recipients.length === 0) {
27
+ // Nobody to decide by yet: plain until a recipient is added.
28
+ return { required: false, reason: null, unknown };
29
+ }
30
+ const statuses = facts.recipients.filter((status) => !!status);
31
+ if (statuses.length < facts.recipients.length) {
32
+ return { required: false, reason: null, unknown: ["recipients"] };
33
+ }
34
+ return decideMessageEncryption(statuses).autoEncrypt ? { required: true, reason: "policy", unknown } : { required: false, reason: null, unknown };
35
+ }
@@ -44,6 +44,8 @@ export interface MailShellContextValue {
44
44
  * A no-op on the default context value, which is only ever read outside a real shell.
45
45
  */
46
46
  onFolderCreated: (folder: Folder) => void;
47
+ /** Says which folders the messages on screen live in: one the sidebar does not know is looked for (see `MailConnection.noteFolderUids`). A no-op on the default context value. */
48
+ noteFolderUids: (folderUids: Iterable<string>) => void;
47
49
  /**
48
50
  * Bumped whenever new mail (or another change to a message) may have arrived - a push event, a reconnect or the safety-net
49
51
  * poll - so the list on screen can quietly refetch its first page. See `useMailLiveUpdates()`. Never changes on the default
@@ -15,10 +15,12 @@ import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
15
15
  import { prefetchComposeWindow, useCompose } from "../compose/ComposeContext.js";
16
16
  import LocalIndexLifecycle from "../../../search/LocalIndexLifecycle.js";
17
17
  import { NO_LIVE_UPDATES } from "../../../mail/useMailLiveUpdates.js";
18
- import { FOLDER_ORDER, MAILBOX_LIST_LIMIT, MailConnectionContext, useMailConnection } from "../../../mail/useMailConnection.js";
18
+ import { MAILBOX_LIST_LIMIT, MailConnectionContext, useMailConnection } from "../../../mail/useMailConnection.js";
19
+ import { folderRows } from "../../../mail/folderTree.js";
19
20
  import { badgeFor, badgeLabel, countOfFolder, inboxUnreadTotal, } from "../../../mail/folderCounts.js";
20
21
  import { useUnreadTitle } from "../../../mail/useUnreadTitle.js";
21
- import NewMailToasts from "../NewMailToasts.js";
22
+ import { pendingCountFor, usePendingSends } from "../../../mail/outbox/pendingSends.js";
23
+ import OutboxBadge from "../OutboxBadge.js";
22
24
  import { ariaKeyShortcuts, withHint } from "../../../keyboard/format.js";
23
25
  import { SHORTCUTS } from "../../../keyboard/keymap.js";
24
26
  import { useKeyEnvironment } from "../../../keyboard/ShortcutProvider.js";
@@ -37,6 +39,7 @@ const MailShellContext = createContext({
37
39
  mailboxes: [],
38
40
  mailboxFolders: [],
39
41
  onFolderCreated: () => undefined,
42
+ noteFolderUids: () => undefined,
40
43
  live: NO_LIVE_UPDATES,
41
44
  trackMessageChange: () => NO_TRACKER,
42
45
  });
@@ -53,13 +56,6 @@ const FOLDER_LABELS = {
53
56
  archive: "Archive",
54
57
  deleted_items: "Deleted Items",
55
58
  };
56
- function folderSortKey(folder) {
57
- const idx = FOLDER_ORDER.indexOf(folder.type);
58
- return idx === -1 ? FOLDER_ORDER.length : idx;
59
- }
60
- function sortedFoldersOf(folders) {
61
- return [...folders].sort((a, b) => folderSortKey(a) - folderSortKey(b) || a.name.localeCompare(b.name));
62
- }
63
59
  /** The badge an "All Mailboxes" entry shows: the folders of that type, summed across every mailbox, under the same rules as a single folder's. */
64
60
  function aggregateBadge(mailboxFolders, type, counts) {
65
61
  const sum = mailboxFolders.reduce((total, mf) => {
@@ -128,7 +124,9 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
128
124
  const navigate = useNavigate();
129
125
  const hosted = useContext(MailConnectionContext);
130
126
  const own = useMailConnection({ userUid, enabled: !hosted, open: navigate });
131
- const { status, error, mailboxes, mailboxFolders, foldersLoading, onFolderCreated, live, folderCounts, notifications } = hosted ?? own;
127
+ const { status, error, mailboxes, mailboxFolders, foldersLoading, onFolderCreated, noteFolderUids, live, folderCounts, outbox: outboxStatuses } = hosted ?? own;
128
+ // The messages this tab is still handing to the server: the Outbox pill counts them at once.
129
+ const pendingSends = usePendingSends();
132
130
  const [requestedMailboxUid, setRequestedMailboxUid] = useState(null);
133
131
  const [requestedFolderUid, setRequestedFolderUid] = useState(null);
134
132
  const [requestedAggregateType, setRequestedAggregateType] = useState(null);
@@ -171,7 +169,7 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
171
169
  const trackMessageChange = folderCounts.track;
172
170
  // `(3) Acme: Mail` in the tab strip while there is unread mail in an Inbox - kept by the frame when there is one.
173
171
  useUnreadTitle(inboxUnreadTotal(mailboxFolders, counts), { enabled: !hosted });
174
- const contextValue = useMemo(() => ({ mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, live, trackMessageChange }), [mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, live, trackMessageChange]);
172
+ const contextValue = useMemo(() => ({ mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, noteFolderUids, live, trackMessageChange }), [mailboxUid, folderUid, aggregateFolderType, mailboxes, mailboxFolders, onFolderCreated, noteFolderUids, live, trackMessageChange]);
175
173
  // A full-screen takeover, not nested inside the rest of the app's chrome — there's nothing else for a
176
174
  // mailbox-less caller to do here yet, so the icon rail/header/folder tree don't render at all. Checks
177
175
  // `mailboxes.length` directly (not `!mailboxUid`) since `mailboxUid` is legitimately undefined in
@@ -200,16 +198,24 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
200
198
  ? "bg-primary/10 text-primary-dark font-semibold"
201
199
  : "text-text hover:bg-surface-alt",
202
200
  ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[type] }), badge && _jsx(FolderBadgeChip, { badge: badge })] }, type));
203
- }) })] })), mailboxFolders.map(({ mailbox, folders, error: mailboxError }) => (_jsxs("div", { children: [_jsxs("div", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-1 px-2.5 truncate", children: [mailbox.displayName, mailbox.ownerUserUid ? "" : " (shared)"] }), mailboxError && (_jsx("div", { className: "px-2.5 pb-1", children: _jsx(Alert, { children: mailboxError }) })), _jsx("div", { className: "flex flex-col gap-0.5", children: sortedFoldersOf(folders).map((folder) => {
201
+ }) })] })), mailboxFolders.map(({ mailbox, folders, error: mailboxError }) => (_jsxs("div", { children: [_jsxs("div", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-1 px-2.5 truncate", children: [mailbox.displayName, mailbox.ownerUserUid ? "" : " (shared)"] }), mailboxError && (_jsx("div", { className: "px-2.5 pb-1", children: _jsx(Alert, { children: mailboxError }) })), _jsx("div", { className: "flex flex-col gap-0.5", children: folderRows(folders, pendingCountFor(mailbox.uid, pendingSends)).map((row) => {
202
+ if (row.kind === "placeholder") {
203
+ // A folder the server has not made (or told us about) yet - the Outbox and Sent Items are made with the first message
204
+ // sent - shown the moment a message is on its way. Nothing to open; the real folder replaces it when it arrives.
205
+ const sending = pendingCountFor(mailbox.uid, pendingSends);
206
+ return (_jsxs("div", { "data-folder-placeholder": row.type, className: "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5 text-text-muted", children: [_jsx("span", { children: FOLDER_LABELS[row.type] }), row.type === "outbox" && _jsx(OutboxBadge, { total: sending, pendingHere: sending })] }, `placeholder-${row.type}`));
207
+ }
208
+ const folder = row.folder;
204
209
  const badge = badgeFor(folder.type, countOfFolder(folder, counts));
205
210
  return (_jsxs("a", { href: `/?mailboxUid=${encodeURIComponent(mailbox.uid)}&folderUid=${encodeURIComponent(folder.uid)}`, className: [
206
211
  "flex items-center justify-between text-sm rounded-sm py-1.5 px-2.5",
207
212
  folder.uid === folderUid
208
213
  ? "bg-primary/10 text-primary-dark font-semibold"
209
214
  : "text-text hover:bg-surface-alt",
210
- ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[folder.type] ?? folder.name }), badge && _jsx(FolderBadgeChip, { badge: badge })] }, folder.uid));
215
+ ].join(" "), children: [_jsx("span", { className: badge?.kind === "unread" ? "font-semibold" : undefined, children: FOLDER_LABELS[folder.type] ?? folder.name }), badge &&
216
+ (folder.type === "outbox" ? (_jsx(OutboxBadge, { total: badge.value, status: outboxStatuses[folder.uid], pendingHere: pendingCountFor(mailbox.uid, pendingSends) })) : (_jsx(FolderBadgeChip, { badge: badge })))] }, folder.uid));
211
217
  }) })] }, mailbox.uid)))] }))] }));
212
- inner = (_jsxs(_Fragment, { children: [_jsx(MailShortcuts, { mailboxUid: defaultMailboxUid }), _jsx("aside", { className: "hidden md:flex w-64 shrink-0 bg-surface border-r border-border flex-col", children: sidebarContent() }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Folders", children: _jsx("div", { className: "flex flex-col", children: sidebarContent() }) }), _jsxs("main", { className: "flex-1 min-w-0 overflow-y-auto", children: [_jsx("button", { type: "button", className: "md:hidden m-3 w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": "Open folders", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx(MailShellContext.Provider, { value: contextValue, children: children })] }), !hosted && (_jsx(NewMailToasts, { toasts: notifications.toasts, onDismiss: notifications.dismiss, offerDesktop: notifications.offerDesktop, onEnableDesktop: () => void notifications.enableDesktop(), onDeclineDesktop: notifications.declineDesktop }))] }));
218
+ inner = (_jsxs(_Fragment, { children: [_jsx(MailShortcuts, { mailboxUid: defaultMailboxUid }), _jsx("aside", { className: "hidden md:flex w-64 shrink-0 bg-surface border-r border-border flex-col", children: sidebarContent() }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Folders", children: _jsx("div", { className: "flex flex-col", children: sidebarContent() }) }), _jsxs("main", { className: "flex-1 min-w-0 overflow-y-auto", children: [_jsx("button", { type: "button", className: "md:hidden m-3 w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": "Open folders", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx(MailShellContext.Provider, { value: contextValue, children: children })] })] }));
213
219
  }
214
220
  // Wraps AppShell unconditionally, at a stable tree position regardless of whether mailboxUid has
215
221
  // resolved yet - KeyEnrollmentGate itself passes `children` through untouched until a real
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ export interface EncryptedBodyProps {
3
+ /** This device has no unlocked key session for the message's mailbox: unlocking could help. */
4
+ locked: boolean;
5
+ /** Why the message couldn't be decrypted when the keys were available (shown for the failed state). */
6
+ reason?: string;
7
+ /** Opens the app's unlock prompt (`useUnlockPrompt().requestUnlock`). Only used - and only offered - when `locked`. */
8
+ onUnlock: () => void;
9
+ /** An unlock is being asked for: the button waits. */
10
+ unlocking?: boolean;
11
+ }
12
+ /**
13
+ * What an encrypted message's body is until (and unless) it can be read, drawn as card content in the app's own tokens: on the theme's opaque surface, so
14
+ * it stays legible over a translucent card and a background photo.
15
+ *
16
+ * **Locked** - the keys are not unlocked: a lock, "This message is encrypted", a hint and a primary Unlock button, which opens the same prompt compose's
17
+ * "Unlock to sign or encrypt" does. When it succeeds the pane decrypts and shows the message in place.
18
+ * **Cannot be decrypted** - the keys are unlocked and it still can't be opened (no key for this mailbox, not a recipient, damaged, unsupported): the reason,
19
+ * and no button - unlocking again wouldn't change it.
20
+ */
21
+ export default function EncryptedBody({ locked, reason, onUnlock, unlocking }: EncryptedBodyProps): React.JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HiOutlineExclamationTriangle, HiOutlineLockClosed } from "react-icons/hi2";
3
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
4
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
5
+ import { useResolvedTheme } from "../../../appearance/resolvedTheme.js";
6
+ import { formatColour } from "./color.js";
7
+ import { useThemeSurface } from "./themeSurface.js";
8
+ /**
9
+ * What an encrypted message's body is until (and unless) it can be read, drawn as card content in the app's own tokens: on the theme's opaque surface, so
10
+ * it stays legible over a translucent card and a background photo.
11
+ *
12
+ * **Locked** - the keys are not unlocked: a lock, "This message is encrypted", a hint and a primary Unlock button, which opens the same prompt compose's
13
+ * "Unlock to sign or encrypt" does. When it succeeds the pane decrypts and shows the message in place.
14
+ * **Cannot be decrypted** - the keys are unlocked and it still can't be opened (no key for this mailbox, not a recipient, damaged, unsupported): the reason,
15
+ * and no button - unlocking again wouldn't change it.
16
+ */
17
+ export default function EncryptedBody({ locked, reason, onUnlock, unlocking = false }) {
18
+ const theme = useResolvedTheme();
19
+ const surface = useThemeSurface(theme);
20
+ const Icon = locked ? HiOutlineLockClosed : HiOutlineExclamationTriangle;
21
+ return (_jsxs("div", { className: "flex flex-col items-center gap-2 rounded-md border border-border px-4 py-8 text-center", style: { backgroundColor: formatColour(surface.background) }, children: [_jsx("span", { "aria-hidden": "true", className: ["inline-flex h-12 w-12 items-center justify-center rounded-full", locked ? "bg-primary/10 text-primary-dark" : "bg-warning/15 text-text"].join(" "), children: _jsx(Icon, { size: 24 }) }), _jsx("p", { className: "text-base font-semibold text-text", children: locked ? "This message is encrypted" : "This message can’t be decrypted" }), locked ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-text-muted", children: "Unlock your keys to read it" }), _jsx(Button, { type: "button", className: "!w-auto mt-1", "aria-label": "Unlock to view this message", loading: unlocking, disabled: unlocking, onClick: onUnlock, children: "Unlock" })] })) : (reason && (_jsx("div", { className: "mt-1 max-w-prose text-left", children: _jsx(Alert, { children: reason }) })))] }));
22
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ /** The subject an encrypted message carries on the server (RFC 9788's placeholder): the real one is inside the ciphertext. */
3
+ export declare const ENCRYPTED_SUBJECT_PLACEHOLDER = "[...]";
4
+ /**
5
+ * What a list row's preview line says for an encrypted message nothing has been decrypted of yet: a small lock and "Encrypted message". The server never
6
+ * derives a preview for encrypted mail (it has no plaintext), so the line would otherwise be empty.
7
+ */
8
+ export declare function EncryptedPreview(): React.JSX.Element;
9
+ /**
10
+ * Whether a conversation row stands for an encrypted latest message. A conversation summary carries no flag for it, but an encrypted message has no preview
11
+ * and the placeholder subject - which a plaintext message with an empty body does not.
12
+ */
13
+ export declare function conversationLooksEncrypted(conversation: {
14
+ subject: string;
15
+ latestPreview: string;
16
+ }): boolean;
17
+ /** The subject to show for a message's own: what an encrypted message says is the placeholder, which nobody should have to read as a title. */
18
+ export declare function displaySubject(subject: string): string;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HiOutlineLockClosed } from "react-icons/hi2";
3
+ /** The subject an encrypted message carries on the server (RFC 9788's placeholder): the real one is inside the ciphertext. */
4
+ export const ENCRYPTED_SUBJECT_PLACEHOLDER = "[...]";
5
+ /**
6
+ * What a list row's preview line says for an encrypted message nothing has been decrypted of yet: a small lock and "Encrypted message". The server never
7
+ * derives a preview for encrypted mail (it has no plaintext), so the line would otherwise be empty.
8
+ */
9
+ export function EncryptedPreview() {
10
+ return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx(HiOutlineLockClosed, { size: 12, "aria-hidden": "true", className: "shrink-0" }), "Encrypted message"] }));
11
+ }
12
+ /**
13
+ * Whether a conversation row stands for an encrypted latest message. A conversation summary carries no flag for it, but an encrypted message has no preview
14
+ * and the placeholder subject - which a plaintext message with an empty body does not.
15
+ */
16
+ export function conversationLooksEncrypted(conversation) {
17
+ return conversation.latestPreview === "" && conversation.subject === ENCRYPTED_SUBJECT_PLACEHOLDER;
18
+ }
19
+ /** The subject to show for a message's own: what an encrypted message says is the placeholder, which nobody should have to read as a title. */
20
+ export function displaySubject(subject) {
21
+ return subject === ENCRYPTED_SUBJECT_PLACEHOLDER ? "Encrypted message" : subject;
22
+ }
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import type { Attachment } from "@rapidmx/react-shared/mail/mailApi.js";
3
+ import type { MimeAttachment } from "@rapidmx/react-shared/crypto/mime.js";
4
+ import { BodyContent } from "./bodyContent.js";
5
+ export interface MessageBodyProps {
6
+ messageUid: string;
7
+ /** Part of what a remembered body is keyed on, so an edited message is fetched again. */
8
+ messageVersion: number;
9
+ /** The accessible name of the body. */
10
+ title: string;
11
+ /** A body the caller already has - decrypted or verified content. Shown instead of asking the server. */
12
+ content?: BodyContent;
13
+ /** The message's server-side attachments, for resolving its inline `cid:` images. */
14
+ attachments?: Attachment[];
15
+ /** The parts inside a signed or encrypted message, for resolving its inline images from the decrypted content. */
16
+ inlineParts?: MimeAttachment[];
17
+ /** Show the message as its author wrote it: nothing adapted to the theme. */
18
+ original?: boolean;
19
+ /** Says whether "view original" would change anything, once that is known: never for plain text, a message with its own dark styles, or a
20
+ * message the theme leaves as it is. */
21
+ onAdaptable?: (adaptable: boolean) => void;
22
+ }
23
+ /** Placeholder lines shown where the body will be, in the body's own place, until it has arrived and been laid out. */
24
+ export declare function BodySkeleton(): React.JSX.Element;
25
+ /**
26
+ * The body of one message, in the card's own place: the sanitized HTML in a frame that is exactly as tall as its content (following it as
27
+ * images load and the width changes), or plain text. See `frameDocument.ts` for the isolation and `themeAdaptation.ts` for how a message
28
+ * takes the app's theme.
29
+ *
30
+ * Nothing waits on the network to lay out: the skeleton is here at once, and the frame's height is only ever the height of what is in it.
31
+ */
32
+ export default function MessageBody({ messageUid, messageVersion, title, content, attachments, inlineParts, original, onAdaptable }: MessageBodyProps): React.JSX.Element;
@@ -0,0 +1,136 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ ///////////////////////////////////////////////////////////////////////////////
3
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
4
+ // SPDX-License-Identifier: MPL-2.0
5
+ ///////////////////////////////////////////////////////////////////////////////
6
+ import { useEffect, useMemo, useRef, useState } from "react";
7
+ import { ApiRequestError, apiUrl } from "@rapidmx/react-shared/util/api.js";
8
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
9
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
10
+ import Skeleton from "@rapidmx/react-shared/components/feedback/Skeleton.js";
11
+ import { useResolvedTheme } from "../../../appearance/resolvedTheme.js";
12
+ import { attachmentSource, cachedBodyContent, fetchBodyContent, makeCidResolver } from "./bodyContent.js";
13
+ import { prepareBodyHtml } from "./bodyHtml.js";
14
+ import { formatColour } from "./color.js";
15
+ import { controlFrame } from "./frameControl.js";
16
+ import { FRAME_SANDBOX, buildFrameDocument } from "./frameDocument.js";
17
+ import { surfaceKey, useThemeSurface } from "./themeSurface.js";
18
+ /** Placeholder lines shown where the body will be, in the body's own place, until it has arrived and been laid out. */
19
+ export function BodySkeleton() {
20
+ return (_jsxs("div", { role: "status", "aria-busy": "true", className: "flex flex-col gap-2.5 py-1", children: [_jsx("span", { className: "sr-only", children: "Loading the message" }), _jsx(Skeleton, { width: "w-11/12" }), _jsx(Skeleton, { width: "w-4/5" }), _jsx(Skeleton, { width: "w-9/12" }), _jsx(Skeleton, { width: "w-1/2" })] }));
21
+ }
22
+ /** A short, stable fingerprint of a document's text (djb2), to tell one loaded document from the next. */
23
+ function fingerprint(text) {
24
+ let hash = 5381;
25
+ for (let i = 0; i < text.length; i++) {
26
+ hash = ((hash << 5) + hash + text.charCodeAt(i)) | 0;
27
+ }
28
+ return (hash >>> 0).toString(36);
29
+ }
30
+ /** The background of a message shown as authored: white, the canvas an HTML message is written for. */
31
+ const AUTHORED_CANVAS = "rgb(255, 255, 255)";
32
+ /**
33
+ * The body of one message, in the card's own place: the sanitized HTML in a frame that is exactly as tall as its content (following it as
34
+ * images load and the width changes), or plain text. See `frameDocument.ts` for the isolation and `themeAdaptation.ts` for how a message
35
+ * takes the app's theme.
36
+ *
37
+ * Nothing waits on the network to lay out: the skeleton is here at once, and the frame's height is only ever the height of what is in it.
38
+ */
39
+ export default function MessageBody({ messageUid, messageVersion, title, content, attachments, inlineParts, original = false, onAdaptable }) {
40
+ const [remote, setRemote] = useState(() => {
41
+ const cached = cachedBodyContent(messageUid, messageVersion);
42
+ return cached ? { status: "ready", content: cached } : { status: "loading" };
43
+ });
44
+ const [attempt, setAttempt] = useState(0);
45
+ const supplied = content !== undefined;
46
+ useEffect(() => {
47
+ if (supplied) {
48
+ return;
49
+ }
50
+ const cached = cachedBodyContent(messageUid, messageVersion);
51
+ if (cached) {
52
+ setRemote({ status: "ready", content: cached });
53
+ return;
54
+ }
55
+ const controller = new AbortController();
56
+ setRemote({ status: "loading" });
57
+ fetchBodyContent(messageUid, messageVersion, controller.signal).then((loaded) => setRemote({ status: "ready", content: loaded }), (err) => {
58
+ if (!controller.signal.aborted) {
59
+ setRemote({ status: "error", message: err instanceof ApiRequestError ? err.message : "Could not load this message." });
60
+ }
61
+ });
62
+ return () => controller.abort();
63
+ }, [messageUid, messageVersion, supplied, attempt]);
64
+ const shown = content ? { status: "ready", content } : remote;
65
+ if (shown.status === "loading") {
66
+ return _jsx(BodySkeleton, {});
67
+ }
68
+ if (shown.status === "error") {
69
+ return (_jsxs("div", { className: "flex flex-col items-start gap-2", children: [_jsx(Alert, { children: shown.message }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setAttempt((n) => n + 1), children: "Try again" })] }));
70
+ }
71
+ if (shown.content.kind === "text") {
72
+ // Rendered as text - React escapes it - never as markup, in the theme's own colours.
73
+ return _jsx("pre", { "aria-label": title, className: "m-0 text-sm font-sans whitespace-pre-wrap break-words text-text", children: shown.content.text });
74
+ }
75
+ return (_jsx(HtmlBody, { messageUid: messageUid, title: title, html: shown.content.html, remoteAvailable: !supplied, attachments: attachments, inlineParts: inlineParts, original: original, onAdaptable: onAdaptable }, messageUid));
76
+ }
77
+ function HtmlBody({ messageUid, title, html, remoteAvailable, attachments, inlineParts, original, onAdaptable, }) {
78
+ const theme = useResolvedTheme();
79
+ const surface = useThemeSurface(theme);
80
+ // Keyed on what the resolver reads rather than on the arrays, which a caller may rebuild on every render.
81
+ const inlineKey = [...(attachments ?? []).map((a) => `${a.uid}:${a.contentId ?? ""}`), inlineParts ? `parts:${inlineParts.length}` : ""].join("|");
82
+ const resolveCid = useMemo(() => makeCidResolver(attachments, inlineParts), [inlineKey]);
83
+ const prepared = useMemo(() => prepareBodyHtml(html, { resolveCid }), [html, resolveCid]);
84
+ const mode = prepared.declaresDarkSupport ? "native" : original ? "original" : "adapt";
85
+ const surfaceCss = formatColour(surface.background);
86
+ const srcDoc = useMemo(() => {
87
+ // The frame may load images from this server's attachment URLs only when an inline image was actually resolved to one.
88
+ const source = prepared.inlineImages > 0 ? attachmentSource() : undefined;
89
+ return buildFrameDocument(prepared.html, {
90
+ mode,
91
+ scheme: theme,
92
+ surface: surfaceCss,
93
+ text: formatColour(surface.text),
94
+ attachmentSource: source && prepared.html.includes(source) ? source : undefined,
95
+ });
96
+ }, [prepared, mode, theme, surfaceCss, surface.text]);
97
+ // A different body, surface or mode is a different document: the adaptation pass edits the document it ran on, so it is run on a fresh
98
+ // one, and the old one's readiness is not the new one's.
99
+ const frameKey = useMemo(() => `${mode}:${surfaceKey(surface)}:${fingerprint(srcDoc)}`, [mode, surface, srcDoc]);
100
+ const [height, setHeight] = useState(undefined);
101
+ const [readyFor, setReadyFor] = useState(null);
102
+ const [canvasAuthored, setCanvasAuthored] = useState(false);
103
+ const dispose = useRef(undefined);
104
+ useEffect(() => () => dispose.current?.(), []);
105
+ const reportAdaptable = useRef(onAdaptable);
106
+ reportAdaptable.current = onAdaptable;
107
+ useEffect(() => {
108
+ if (mode === "native") {
109
+ reportAdaptable.current?.(false);
110
+ }
111
+ }, [mode]);
112
+ if (prepared.status === "too_large") {
113
+ return (_jsxs(Alert, { children: ["This message is too large to show here.", " ", remoteAvailable && (_jsx("a", { href: apiUrl(`/mail/messages/${encodeURIComponent(messageUid)}/content`), target: "_blank", rel: "noopener noreferrer", className: "font-medium underline", children: "Open it in its own tab" }))] }));
114
+ }
115
+ const ready = readyFor === frameKey;
116
+ const canvas = mode === "original" ? AUTHORED_CANVAS : surfaceCss;
117
+ const framed = mode !== "adapt" || canvasAuthored;
118
+ return (_jsxs("div", { className: "relative rounded-md overflow-hidden", style: { backgroundColor: canvas }, children: [!ready && _jsx(BodySkeleton, {}), _jsx("iframe", { title: title, srcDoc: srcDoc, sandbox: FRAME_SANDBOX, scrolling: "no", onLoad: (event) => {
119
+ dispose.current?.();
120
+ dispose.current = controlFrame(event.currentTarget, {
121
+ surface: mode === "adapt" ? surface : undefined,
122
+ onHeight: setHeight,
123
+ onReady: (adaptation) => {
124
+ setReadyFor(frameKey);
125
+ if (adaptation) {
126
+ setCanvasAuthored(adaptation.authoredCanvas);
127
+ reportAdaptable.current?.(adaptation.material);
128
+ }
129
+ },
130
+ });
131
+ }, className: [
132
+ "block w-full border-0 rounded-md",
133
+ ready ? "" : "absolute inset-x-0 top-0 invisible",
134
+ framed && ready ? "ring-1 ring-border" : "",
135
+ ].join(" "), style: { height: height ?? 0 } }, frameKey)] }));
136
+ }
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import type { MailAddressLike } from "@rapidmx/react-shared/mail/mailAddress.js";
3
+ /**
4
+ * The pieces the reading pane is drawn from: a header card that carries the subject, and one card per message. Built from the app's own
5
+ * tokens only (`bg-surface`, `text-text`, `border-border`), so they follow the theme, a branding palette and the Appearance colours; the
6
+ * body area inside a card paints its own opaque surface (`MessageBody`), so a translucent card over a background photo stays legible.
7
+ */
8
+ /** The header-style card at the top of the pane: the conversation's (or message's) subject, and what goes with it. */
9
+ export declare function SubjectCard({ subject, meta, children, }: {
10
+ subject: string;
11
+ /** A line under the subject, such as "3 messages". */
12
+ meta?: React.ReactNode;
13
+ children?: React.ReactNode;
14
+ }): React.JSX.Element;
15
+ /** One message's card: rounded, bordered, lifted a little, its content clipped to the corners. The accent bar of an unread message runs down its left edge. */
16
+ export declare function CardShell({ unread, className, children, }: {
17
+ unread?: boolean;
18
+ className?: string;
19
+ children: React.ReactNode;
20
+ }): React.JSX.Element;
21
+ /**
22
+ * Headings carry the display typeface globally (`app.css`, unlayered - so no utility class can beat it); a sender line that happens to be a
23
+ * heading is body text and keeps the body's, by inline style.
24
+ */
25
+ export declare const BODY_FONT_STYLE: React.CSSProperties;
26
+ /** A sender's avatar in the card header. There are no photos in the data, so this is the initials disc every contact gets. */
27
+ export declare function SenderAvatar({ from }: {
28
+ from: MailAddressLike;
29
+ }): React.JSX.Element;
30
+ /**
31
+ * A collapsed message: its card with the sender, the date and the first line of the body, all of it the one button that expands it.
32
+ * Shares the header's look with an expanded card, so expanding does not change how the message begins.
33
+ */
34
+ export declare function CollapsedCard({ from, date, preview, unread, buttonProps, buttonRef, senderClassName, dateClassName, }: {
35
+ from: MailAddressLike;
36
+ date: string;
37
+ preview: React.ReactNode;
38
+ unread: boolean;
39
+ buttonProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
40
+ buttonRef: (node: HTMLButtonElement | null) => void;
41
+ senderClassName: string;
42
+ dateClassName: string;
43
+ }): React.JSX.Element;
44
+ /** The card's place-holder while its message is still loading: an avatar, two header lines and a few lines of body. */
45
+ export declare function CardSkeleton(): React.JSX.Element;
46
+ /**
47
+ * What the pane shows while it is getting ready - its code loading, a conversation's messages arriving: the subject card as soon as the
48
+ * subject is known (`subject` is `undefined` when it is not, as on the mobile page before its message arrived) and a skeleton card per
49
+ * message, so the frame of the pane is on screen at once and the messages only fill in. One live region for the whole thing.
50
+ */
51
+ export declare function ReadingPaneSkeleton({ subject, messageCount }: {
52
+ subject?: string;
53
+ messageCount?: number;
54
+ }): React.JSX.Element;
55
+ /**
56
+ * The skeleton cards themselves - up to three, one live region ("Loading the message") for all of them - in the place the message cards take,
57
+ * so a pane that already shows its subject card (a thread whose messages are still coming) swaps them for the real ones without the header
58
+ * card being drawn again.
59
+ */
60
+ export declare function SkeletonCards({ messageCount }: {
61
+ messageCount?: number;
62
+ }): React.JSX.Element;
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import ContactAvatar from "@rapidmx/react-shared/components/avatar/ContactAvatar.js";
3
+ import Skeleton from "@rapidmx/react-shared/components/feedback/Skeleton.js";
4
+ import MailAddress from "../MailAddress.js";
5
+ import { UnreadBar, UnreadLabel } from "../unreadStyle.js";
6
+ /**
7
+ * The pieces the reading pane is drawn from: a header card that carries the subject, and one card per message. Built from the app's own
8
+ * tokens only (`bg-surface`, `text-text`, `border-border`), so they follow the theme, a branding palette and the Appearance colours; the
9
+ * body area inside a card paints its own opaque surface (`MessageBody`), so a translucent card over a background photo stays legible.
10
+ */
11
+ /** The header-style card at the top of the pane: the conversation's (or message's) subject, and what goes with it. */
12
+ export function SubjectCard({ subject, meta, children, }) {
13
+ return (_jsxs("header", { className: "shrink-0 mx-2 mt-2 sm:mx-3 sm:mt-3 rounded-lg border border-border bg-surface backdrop-blur-sm shadow-sm px-4 py-2.5", children: [_jsx("h1", { className: "text-base sm:text-lg font-bold tracking-tight line-clamp-2 break-words", children: subject }), meta && _jsx("p", { className: "text-xs sm:text-sm text-text-muted mt-0.5", children: meta }), children] }));
14
+ }
15
+ /** One message's card: rounded, bordered, lifted a little, its content clipped to the corners. The accent bar of an unread message runs down its left edge. */
16
+ export function CardShell({ unread = false, className = "", children, }) {
17
+ return (_jsxs("div", { className: ["relative rounded-lg border border-border bg-surface backdrop-blur-sm shadow-sm overflow-hidden", className].join(" "), children: [_jsx(UnreadBar, { unread: unread }), children] }));
18
+ }
19
+ /**
20
+ * Headings carry the display typeface globally (`app.css`, unlayered - so no utility class can beat it); a sender line that happens to be a
21
+ * heading is body text and keeps the body's, by inline style.
22
+ */
23
+ export const BODY_FONT_STYLE = { fontFamily: "var(--rr-font-family)" };
24
+ /** A sender's avatar in the card header. There are no photos in the data, so this is the initials disc every contact gets. */
25
+ export function SenderAvatar({ from }) {
26
+ return _jsx(ContactAvatar, { displayName: from.displayName || from.name || from.address, size: 40 });
27
+ }
28
+ /**
29
+ * A collapsed message: its card with the sender, the date and the first line of the body, all of it the one button that expands it.
30
+ * Shares the header's look with an expanded card, so expanding does not change how the message begins.
31
+ */
32
+ export function CollapsedCard({ from, date, preview, unread, buttonProps, buttonRef, senderClassName, dateClassName, }) {
33
+ return (_jsx(CardShell, { unread: unread, children: _jsx("h2", { style: BODY_FONT_STYLE, children: _jsxs("button", { type: "button", ref: buttonRef, ...buttonProps, className: [
34
+ "w-full text-left flex items-center gap-3 px-4 py-3",
35
+ unread ? "bg-primary/[0.07] hover:bg-primary/10" : "hover:bg-surface-alt",
36
+ "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary",
37
+ ].join(" "), children: [_jsx(UnreadLabel, { unread: unread }), _jsx(SenderAvatar, { from: from }), _jsxs("span", { className: "flex-1 min-w-0", children: [_jsxs("span", { className: "flex flex-wrap items-baseline justify-between gap-x-2 text-sm", children: [_jsx(MailAddress, { recipient: from, className: `${senderClassName} min-w-[13rem]! flex-1` }), _jsx("span", { className: ["text-xs shrink-0", dateClassName].join(" "), children: date })] }), _jsx("span", { className: "block text-xs text-text-muted truncate font-normal", children: preview })] })] }) }) }));
38
+ }
39
+ /** The card's place-holder while its message is still loading: an avatar, two header lines and a few lines of body. */
40
+ export function CardSkeleton() {
41
+ return (_jsxs("div", { "aria-hidden": "true", className: "rounded-lg border border-border bg-surface shadow-sm overflow-hidden", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [_jsx(Skeleton, { width: "w-10", height: "h-10", className: "rounded-full shrink-0" }), _jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [_jsx(Skeleton, { width: "w-1/3" }), _jsx(Skeleton, { width: "w-1/2", height: "h-3" })] }), _jsx(Skeleton, { width: "w-16", height: "h-3" })] }), _jsxs("div", { className: "flex flex-col gap-2.5 px-4 pb-4", children: [_jsx(Skeleton, { width: "w-11/12" }), _jsx(Skeleton, { width: "w-4/5" }), _jsx(Skeleton, { width: "w-9/12" })] })] }));
42
+ }
43
+ /**
44
+ * What the pane shows while it is getting ready - its code loading, a conversation's messages arriving: the subject card as soon as the
45
+ * subject is known (`subject` is `undefined` when it is not, as on the mobile page before its message arrived) and a skeleton card per
46
+ * message, so the frame of the pane is on screen at once and the messages only fill in. One live region for the whole thing.
47
+ */
48
+ export function ReadingPaneSkeleton({ subject, messageCount = 1 }) {
49
+ return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col", children: [subject !== undefined ? (_jsx(SubjectCard, { subject: subject || "(no subject)", meta: messageCount > 1 ? `${messageCount} messages` : undefined })) : (_jsx("div", { "aria-hidden": "true", className: "shrink-0 mx-2 mt-2 sm:mx-3 sm:mt-3 rounded-lg border border-border bg-surface shadow-sm px-4 py-3", children: _jsx(Skeleton, { width: "w-2/3", height: "h-6" }) })), _jsx(SkeletonCards, { messageCount: messageCount })] }));
50
+ }
51
+ /**
52
+ * The skeleton cards themselves - up to three, one live region ("Loading the message") for all of them - in the place the message cards take,
53
+ * so a pane that already shows its subject card (a thread whose messages are still coming) swaps them for the real ones without the header
54
+ * card being drawn again.
55
+ */
56
+ export function SkeletonCards({ messageCount = 1 }) {
57
+ return (_jsxs("div", { role: "status", "aria-busy": "true", className: "flex-1 min-h-0 overflow-y-auto p-2 sm:p-3 flex flex-col gap-3", children: [_jsx("span", { className: "sr-only", children: "Loading the message" }), Array.from({ length: Math.min(3, Math.max(1, messageCount)) }, (_, index) => (_jsx(CardSkeleton, {}, index)))] }));
58
+ }
@@ -0,0 +1,38 @@
1
+ import { Attachment } from "@rapidmx/react-shared/mail/mailApi.js";
2
+ import type { MimeAttachment } from "@rapidmx/react-shared/crypto/mime.js";
3
+ /** What a message's body is: server-sanitized HTML, or plain text (a message with no HTML part, or the server's plain-text fallback). */
4
+ export type BodyContent = {
5
+ kind: "html";
6
+ html: string;
7
+ } | {
8
+ kind: "text";
9
+ text: string;
10
+ };
11
+ /** What the pane asks `/content` for. */
12
+ export declare const ACCEPT = "text/html, text/plain;q=0.8";
13
+ /** Forgets every remembered body (sign-out, and the tests). */
14
+ export declare function clearBodyContentCache(): void;
15
+ /** The remembered body for a message at a version, if any. */
16
+ export declare function cachedBodyContent(uid: string, version: number): BodyContent | undefined;
17
+ /**
18
+ * The message's body from the server's `GET /mail/messages/:id/content`: the HTML the server sanitized when it ingested the message
19
+ * (`sanitizedHtmlBlobKey`), or - for a message with none - the plain-text preview. A request that fails throws an `ApiRequestError`.
20
+ * Successful answers are remembered per uid and version.
21
+ */
22
+ export declare function fetchBodyContent(uid: string, version: number, signal?: AbortSignal): Promise<BodyContent>;
23
+ /** The largest inline image embedded into a body from a signed or encrypted message. */
24
+ export declare const MAX_INLINE_IMAGE_BYTES = 5000000;
25
+ /** A server attachment record as it may really arrive: the model carries the part's `Content-ID`, though the client type doesn't list it. */
26
+ export type InlineCapable = Attachment & {
27
+ contentId?: string;
28
+ };
29
+ /** The absolute URL of an attachment - the frame is a document of its own, where a relative URL would not mean what it does here. */
30
+ export declare function absoluteAttachmentUrl(uid: string): string;
31
+ /** The CSP source that allows exactly this server's attachment URLs and nothing else. */
32
+ export declare function attachmentSource(): string;
33
+ /**
34
+ * How an inline `cid:` image is resolved to something the frame may load: for a message shown from the server, the attachment whose
35
+ * `Content-ID` matches (its URL on this server); for a decrypted or verified one, the part inside the signed/encrypted entity, embedded
36
+ * as a `data:` URI (raster and SVG images up to `MAX_INLINE_IMAGE_BYTES`). Anything else has no source, so the image is not shown.
37
+ */
38
+ export declare function makeCidResolver(attachments: InlineCapable[] | undefined, parts: MimeAttachment[] | undefined): (contentId: string) => string | undefined;