Commit Graph
696 Commits
Author SHA1 Message Date
Linus Rath d6c923f20e Merge pull request #947 from shukiv/feat/list-attachment-chips
feat(list): show attachments on list rows and open them from there
2026-09-11 17:05:08 +02:00
Linus Rath 51b2198f3f Merge pull request #1003 from lucamzanon/fix/navigation-protocol-account-limit
fix: detect HTTP/2 from the initial navigation timing so accounts aren't capped at five
2026-09-11 11:59:43 +02:00
dealerweb 1c00812ab7 fix: hide body-embedded cid parts declared as octet-stream from the attachment list
Some senders ship the images their HTML body embeds via cid: as
application/octet-stream with no Content-Disposition and no filename.
The body rendered them fine, but the "hide inline images" check only
recognised parts declared inline with an image/* type, so they also
showed up as nameless "Attachment" chips next to the real files.

Both viewers now scan the HTML they actually render for cid: references
(the same pattern the renderer rewrites to blob URLs) and hide a
referenced part when it is an image or generically typed. An explicit
attachment disposition always keeps its chip, as does a referenced part
with a real non-image type, since that chip is its only download. Plain
text rendering references nothing, so nothing is hidden there. The
shared rule lives in lib/attachment-visibility.ts so the desktop viewer,
the thread view and the decrypted-message path cannot drift apart.
2026-09-10 10:45:45 +02:00
lucamzanon 335347a56f fix: detect HTTP/2 from the initial navigation timing so accounts aren't capped at five
isHttp2Available() only inspected "resource" timing entries, which can be
empty right after a fresh login or session restore, before any subresource
finishes loading. On an HTTP/2 page this made getMaxAccounts() fall back to
the HTTP/1.1 cap of five accounts. Include the "navigation" timing entry,
whose nextHopProtocol is known immediately, so h2/h3 is recognized on first
load. Environments without timing data still return false (no h2 assumed).
2026-09-10 10:14:25 +02:00
Linus Rath 565d5c1095 feat: choose exact-address or same-domain matching for reply from received address #1000 2026-09-09 19:27:08 +02:00
Linus Rath c395226926 fix: reject wrong passwords server-side so the browser never shows its Basic Auth dialog #969 2026-09-09 19:21:40 +02:00
Linus Rath b91b438076 test: keep stray fetches and timers from leaking across resilience tests 2026-09-09 18:56:51 +02:00
Linus Rath 9796b0b14d fix: index newer settings and flat calendar toggles in the settings search 2026-09-09 18:40:15 +02:00
Linus Rath 8d8ef7e2f1 fix: unreadable native <select> option lists in dark themes #999
The browser paints a native <select>'s option list itself, so the
bg-muted / text-foreground classes on the control never reach it. Themes
whose --color-muted is translucent (Aurora Glass dark uses
rgba(255,255,255,0.05)) composited to a light popup with near-white
text, leaving every unselected option almost invisible.

Pin option background/colour to the popover tokens, which every built-in
theme defines as an opaque surface, and set color-scheme: dark on
selects under .dark so Safari (which ignores option colours) renders a
dark popup too. Fixes all ~40 native selects at once.

Adds a stylesheet test that locks the rules and asserts every built-in
theme's --color-popover stays opaque in each variant.
2026-09-09 18:30:59 +02:00
Linus Rath ae403b06af feat: setting to turn off free scrolling in the calendar 2026-09-09 18:27:01 +02:00
Linus Rath 15a5497a12 feat: freely scrolling month, week and day views #759 2026-09-09 16:38:55 +02:00
Linus Rath 26358c7a63 Merge pull request #991 from rotterp/fix/shared-mailbox-send-identity
fix(composer): send from the address a message was delivered to
2026-09-07 22:38:51 +02:00
Linus Rath d28d8dcd86 fix: report calendar clear failures instead of counting zero #434 2026-09-07 22:34:47 +02:00
Linus Rath 07bbde4786 fix: make the iCal subscription size limit configurable and show the real error #692 2026-09-07 22:34:47 +02:00
Linus Rath 928e2e3794 feat: add a server-side switch for the Stalwart JMAP passthrough #904 2026-09-07 22:34:47 +02:00
Linus Rath e98109b80d fix: require a session for the translate API #903 2026-09-07 22:34:47 +02:00
Linus Rath 4afc2a5db0 fix: surface Email/set failures on delete and move #956 2026-09-07 22:34:47 +02:00
Linus Rath e095805851 fix: use the selected server's issuer for SSO discovery #952 2026-09-07 22:34:47 +02:00
Linus Rath 40e38d1679 fix: retry the JMAP session fetch when a redirect drops the auth header #892 2026-09-07 22:34:47 +02:00
Linus Rath faed1591ef fix: fail fast when the plugin storage database is blocked #840 2026-09-07 22:34:47 +02:00
Linus Rath b4f12b6786 fix: resolve push previews for shared and group mailboxes #839 2026-09-07 22:34:47 +02:00
Linus Rath 2b186910d5 fix: subscribe newly created mailboxes #951 2026-09-07 22:34:47 +02:00
Linus Rath af234f64a4 fix: drop max_age=0 from OIDC re-authentication requests #938 2026-09-07 22:34:47 +02:00
Patrick RotterandClaude Opus 5 4fc7b8b50b fix(composer): send from the address a message was delivered to
On a shared, group or aliased mailbox every reply, reply-all and forward
went out as `identities[0]` - the account owner - whichever mailbox was
open. `autoSelectReplyIdentity` fused two behaviours: matching one of the
user's OWN identities, which never rewrites `From:`, and the domain
catch-all, which puts an address they have not configured into `From:`.
Only the first is safe for everyone, so it is now unconditional; the
rewrite stays behind the setting, which is what its description promises.
`findReplyIdentityId` already implemented that match and was referenced
only from tests.

`forward` is admitted to the match - it was excluded, so a forward from a
shared mailbox went out as the owner - but never takes the rewrite: a reply
continues a thread whose participants know the addressing, while a forward
introduces a rewritten From to a recipient the user just typed. Quick reply
matches own identities only, having no From row to show or correct a
rewrite, and now falls back to the primary's signature as the composer
does, so a shared-mailbox quick reply is still signed.

`findReplyIdentityId` now scans recipients in To, Cc, Bcc order rather than
scanning identities, and prefers an untagged identity over a differently
tagged sibling. Scanning identities let their order decide, and
`sortIdentities` puts the login's own address first - so `To: team@,
Cc: you@` replied as you, the case this is meant to fix.

Automatic selection is restricted to the active account: `composerClient`
follows the chosen identity, and a reply or forward still carries blobIds
only its own account's server can resolve. The dropdown still offers every
account's identities to pick by hand.

New messages are deliberately left gated: `composeFromAccountEmail` falls
back to `AccountEntry.email`, written once at first login, so resolving it
unconditionally would override a user-chosen default sender identity (#507).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 20:37:29 +02:00
Linus Rath e11e40f0e7 Merge pull request #928 from hildebrandttk/fix/truncated-body-blank-render
fix(email): refetch body value when JMAP truncates the displayed part
2026-09-07 17:55:15 +02:00
Linus Rath b168e45e22 Merge pull request #975 from bartfaizoli76/feature/plugin-dialog-open
feat: api.ui.openDialog + plugin-dialog slot for large, clickable custom UI
2026-09-07 17:42:42 +02:00
Linus Rath 3eeda39d27 Merge pull request #974 from bartfaizoli76/feature/attachment-actions-plugin-slot
Add attachment-actions and composer-attachment-source plugin slots
2026-09-07 17:42:31 +02:00
Linus Rath 93b8d99ff9 fix(): refill missing managed bundles from the server #636 2026-09-07 16:52:20 +02:00
bartfaizoli76 2adc281a9d feat: add ui.openDialog + 'plugin-dialog' slot for large, clickable custom UI
Every existing plugin slot renders wherever it's placed in the page (a
toolbar row, an attachment's hover-action bar, etc.) and is constrained
by that spot's own CSS - fine for small controls, but there's no way
for a plugin to show a large custom UI (a graphical file browser, a
multi-step wizard) since even a position:fixed overlay rendered from
inside such a slot is clipped to the surrounding row's own layout, not
the real page (confirmed live building the webdav-storage plugin: a
folder browser rendered but was squeezed into an unusable sliver).

Adds:
- host-dialog.ts: new 'custom' DialogKind + awaitCustomDialog(), reusing
  the existing confirm/alert/prompt queue plumbing.
- host-api.ts: 'ui.openDialog' API method.
- runtime.tsx: sandbox-side api.ui.openDialog(opts) proxy.
- plugin-types.ts: new 'plugin-dialog' SlotName - a plugin implements
  this slot with its actual custom UI.
- plugin-dialog-host.tsx: renders the requested slot via the existing
  PluginIframeSlot INSIDE the dialog's real app-root fixed overlay
  (position: fixed; inset: 0, above everything) instead of the
  confirm/prompt form, for kind: 'custom'. Resolves via a generic
  onResult callback in extraProps (marshalled the same way
  composer-attachment-source's onAttach already is - no new plumbing
  needed there).

A plugin calls api.ui.openDialog({ slot: 'plugin-dialog', extraProps,
title, width }) and awaits the Promise, which resolves to whatever its
own slot component passes to props.onResult(), or null if the user
closes the dialog without doing so.
2026-09-05 18:57:49 +02:00
bartfaizoli76 06f51486e0 feat: add attachment-actions and composer-attachment-source plugin slots
Two new extension points for third-party attachment/cloud-storage plugins
(e.g. a WebDAV/Nextcloud file browser), neither of which the plugin API
currently supports:

- attachment-actions: a new slot rendered into every attachment's hover
  action row (all six render paths: beside-sender/below-header/mobile,
  each in its compact and expanded-popup form), with the AttachmentInfo
  (name, type, size, blobId, emailId) passed through extraProps. Lets a
  plugin offer a 'save this attachment elsewhere' action next to the
  existing download/preview buttons - previously the only per-attachment
  hook was the read-only onAttachmentDownload observer.

- composer-attachment-source: a new slot rendered in the composer toolbar
  next to the existing composer-toolbar slot, with an onAttach(upload)
  callback in extraProps. Lets a plugin that has already uploaded a file to
  the JMAP server itself (api.jmap.uploadBlob) - e.g. a file picked from an
  external cloud storage browser - have it added to the current draft as a
  real attachment. Previously there was no way for a plugin to add an
  attachment to the draft at all; the closest existing mechanism
  (onBeforeBlobUpload's ExternalAttachmentResult) only replaces a locally
  picked file with body text/a link, it cannot add a brand-new attachment
  from an external source.

Both slots follow the existing PluginSlot pattern (calendar-event-actions):
render nothing when no plugin offers the slot, so this is purely additive.

Motivating use case: the existing nextcloud-attachments community plugin
uploads local files to Nextcloud and appends a share link to the message
body - useful for large files, but there was no way for a plugin to do the
inverse (attach a file that already lives in cloud storage as a real binary
attachment, not a link) or to let attachment recipients save an incoming
attachment back out to their own cloud storage. These two slots make both
directions possible without touching plugin-store or the sandbox bridge -
existing slot/extraProps plumbing (see calendar-event-actions) already
supports arbitrary POJO props.
2026-09-05 11:14:10 +02:00
Stefan Hildebrandt 242fe6aa2d fix(mail): unified mailbox missed accounts that had not connected yet
After a reload the unified mailbox showed only the active account's mail;
other accounts appeared only once visited. Intermittent (~1 in 14), and
not a timeout - nothing on this path uses setTimeout/AbortSignal/race.

checkAuth awaits only the active account and restores the rest in a
fire-and-forget background loop, registering each client only after
connect() resolves. buildUnifiedAccountClients drops accounts with no
live client (`if (!c) continue`). And isConnected was persisted, so it
rehydrated stale-true: the account passed the isConnected filter while
failing the client lookup, and connectedAccountsSignature already looked
complete at mount so nothing re-fired - and the effects keyed on it only
recompute counts, never the list.

Clear isConnected/hasError on rehydrate so both the filter and the
signature describe this session, and refetch the open unified/cross list
when an account joins the connected set (growth only, not on first
render, not while a search owns the list). Extracted
connectedAccountsGrew so the membership check is testable without
rendering MailApp.

Verified the reset cannot block reconnection: checkAuth reads the full
account list and never filters on isConnected.

Fixes #950
2026-09-02 22:15:50 +02:00
Stefan Hildebrandt 2cf7427e32 fix(email): refetch body value when JMAP truncates the displayed part
Large HTML emails (e.g. image-only reports with a data:image src cut
mid-tag by the 256 KB body cap) rendered completely blank because
Email.bodyValues[partId].isTruncated was declared but never acted on.
On truncation, issue one follow-up Email/get with a much larger cap
for the parts actually shown, batched across a thread instead of one
refetch per message.

Closes #884
2026-09-02 08:34:31 +02:00
Linus Rath b5b18371a4 fix: drop unused symbols left by the search dedupe change 2026-09-01 22:16:58 +02:00
Linus Rath bc3b6a0eb6 fix: collapse search hits for the same server object reached through several logins #641 2026-09-01 22:15:49 +02:00
Linus Rath 1d8e4b9d8a fix: duplicate calendar-app import and type errors the stale tsbuildinfo hid 2026-09-01 21:08:10 +02:00
Linus Rath 0c7437f67a feat: open global search hits through the owning login on every surface #847 2026-09-01 20:55:13 +02:00
Linus Rath 0db6bfdc64 feat: global search core: query parser, ranking, cross-account providers #641 2026-09-01 20:55:12 +02:00
Shuki Vaknin 0e1c34cdec feat(list): show attachments on list rows and open them from there
A message with attachments showed a paperclip and nothing else, so the
only way to reach a file was to open the message, scroll to the bottom
and find it. The list rows never had the information to do better:
EMAIL_LIST_PROPERTIES asked for hasAttachment, a boolean.

Request attachments alongside it and render the files as chips under the
preview, each one opening that file directly. Chips honour the existing
mailAttachmentAction setting, so they preview or download exactly as the
viewer's attachment list does.

Inline parts are filtered out. A single Outlook message routinely carries
half a dozen cid: signature spacers of under 200 bytes; chipping those
would bury the one file the reader actually wants. Only parts that are
neither inline nor cid-referenced, and that carry a filename, are shown —
two at a time, with the remainder collapsed into a count.

Downloads resolve the blob source from the row's own message rather than
the selected one, so they stay correct in the unified inbox where each
row can belong to a different account.
2026-09-01 18:51:51 +03:00
Linus Rath a210b22e49 fix: set name.full on all contact write paths so vCards carry mandatory FN #430 2026-08-31 23:53:24 +02:00
Linus Rath 1e9b6ac497 fix: FileNode + blob round-trip in dev mock JMAP, WOPI support for same-origin dev server 2026-08-31 23:45:27 +02:00
Linus Rath 76d67aa1ee feat: add office document fixtures to demo Files and a word-document icon (doc/docx/odt/rtf) 2026-08-31 23:25:05 +02:00
Linus Rath ca7fbbe416 feat: WOPI office document editing in Files (Collabora/OnlyOffice/EuroOffice) #425 2026-08-31 23:16:19 +02:00
Linus Rath 58ebf8d4c5 fix: paginate calendar event fetch so import UID dedup sees all events 2026-08-31 22:48:00 +02:00
Linus Rath d60eb59575 fix: pad attachment preview overlays for the iOS PWA safe area #936 2026-08-31 21:03:08 +02:00
Linus Rath 47dac6421e Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-31 20:43:24 +02:00
Linus Rath a69b3651fd fix: render plain-text-only mail as text in the mobile thread view #489 2026-08-31 20:41:45 +02:00
Michael Richter 84617d6186 feat: OAuth handler for plugins; extended contacts and addressbook API 2026-08-31 09:55:06 +02:00
Linus Rath b11cf4b1a5 feat: offer deleting a non-empty folder with its messages 2026-08-29 18:23:09 +02:00
Linus Rath 03b588096d feat: filter advanced search by message size 2026-08-29 18:20:21 +02:00
Linus Rath cc90903f05 feat: request delivery notifications and REQUIRETLS when sending 2026-08-29 18:16:52 +02:00