@bobfrankston/mailx 1.0.9 → 1.0.13

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 (246) hide show
  1. package/bin/mailx.js +47 -29
  2. package/bin/postinstall.js +41 -0
  3. package/client/app.js +93 -13
  4. package/client/components/folder-tree.js +84 -3
  5. package/client/components/message-list.js +134 -8
  6. package/client/components/message-viewer.js +130 -13
  7. package/client/compose/compose.html +4 -4
  8. package/client/compose/compose.js +53 -34
  9. package/client/index.html +33 -9
  10. package/client/lib/api-client.js +102 -30
  11. package/client/lib/mailxapi.js +123 -0
  12. package/client/package.json +5 -1
  13. package/client/styles/components.css +188 -15
  14. package/client/styles/layout.css +2 -1
  15. package/killmail.cmd +6 -0
  16. package/launch.ps1 +48 -3
  17. package/launcher/bin/mailx-app.exe +0 -0
  18. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Breadcrumbs +0 -0
  19. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/metadata +0 -0
  20. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
  21. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/throttle_store.dat +1 -0
  22. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/CrashpadMetrics-active.pma +0 -0
  23. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/BrowsingTopicsSiteData +0 -0
  24. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Cache/No_Vary_Search/journal.baj +1 -0
  25. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DIPS +0 -0
  26. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DashTrackerDatabase +0 -0
  27. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/EdgeJourneys/EdgeJourneys.db +0 -0
  28. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOCK +0 -0
  29. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOG +3 -0
  30. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/MANIFEST-000001 +0 -0
  31. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOCK +0 -0
  32. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOG +3 -0
  33. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/MANIFEST-000001 +0 -0
  34. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOCK +0 -0
  35. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -0
  36. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/MANIFEST-000001 +0 -0
  37. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityComp +0 -0
  38. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityEdge +0 -0
  39. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Favicons +0 -0
  40. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History +0 -0
  41. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History-journal +0 -0
  42. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOCK +0 -0
  43. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOG +3 -0
  44. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/MANIFEST-000001 +0 -0
  45. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOCK +0 -0
  46. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -0
  47. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/MANIFEST-000001 +0 -0
  48. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data +0 -0
  49. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data For Account +0 -0
  50. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Cookies +0 -0
  51. NEL +0 -0
  52. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Trust Tokens +0 -0
  53. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network Action Predictor +0 -0
  54. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Preferences +1 -0
  55. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Safe Browsing Network/Safe Browsing Cookies +0 -0
  56. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ServerCertificate +0 -0
  57. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOCK +0 -0
  58. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -0
  59. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/MANIFEST-000001 +0 -0
  60. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shared Dictionary/db +0 -0
  61. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/SharedStorage +0 -0
  62. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shortcuts +0 -0
  63. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOCK +0 -0
  64. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -0
  65. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/MANIFEST-000001 +0 -0
  66. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOCK +0 -0
  67. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -0
  68. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/MANIFEST-000001 +0 -0
  69. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Top Sites +0 -0
  70. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Vpn Tokens +0 -0
  71. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data +0 -0
  72. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data-journal +0 -0
  73. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
  74. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
  75. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/heavy_ad_intervention_opt_out.db +0 -0
  76. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOCK +0 -0
  77. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -0
  78. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/MANIFEST-000001 +0 -0
  79. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOCK +0 -0
  80. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -0
  81. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/MANIFEST-000001 +0 -0
  82. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/DeferredBrowserMetrics/BrowserMetrics-69CAD063-BE24.pma +0 -0
  83. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Local State +1 -0
  84. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Variations +1 -0
  85. package/launcher/bin/mailx-app.old.exe +0 -0
  86. package/launcher/mailx.ico +0 -0
  87. package/npmg.bat +6 -0
  88. package/package.json +9 -3
  89. package/packages/mailx-api/index.js +79 -26
  90. package/packages/mailx-api/package.json +5 -1
  91. package/packages/mailx-compose/package.json +5 -1
  92. package/packages/mailx-core/index.d.ts +129 -0
  93. package/packages/mailx-core/index.js +323 -0
  94. package/packages/mailx-core/ipc.d.ts +13 -0
  95. package/packages/mailx-core/ipc.js +56 -0
  96. package/packages/mailx-core/package.json +18 -0
  97. package/packages/mailx-imap/index.d.ts +5 -1
  98. package/packages/mailx-imap/index.js +76 -14
  99. package/packages/mailx-imap/package.json +5 -1
  100. package/packages/mailx-send/package.json +12 -2
  101. package/packages/mailx-server/index.js +42 -31
  102. package/packages/mailx-server/package.json +5 -2
  103. package/packages/mailx-settings/index.d.ts +1 -1
  104. package/packages/mailx-settings/index.js +21 -12
  105. package/packages/mailx-settings/package.json +5 -1
  106. package/packages/mailx-store/db.d.ts +5 -1
  107. package/packages/mailx-store/db.js +64 -12
  108. package/packages/mailx-store/file-store.d.ts +2 -8
  109. package/packages/mailx-store/file-store.js +7 -31
  110. package/packages/mailx-store/package.json +5 -1
  111. package/packages/mailx-types/index.d.ts +3 -1
  112. package/packages/mailx-types/package.json +6 -2
  113. package/.tswalk.json +0 -3366
  114. package/mailx.json +0 -9
  115. package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
  116. package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
  117. package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
  118. package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
  119. package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
  120. package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
  121. package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
  122. package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
  123. package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
  124. package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  125. package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
  126. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
  127. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  128. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  129. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  130. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  131. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
  132. package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  133. package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  134. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
  135. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  136. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  137. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  138. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  139. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  140. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  141. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  142. package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
  143. package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
  144. package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
  145. package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  146. package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  147. package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
  148. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  149. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  150. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  151. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  152. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  153. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  154. package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  155. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
  156. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
  157. package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  158. package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
  159. package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
  160. package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
  161. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
  162. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
  163. package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
  164. package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
  165. package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
  166. package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
  167. package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
  168. package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  169. package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
  170. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
  171. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  172. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  173. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  174. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  175. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
  176. package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  177. package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  178. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
  179. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  180. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  181. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  182. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  183. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  184. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  185. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  186. package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
  187. package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
  188. package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
  189. package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  190. package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  191. package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
  192. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  193. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  194. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  195. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  196. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  197. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  198. package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  199. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
  200. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
  201. package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  202. package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
  203. package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
  204. package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
  205. package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
  206. package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
  207. package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
  208. package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
  209. package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
  210. package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
  211. package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
  212. package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  213. package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
  214. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
  215. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  216. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  217. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  218. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  219. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
  220. package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  221. package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  222. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
  223. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  224. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  225. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  226. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  227. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  228. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  229. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  230. package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
  231. package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
  232. package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
  233. package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  234. package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  235. package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
  236. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  237. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  238. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  239. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  240. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  241. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  242. package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  243. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
  244. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
  245. package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  246. package/packages/mailx-send/node_modules/nodemailer/package.json +0 -47
@@ -3,10 +3,24 @@
3
3
  * Loads more messages on scroll.
4
4
  */
5
5
  import { getMessages, getUnifiedInbox, searchMessages } from "../lib/api-client.js";
6
+ function clearFilter() {
7
+ const f = document.getElementById("ml-filter-input");
8
+ if (f)
9
+ f.value = "";
10
+ }
11
+ /** Clear the message viewer when no message is selected */
12
+ function clearViewer() {
13
+ const bodyEl = document.getElementById("mv-body");
14
+ const headerEl = document.getElementById("mv-header");
15
+ if (bodyEl)
16
+ bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
17
+ if (headerEl)
18
+ headerEl.hidden = true;
19
+ }
6
20
  let onMessageSelect;
7
21
  let currentAccountId;
8
22
  let currentFolderId;
9
- let selectedRow;
23
+ let lastClickedRow = null;
10
24
  let currentPage;
11
25
  let totalMessages;
12
26
  let loading = false;
@@ -14,11 +28,62 @@ let unifiedMode = false;
14
28
  let searchMode = false;
15
29
  let currentSearchQuery = "";
16
30
  let showToInsteadOfFrom = false;
31
+ /** Get all selected message rows */
32
+ export function getSelectedMessages() {
33
+ const body = document.getElementById("ml-body");
34
+ if (!body)
35
+ return [];
36
+ const rows = body.querySelectorAll(".ml-row.selected");
37
+ return Array.from(rows).map(r => ({
38
+ accountId: r.dataset.accountId || "",
39
+ uid: Number(r.dataset.uid),
40
+ folderId: Number(r.dataset.folderId),
41
+ }));
42
+ }
43
+ function clearSelection() {
44
+ const body = document.getElementById("ml-body");
45
+ if (body)
46
+ body.querySelectorAll(".ml-row.selected").forEach(r => r.classList.remove("selected"));
47
+ }
48
+ function selectRange(from, to) {
49
+ const body = document.getElementById("ml-body");
50
+ if (!body)
51
+ return;
52
+ const rows = Array.from(body.querySelectorAll(".ml-row"));
53
+ const fromIdx = rows.indexOf(from);
54
+ const toIdx = rows.indexOf(to);
55
+ if (fromIdx < 0 || toIdx < 0)
56
+ return;
57
+ const lo = Math.min(fromIdx, toIdx);
58
+ const hi = Math.max(fromIdx, toIdx);
59
+ for (let i = lo; i <= hi; i++)
60
+ rows[i].classList.add("selected");
61
+ }
17
62
  const timeFmt = { hour: "2-digit", minute: "2-digit", hour12: false };
18
63
  const dateFmt = { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false };
19
64
  const dateFmtSameYear = { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false };
20
65
  export function initMessageList(handler) {
21
66
  onMessageSelect = handler;
67
+ // Client-side filter
68
+ const filterInput = document.getElementById("ml-filter-input");
69
+ if (filterInput) {
70
+ filterInput.addEventListener("input", () => {
71
+ const query = filterInput.value.toLowerCase();
72
+ const body = document.getElementById("ml-body");
73
+ if (!body)
74
+ return;
75
+ for (const row of body.querySelectorAll(".ml-row")) {
76
+ const text = row.textContent?.toLowerCase() || "";
77
+ row.classList.toggle("filter-hidden", query.length > 0 && !text.includes(query));
78
+ }
79
+ });
80
+ filterInput.addEventListener("keydown", (e) => {
81
+ if (e.key === "Escape") {
82
+ filterInput.value = "";
83
+ filterInput.dispatchEvent(new Event("input"));
84
+ }
85
+ });
86
+ }
22
87
  // Infinite scroll
23
88
  const body = document.getElementById("ml-body");
24
89
  if (body) {
@@ -46,6 +111,7 @@ export function reloadCurrentFolder() {
46
111
  }
47
112
  /** Load unified inbox (all accounts) */
48
113
  export async function loadUnifiedInbox() {
114
+ clearFilter();
49
115
  unifiedMode = true;
50
116
  currentPage = 1;
51
117
  totalMessages = 0;
@@ -58,17 +124,24 @@ export async function loadUnifiedInbox() {
58
124
  totalMessages = result.total;
59
125
  if (result.items.length === 0) {
60
126
  body.innerHTML = `<div class="ml-empty">No messages</div>`;
127
+ clearViewer();
61
128
  return;
62
129
  }
63
130
  body.innerHTML = "";
64
131
  appendMessages(body, "", result.items);
132
+ const firstRow = body.querySelector(".ml-row");
133
+ if (firstRow)
134
+ firstRow.click();
65
135
  }
66
136
  catch (e) {
137
+ if (e.name === "AbortError")
138
+ return;
67
139
  body.innerHTML = `<div class="ml-empty">Error: ${e.message}</div>`;
68
140
  }
69
141
  }
70
142
  /** Load search results */
71
143
  export async function loadSearchResults(query) {
144
+ clearFilter();
72
145
  searchMode = true;
73
146
  unifiedMode = false;
74
147
  currentSearchQuery = query;
@@ -93,6 +166,7 @@ export async function loadSearchResults(query) {
93
166
  }
94
167
  }
95
168
  export async function loadMessages(accountId, folderId, page = 1, specialUse = "") {
169
+ clearFilter();
96
170
  searchMode = false;
97
171
  unifiedMode = false;
98
172
  showToInsteadOfFrom = ["sent", "drafts", "outbox"].includes(specialUse) ||
@@ -114,12 +188,19 @@ export async function loadMessages(accountId, folderId, page = 1, specialUse = "
114
188
  totalMessages = result.total;
115
189
  if (result.items.length === 0) {
116
190
  body.innerHTML = `<div class="ml-empty">No messages</div>`;
191
+ clearViewer();
117
192
  return;
118
193
  }
119
194
  body.innerHTML = "";
120
195
  appendMessages(body, accountId, result.items);
196
+ // Auto-select first message
197
+ const firstRow = body.querySelector(".ml-row");
198
+ if (firstRow)
199
+ firstRow.click();
121
200
  }
122
201
  catch (e) {
202
+ if (e.name === "AbortError")
203
+ return; // Superseded by newer request
123
204
  body.innerHTML = `<div class="ml-empty">Error: ${e.message}</div>`;
124
205
  }
125
206
  }
@@ -146,13 +227,18 @@ async function loadMoreMessages() {
146
227
  }
147
228
  function appendMessages(body, accountId, items) {
148
229
  for (const msg of items) {
230
+ // Use per-message accountId for unified inbox, fallback to list-level accountId
231
+ const msgAccountId = msg.accountId || accountId;
149
232
  const row = document.createElement("div");
150
233
  row.className = "ml-row";
234
+ row.draggable = true;
151
235
  if (!msg.flags.includes("\\Seen"))
152
236
  row.classList.add("unread");
153
237
  if (msg.flags.includes("\\Flagged"))
154
238
  row.classList.add("flagged");
155
239
  row.dataset.uid = String(msg.uid);
240
+ row.dataset.accountId = msgAccountId;
241
+ row.dataset.folderId = String(msg.folderId);
156
242
  const flag = document.createElement("span");
157
243
  flag.className = "ml-flag";
158
244
  flag.textContent = msg.flags.includes("\\Flagged") ? "\u2605" : "\u2606"; // ★ or ☆
@@ -185,7 +271,7 @@ function appendMessages(body, accountId, items) {
185
271
  ? currentFlags.filter((f) => f !== "\\Flagged")
186
272
  : [...currentFlags, "\\Flagged"];
187
273
  try {
188
- await fetch(`/api/message/${accountId}/${msg.uid}/flags`, {
274
+ await fetch(`/api/message/${msgAccountId}/${msg.uid}/flags`, {
189
275
  method: "PATCH",
190
276
  headers: { "Content-Type": "application/json" },
191
277
  body: JSON.stringify({ flags: newFlags }),
@@ -200,14 +286,54 @@ function appendMessages(body, accountId, items) {
200
286
  row.appendChild(from);
201
287
  row.appendChild(date);
202
288
  row.appendChild(subject);
203
- row.addEventListener("click", () => {
204
- if (selectedRow)
205
- selectedRow.classList.remove("selected");
206
- row.classList.add("selected");
207
- selectedRow = row;
289
+ row.addEventListener("click", (e) => {
290
+ if (e.shiftKey && lastClickedRow) {
291
+ // Shift+click: range select from last clicked to this row
292
+ clearSelection();
293
+ selectRange(lastClickedRow, row);
294
+ }
295
+ else if (e.ctrlKey || e.metaKey) {
296
+ // Ctrl+click: toggle this row
297
+ row.classList.toggle("selected");
298
+ }
299
+ else {
300
+ // Plain click: single select
301
+ clearSelection();
302
+ row.classList.add("selected");
303
+ }
304
+ lastClickedRow = row;
208
305
  row.classList.remove("unread");
209
- onMessageSelect(accountId, msg.uid);
306
+ onMessageSelect(msgAccountId, msg.uid, msg.folderId);
307
+ });
308
+ row.addEventListener("dragstart", (e) => {
309
+ // If dragging a non-selected row, select it first
310
+ if (!row.classList.contains("selected")) {
311
+ clearSelection();
312
+ row.classList.add("selected");
313
+ lastClickedRow = row;
314
+ }
315
+ const selected = getSelectedMessages();
316
+ e.dataTransfer.setData("application/x-mailx-messages", JSON.stringify(selected));
317
+ // Legacy single-message format for backwards compat
318
+ e.dataTransfer.setData("application/x-mailx-message", JSON.stringify({
319
+ accountId: msgAccountId,
320
+ uid: msg.uid,
321
+ folderId: msg.folderId,
322
+ subject: msg.subject,
323
+ }));
324
+ e.dataTransfer.effectAllowed = "copyMove";
325
+ row.classList.add("dragging");
326
+ // Show drag count
327
+ if (selected.length > 1) {
328
+ const badge = document.createElement("div");
329
+ badge.textContent = `${selected.length} messages`;
330
+ badge.style.cssText = "position:absolute;top:-1000px;background:#333;color:white;padding:4px 8px;border-radius:4px;font-size:12px";
331
+ document.body.appendChild(badge);
332
+ e.dataTransfer.setDragImage(badge, 0, 0);
333
+ setTimeout(() => badge.remove(), 0);
334
+ }
210
335
  });
336
+ row.addEventListener("dragend", () => row.classList.remove("dragging"));
211
337
  body.appendChild(row);
212
338
  }
213
339
  }
@@ -5,12 +5,14 @@ import { getMessage, updateFlags } from "../lib/api-client.js";
5
5
  /** Currently displayed message (for reply/forward) */
6
6
  let currentMessage = null;
7
7
  let currentAccountId = "";
8
+ let showMessageGeneration = 0; // Cancel stale fetches
8
9
  export function getCurrentMessage() {
9
10
  if (!currentMessage)
10
11
  return null;
11
12
  return { accountId: currentAccountId, message: currentMessage };
12
13
  }
13
- export async function showMessage(accountId, uid) {
14
+ export async function showMessage(accountId, uid, folderId, specialUse) {
15
+ const gen = ++showMessageGeneration;
14
16
  const headerEl = document.getElementById("mv-header");
15
17
  const bodyEl = document.getElementById("mv-body");
16
18
  const attEl = document.getElementById("mv-attachments");
@@ -18,9 +20,10 @@ export async function showMessage(accountId, uid) {
18
20
  headerEl.hidden = true;
19
21
  attEl.hidden = true;
20
22
  try {
21
- // Timeout after 15 seconds
22
- const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout loading message")), 15000));
23
- const msg = await Promise.race([getMessage(accountId, uid), timeout]);
23
+ const msg = await getMessage(accountId, uid, false, folderId);
24
+ // Stale response a newer showMessage was called while we were fetching
25
+ if (gen !== showMessageGeneration)
26
+ return;
24
27
  currentMessage = msg;
25
28
  currentAccountId = accountId;
26
29
  // Mark as read
@@ -37,18 +40,114 @@ export async function showMessage(accountId, uid) {
37
40
  }
38
41
  headerEl.querySelector(".mv-subject").textContent = msg.subject;
39
42
  headerEl.querySelector(".mv-date").textContent = new Date(msg.date).toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false });
40
- // Remote content banner
43
+ // Unsubscribe button (upper right of header)
44
+ const unsubBtn = document.getElementById("mv-unsubscribe");
45
+ const headerUnsub = msg.listUnsubscribe || "";
46
+ const headerUnsubUrl = headerUnsub.match(/<(https?:\/\/[^>]+)>/)?.[1]
47
+ || headerUnsub.match(/<(mailto:[^>]+)>/)?.[1] || "";
48
+ if (unsubBtn) {
49
+ if (headerUnsubUrl) {
50
+ unsubBtn.hidden = false;
51
+ unsubBtn.href = headerUnsubUrl;
52
+ unsubBtn.target = "_blank";
53
+ unsubBtn.rel = "noopener noreferrer";
54
+ }
55
+ else {
56
+ unsubBtn.hidden = true;
57
+ }
58
+ }
59
+ // View Source button — shows .eml file path
60
+ const srcBtn = document.getElementById("mv-view-source");
61
+ if (srcBtn) {
62
+ if (msg.emlPath) {
63
+ srcBtn.hidden = false;
64
+ srcBtn.title = msg.emlPath;
65
+ srcBtn.onclick = () => {
66
+ // Copy path to clipboard and show in status bar
67
+ navigator.clipboard.writeText(msg.emlPath).then(() => {
68
+ const status = document.getElementById("status-sync");
69
+ if (status)
70
+ status.textContent = `Path copied: ${msg.emlPath}`;
71
+ }).catch(() => {
72
+ prompt("EML file path:", msg.emlPath);
73
+ });
74
+ };
75
+ }
76
+ else {
77
+ srcBtn.hidden = true;
78
+ }
79
+ }
80
+ // Edit Draft / Send from Outbox button
81
+ const editBtn = document.getElementById("mv-edit-draft");
82
+ if (editBtn) {
83
+ const isDraft = specialUse === "drafts" || specialUse === "outbox";
84
+ if (isDraft) {
85
+ editBtn.hidden = false;
86
+ editBtn.textContent = specialUse === "outbox" ? "Edit & Send" : "Edit Draft";
87
+ editBtn.onclick = () => {
88
+ // Open compose window pre-filled with this draft
89
+ const init = {
90
+ mode: "draft",
91
+ accountId,
92
+ to: msg.to || [],
93
+ cc: msg.cc || [],
94
+ subject: msg.subject || "",
95
+ bodyHtml: msg.bodyHtml || "",
96
+ inReplyTo: msg.inReplyTo || "",
97
+ references: msg.references || [],
98
+ accounts: [],
99
+ draftUid: msg.uid,
100
+ draftFolderId: msg.folderId,
101
+ };
102
+ sessionStorage.setItem("composeInit", JSON.stringify(init));
103
+ window.open("/compose/compose.html", "_blank", "width=800,height=600,menubar=no,toolbar=no,status=no");
104
+ };
105
+ }
106
+ else {
107
+ editBtn.hidden = true;
108
+ }
109
+ }
110
+ // Remote content banner (collapsible dropdown with sender/recipient details)
41
111
  bodyEl.innerHTML = "";
42
112
  if (msg.hasRemoteContent) {
43
113
  const senderAddr = msg.from?.address || "";
114
+ const senderName = msg.from?.name || "";
44
115
  const senderDomain = senderAddr.split("@")[1] || "";
116
+ const deliveredTo = msg.deliveredTo || "";
117
+ const toAddr = msg.to?.[0]?.address || "";
118
+ const returnPath = msg.returnPath || "";
45
119
  const banner = document.createElement("div");
46
120
  banner.className = "mv-remote-banner";
47
- banner.innerHTML = `Remote content blocked from <b>${senderAddr}</b>. ` +
48
- `<button id="btn-load-remote">Load this time</button> ` +
49
- `<button id="btn-allow-sender">Always from ${senderAddr}</button> ` +
50
- (senderDomain ? `<button id="btn-allow-domain">Always from ${senderDomain}</button>` : "");
121
+ banner.innerHTML =
122
+ `<div class="mv-rb-summary">` +
123
+ `<span class="mv-rb-toggle">&#x25B8;</span>` +
124
+ `<span>Remote content blocked</span>` +
125
+ `<span class="mv-rb-buttons">` +
126
+ `<button id="btn-load-remote">Load once</button>` +
127
+ `<button id="btn-allow-sender" title="${escapeText(senderAddr)}">Always: ${escapeText(senderAddr)}</button>` +
128
+ (senderDomain ? `<button id="btn-allow-domain" title="*@${escapeText(senderDomain)}">Always: *@${escapeText(senderDomain)}</button>` : "") +
129
+ `</span>` +
130
+ `</div>` +
131
+ `<div class="mv-rb-details" hidden>` +
132
+ `<div class="mv-rb-info">` +
133
+ `<div><span class="mv-rb-label">From:</span> ${escapeText(senderName ? `${senderName} <${senderAddr}>` : senderAddr)}</div>` +
134
+ (deliveredTo ? `<div><span class="mv-rb-label">Delivered-To:</span> ${escapeText(deliveredTo)}</div>` : "") +
135
+ (toAddr && toAddr !== deliveredTo ? `<div><span class="mv-rb-label">To:</span> ${escapeText(toAddr)}</div>` : "") +
136
+ (returnPath && returnPath !== senderAddr ? `<div><span class="mv-rb-label">Return-Path:</span> ${escapeText(returnPath)}</div>` : "") +
137
+ `</div>` +
138
+ (deliveredTo || toAddr ? `<div class="mv-rb-actions"><button id="btn-allow-to">Always allow to: ${escapeText(deliveredTo || toAddr)}</button></div>` : "") +
139
+ `</div>`;
51
140
  bodyEl.appendChild(banner);
141
+ // Toggle dropdown — click arrow or text to expand details
142
+ const summary = banner.querySelector(".mv-rb-summary");
143
+ const details = banner.querySelector(".mv-rb-details");
144
+ const toggle = banner.querySelector(".mv-rb-toggle");
145
+ summary.addEventListener("click", (e) => {
146
+ if (e.target.tagName === "BUTTON")
147
+ return;
148
+ details.hidden = !details.hidden;
149
+ toggle.textContent = details.hidden ? "\u25B8" : "\u25BE";
150
+ });
52
151
  const loadRemote = async () => {
53
152
  banner.remove();
54
153
  const full = await getMessage(accountId, uid, true);
@@ -78,6 +177,17 @@ export async function showMessage(accountId, uid) {
78
177
  });
79
178
  loadRemote();
80
179
  });
180
+ banner.querySelector("#btn-allow-to")?.addEventListener("click", async () => {
181
+ const addr = deliveredTo || toAddr;
182
+ if (!addr)
183
+ return;
184
+ await fetch("/api/settings/allow-remote", {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ body: JSON.stringify({ type: "recipient", value: addr }),
188
+ });
189
+ loadRemote();
190
+ });
81
191
  }
82
192
  // Body in sandboxed iframe
83
193
  if (msg.bodyHtml) {
@@ -85,7 +195,8 @@ export async function showMessage(accountId, uid) {
85
195
  iframe.sandbox.add("allow-same-origin");
86
196
  iframe.sandbox.add("allow-popups");
87
197
  iframe.sandbox.add("allow-popups-to-escape-sandbox");
88
- iframe.srcdoc = wrapHtmlBody(msg.bodyHtml);
198
+ iframe.sandbox.add("allow-top-navigation-by-user-activation");
199
+ iframe.srcdoc = wrapHtmlBody(msg.bodyHtml, msg.remoteAllowed);
89
200
  bodyEl.appendChild(iframe);
90
201
  }
91
202
  else if (msg.bodyText) {
@@ -118,6 +229,11 @@ function formatAddr(addr) {
118
229
  return `${addr.name} <${addr.address}>`;
119
230
  return addr.address;
120
231
  }
232
+ function escapeText(s) {
233
+ const div = document.createElement("div");
234
+ div.textContent = s;
235
+ return div.innerHTML;
236
+ }
121
237
  function formatSize(bytes) {
122
238
  if (bytes < 1024)
123
239
  return `${bytes} B`;
@@ -126,9 +242,10 @@ function formatSize(bytes) {
126
242
  return `${(bytes / 1048576).toFixed(1)} MB`;
127
243
  }
128
244
  function wrapHtmlBody(html, allowRemote = false) {
245
+ // CSP blocks remote resource loading (tracking pixels, external CSS) but allows link clicks
129
246
  const csp = allowRemote
130
- ? "" // no CSP restriction when remote content allowed
131
- : `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: cid:;">`;
247
+ ? ""
248
+ : `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: cid:; form-action 'none';">`;
132
249
  return `<!DOCTYPE html>
133
250
  <html><head>
134
251
  <meta charset="UTF-8">
@@ -136,7 +253,7 @@ ${csp}
136
253
  <style>
137
254
  body {
138
255
  font-family: system-ui, sans-serif;
139
- font-size: 14px;
256
+ font-size: 17.5px;
140
257
  line-height: 1.5;
141
258
  color: #1a1a2e;
142
259
  background: #fff;
@@ -13,10 +13,10 @@
13
13
  </head>
14
14
  <body>
15
15
  <div class="compose-header">
16
- <div class="compose-field">
17
- <label for="compose-from">From</label>
18
- <input type="text" id="compose-from" list="compose-from-list" autocomplete="off">
19
- <datalist id="compose-from-list"></datalist>
16
+ <div class="compose-field compose-from-field">
17
+ <label for="compose-from-select">From</label>
18
+ <select id="compose-from-select"></select>
19
+ <input type="text" id="compose-from-custom" placeholder="Custom address..." hidden>
20
20
  </div>
21
21
  <div class="compose-field">
22
22
  <label for="compose-to">To</label>
@@ -18,27 +18,59 @@ const editor = new Quill("#compose-editor", {
18
18
  }
19
19
  });
20
20
  // ── Populate from init data ──
21
- const fromInput = document.getElementById("compose-from");
22
- const fromDatalist = document.getElementById("compose-from-list");
21
+ const fromSelect = document.getElementById("compose-from-select");
22
+ const fromCustom = document.getElementById("compose-from-custom");
23
23
  const toInput = document.getElementById("compose-to");
24
24
  const ccInput = document.getElementById("compose-cc");
25
25
  const bccInput = document.getElementById("compose-bcc");
26
26
  const subjectInput = document.getElementById("compose-subject");
27
- /** Extract account ID from the From field value, or return the raw value for custom addresses */
27
+ /** Populate the From dropdown with accounts */
28
+ function populateFromSelect(accounts, selectedId) {
29
+ fromSelect.innerHTML = "";
30
+ for (const acct of accounts) {
31
+ const opt = document.createElement("option");
32
+ opt.value = acct.id;
33
+ const displayLabel = acct.label || acct.name;
34
+ opt.textContent = `${displayLabel}: ${acct.name} <${acct.email}>`;
35
+ opt.dataset.email = acct.email;
36
+ opt.dataset.name = acct.name;
37
+ if (acct.defaultSend)
38
+ opt.dataset.defaultSend = "true";
39
+ if (acct.id === selectedId)
40
+ opt.selected = true;
41
+ fromSelect.appendChild(opt);
42
+ }
43
+ // "Other..." option for custom address
44
+ const other = document.createElement("option");
45
+ other.value = "__custom__";
46
+ other.textContent = "Other...";
47
+ fromSelect.appendChild(other);
48
+ }
49
+ fromSelect.addEventListener("change", () => {
50
+ if (fromSelect.value === "__custom__") {
51
+ fromCustom.hidden = false;
52
+ fromCustom.focus();
53
+ }
54
+ else {
55
+ fromCustom.hidden = true;
56
+ fromCustom.value = "";
57
+ }
58
+ });
59
+ /** Extract account ID from the From field */
28
60
  function getFromAccountId() {
29
- const val = fromInput.value;
30
- // Check datalist options for a matching account
31
- for (const opt of fromDatalist.options) {
32
- if (opt.value === val && opt.dataset.accountId)
33
- return opt.dataset.accountId;
61
+ if (fromSelect.value === "__custom__") {
62
+ // Custom address use default send account, fallback to first
63
+ const defaultOpt = Array.from(fromSelect.options).find(o => o.dataset.defaultSend === "true");
64
+ return defaultOpt?.value || fromSelect.options[0]?.value || "";
34
65
  }
35
- // No match — return first account ID as the sending account (custom From address)
36
- const first = fromDatalist.options[0];
37
- return first?.dataset.accountId || val;
66
+ return fromSelect.value;
38
67
  }
39
68
  /** Get the From address string for the message headers */
40
69
  function getFromAddress() {
41
- return fromInput.value;
70
+ if (fromSelect.value === "__custom__")
71
+ return fromCustom.value;
72
+ const opt = fromSelect.selectedOptions[0];
73
+ return opt ? `${opt.dataset.name} <${opt.dataset.email}>` : "";
42
74
  }
43
75
  /** Smart tab — skip to next empty field, ending at body */
44
76
  function smartTab(current) {
@@ -145,8 +177,7 @@ function setupAutocomplete(input) {
145
177
  e.preventDefault();
146
178
  const idx = activeIndex >= 0 ? activeIndex : 0;
147
179
  items[idx].dispatchEvent(new MouseEvent("mousedown"));
148
- // After selecting, smart-tab to next empty field
149
- setTimeout(() => smartTab(input), 50);
180
+ // Stay in field — user may want to add more addresses
150
181
  return;
151
182
  }
152
183
  }
@@ -176,16 +207,7 @@ function parseAddrs(s) {
176
207
  }
177
208
  function applyInit(init) {
178
209
  // Populate From dropdown
179
- fromDatalist.innerHTML = "";
180
- for (const acct of init.accounts) {
181
- const opt = document.createElement("option");
182
- opt.value = `${acct.name} <${acct.email}>`;
183
- opt.dataset.accountId = acct.id;
184
- fromDatalist.appendChild(opt);
185
- }
186
- const selectedAcct = init.accounts.find((a) => a.id === init.accountId) || init.accounts[0];
187
- if (selectedAcct)
188
- fromInput.value = `${selectedAcct.name} <${selectedAcct.email}>`;
210
+ populateFromSelect(init.accounts, init.accountId);
189
211
  toInput.value = formatAddrs(init.to);
190
212
  ccInput.value = formatAddrs(init.cc);
191
213
  subjectInput.value = init.subject;
@@ -193,6 +215,10 @@ function applyInit(init) {
193
215
  editor.clipboard.dangerouslyPasteHTML(init.bodyHtml);
194
216
  editor.setSelection(0, 0);
195
217
  }
218
+ // If resuming a draft, track its UID for deletion after send
219
+ if (init.draftUid) {
220
+ draftUid = init.draftUid;
221
+ }
196
222
  document.title = init.subject ? `${init.subject} - Compose` : "Compose - mailx";
197
223
  // Focus first empty field: To → Subject → body
198
224
  if (!toInput.value)
@@ -213,19 +239,11 @@ else {
213
239
  toInput.focus();
214
240
  }
215
241
  // If From dropdown is empty (new compose without init, or init had no accounts), fetch from API
216
- if (fromDatalist.children.length === 0) {
242
+ if (fromSelect.options.length === 0) {
217
243
  fetch("/api/accounts")
218
244
  .then(r => r.json())
219
245
  .then((accounts) => {
220
- for (const acct of accounts) {
221
- const opt = document.createElement("option");
222
- opt.value = `${acct.name} <${acct.email}>`;
223
- opt.dataset.accountId = acct.id;
224
- fromDatalist.appendChild(opt);
225
- }
226
- if (!fromInput.value && accounts.length > 0) {
227
- fromInput.value = `${accounts[0].name} <${accounts[0].email}>`;
228
- }
246
+ populateFromSelect(accounts);
229
247
  })
230
248
  .catch(e => console.error("Failed to load accounts:", e));
231
249
  }
@@ -270,6 +288,7 @@ document.getElementById("btn-send")?.addEventListener("click", async () => {
270
288
  btn.textContent = "Sending...";
271
289
  const body = {
272
290
  from: getFromAccountId(),
291
+ fromAddress: getFromAddress(),
273
292
  to: parseAddrs(toInput.value),
274
293
  cc: parseAddrs(ccInput.value),
275
294
  bcc: parseAddrs(bccInput.value),