Commit Graph
1734 Commits
Author SHA1 Message Date
Linus Rath c8edd1f2e1 Merge pull request #715 from lucletoffe/fix/unread-dot-alignment
fix(list): align the unread dot with the first line, not the row midpoint
2026-09-07 17:56:31 +02:00
Linus Rath 6a44442594 Merge pull request #953 from shukiv/fix/list-avatar-vertical-center
fix(list): centre the sender avatar against the row
2026-09-07 17:56:01 +02:00
Linus Rath bf33165ffb Merge pull request #914 from bitfactory-dk/fix/notification-click-focus-before-navigate
fix(sw): focus the client before navigating on notification click
2026-09-07 17:55:44 +02:00
Linus Rath ea49cc0d92 Merge pull request #968 from lucletoffe/fix/calendar-description-links
fix(calendar): linkify URLs in the event description
2026-09-07 17:55:32 +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 949c6731ec Merge pull request #934 from dev-hive-kazniisa/feat/pwa-app-badge
feat(pwa): badge the installed app icon with unread count (Badging API)
2026-09-07 17:55:02 +02:00
Linus Rath 2c104f8191 Merge pull request #939 from paulhenry46/plugin_trad_fix
fix: add translations for plugin fetched from marketplace
2026-09-07 17:54:48 +02:00
Linus Rath 1122306ba1 Merge pull request #944 from shukiv/fix/rtl-mobile-drawer-side
fix(mobile): open the folder drawer from the right in RTL
2026-09-07 17:54:30 +02:00
Linus Rath 987498ddb4 Merge pull request #979 from Almost-Senseless-Coder/main
feat(auth): Send prompt=select_account to IdM when adding additional accounts
2026-09-07 17:53:02 +02:00
Linus Rath adea2a5376 Merge pull request #945 from shukiv/feat/mobile-search-first-header
feat(mobile): search-first header, replacing the second toolbar row
2026-09-07 17:47:16 +02:00
Linus Rath 7652d974e1 Merge pull request #860 from dealerweb/fix/logout-test-force-url
test: match the forced token refresh URL in the logout tests
2026-09-07 17:43:22 +02:00
Linus Rath 75585f224e Merge pull request #987 from ibayue/feature/composer-sticky-toolbar-font-size
feat(composer): sticky formatting toolbar with font-size and background color
2026-09-07 17:42:52 +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
ibayueandClaude aa0d51600e fix(i18n): add toolbar keys to nb and zh-TW locales
Upstream added these locale files after this branch was cut; the
translations completeness test requires every locale to carry the new
email_composer.toolbar keys.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:59:17 +08:00
ibayueandClaude cc64a9871a fix(composer): background-color picker uses the text-color palette
The dedicated pastel highlighter palette read washed-out against the
light popover. Use the same 16 TEXT_COLORS swatches, grid and styling
as the text-color picker so the two controls read as siblings (Gmail
does the same for both pickers).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:54 +08:00
ibayueandClaude 0459976138 fix(composer): cleaner font-size glyph, number shown once a size is set
The lucide ALargeSmall icon read poorly and lingered after picking a
size. Replace it with two baseline-aligned A's drawn in text (large +
small sharing a baseline - the classic size glyph), and once a size is
selected show that number on the button as before.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:54 +08:00
ibayueandClaude 3957b35809 feat(composer): add background color and clearer font-size button
- Background color toolbar button next to text color, using the
  BackgroundColor extension from @tiptap/extension-text-style (same
  textStyle-mark pattern as Color) with a 2x8 soft-tint highlighter
  palette and a remove action; emitted as inline
  style="background-color: …" which survives email round-trips.
- Font-size button now shows a big-A/small-A glyph (ALargeSmall) with
  the active size as a small badge, replacing the plain "Aa" text that
  was easy to miss.
- background_color/remove_background_color toolbar labels in all 24
  locales; unit tests for apply/unset/parse/coexistence with color and
  font-size on the same span.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:54 +08:00
ibayueandClaude 8fbe508ba3 fix(composer): keep toolbar background at main's exact muted/30
The previous fix paired bg-muted/30 with
supports-[backdrop-filter]:bg-muted/70, intending a fallback for
browsers without backdrop-filter - but every modern browser supports
it, so the 70%-opaque variant was always active and the toolbar read
darker than main at rest. Drop the override entirely: the background
is bg-muted/30 in every state, identical to main, and pinned
legibility comes from backdrop-blur-sm alone (blurring a solid page
background is a no-op at rest; it only softens body text sliding
underneath once the toolbar is pinned).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:51 +08:00
ibayueandClaude 08e279d21d fix(composer): restore main's translucent toolbar look
The sticky rework had swapped the toolbar background to opaque
bg-muted, which reads darker than main's bg-muted/30. Restore the
translucent background and pair it with backdrop-blur + a slightly
stronger muted overlay (only when backdrop-filter is supported) so
pinned-on-body-text stays legible without the flat solid band.
Also pin the font-size button's line height so the toolbar's row
height matches main exactly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:51 +08:00
ibayueandClaude 0ead019c5c fix(composer): keep From/To/Cc/Bcc/Subject fields pinned above the body
Move the fields section out of the composer's scroll container so it
stays visible while scrolling long bodies, matching the pinned
formatting toolbar. Since the fields were the first child of the
scroll container, hoisting them above it is visually identical to
making them sticky - but needs no dynamic offset for the toolbar's
own sticky top (the fields' height varies with Cc/Bcc toggling and
recipient-chip wrapping). The scroll container now wraps only the
body and the signature preview.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:51 +08:00
ibayueandClaude 7e0a86a660 feat(composer): sticky formatting toolbar and font-size picker
- Pin the rich-text toolbar to the top of the composer's scroll
  container (sticky) so it stays visible while editing long bodies;
  the previously translucent background becomes opaque so body text
  doesn't bleed through while pinned.
- Add a font-size picker to the toolbar (14/16/18/20/24/32px plus
  "Default") backed by a new TipTap FontSize extension that stores
  the size as an inline style on the TextStyle mark, mirroring how
  @tiptap/extension-color handles colour - inline styles survive
  email round-trips, and unsetting the size leaves other text-style
  attributes (e.g. colour) intact.
- Add font_size / font_size_default toolbar strings to all 25
  locales.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:58:50 +08:00
Almost-Senseless-Coder 5771a213dd feat(auth): Send prompt=select_account to IdM when adding additional accounts. 2026-09-06 10:19:01 +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
lucletoffeandClaude Opus 5 208f632ca3 fix(calendar): linkify URLs in the event description
A meeting invitation puts the join URL in the description - Teams, Meet,
Zoom and the plain iTIP fallbacks all do - and the three read-only
description renders (invitation RSVP view, event detail view, detail
popover) printed it as bare text. The one actionable thing in the event
was not clickable: you had to select the URL by hand, across the line
wraps, and paste it into the address bar.

Render those three through the same pipeline the plain-text mail body
already uses (`plainTextToSafeHtml` + `sanitizePlainTextRenderedHtml`),
wrapped in a small `LinkifiedText` component. The text stays plain text:
it is escaped, only `<a href="http(s)://...">` is emitted, and the result
is sanitized again before it reaches the DOM - an event description is
attacker-controlled, since anyone can send an iTIP invitation.

Two details along the way: `break-words` on the paragraphs, so a long
join URL wraps instead of widening the panel, and `whitespace-pre-line`
on the invitation view, which was the only one of the three dropping the
description's own line breaks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QPPAw4SK4M9Z7euVPM2cL
2026-09-04 09:35:50 +02:00
Linus Rath 63db562865 Merge pull request #959 from hildebrandttk/fix/unified-mailbox-account-race
fix(mail): unified mailbox missed accounts that had not connected yet
2026-09-03 10:21:34 +02:00
KazNIISA IT e7d3782344 feat(pwa): badge the installed app icon with unread count (Badging API)
Adds useAppBadge alongside the existing useFaviconBadge, mounted from the
same FaviconBadge component and gated by the same faviconUnreadBadge
setting. Targets the separate icon Chrome/Edge/WebKit show for an
installed PWA (taskbar/dock/Home Screen/pinned-app-shortcut), which does
not react to favicon changes at all - a merely pinned browser tab is
unaffected, only an actually installed app.

No unmount cleanup: the badge is scoped to the installed app/origin, not
to this component or window, so clearing on every unmount would blank
another still-open window's accurate badge. A count of zero or the
setting being off already clears it through the main effect.
2026-09-03 11:37:36 +05: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
Shuki Vaknin 6f92547a49 fix(list): centre the sender avatar against the row
List rows lay out with items-start, because the sender line, subject and
preview stack and the body should govern the row height. The avatar
inherited that and pinned itself to the top, level with the sender line,
leaving it visibly high against a two- or three-line row.

Give the avatar self-center in both the single-message and thread rows.
The row keeps items-start, so nothing else moves — in particular the
extra-compact checkbox, which is mutually exclusive with the avatar and
carries its own mt-2 offset, is untouched.
2026-09-02 00:40:54 +03:00
Linus Rath 6632834767 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-09-01 23:25:30 +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 c73f90eb35 Merge pull request #852 from shukiv/feat/clear-search-on-folder-change
feat(search): add "clear search when switching folders" setting
2026-09-01 21:53:00 +02:00
Linus Rath 96cdf3749f feat: avatars, tinted icons and structured previews in global search results #641 2026-09-01 21:41:35 +02:00
Linus Rath a9cd4ced60 feat: match the search tab chrome to the mail page sidebar #641 2026-09-01 21:30:16 +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 c075b79cbb feat: global search palette and search tab in the Pro shell #641 2026-09-01 20:55:13 +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 12f01409a6 feat(search): add "clear search when switching folders" setting
Switching folders currently keeps an active search applied and re-runs
it in the newly selected folder — deliberate behavior from #553, where
users wanted the query to follow them across folders. Others expect the
opposite: clicking Inbox/Sent should drop the search and just browse the
folder.

Add an opt-in setting (Settings → email behavior) that reconciles both.
Default is off, so #553's search-follows-you behavior is unchanged for
everyone. When on, any folder click clears the text query and advanced
filters and browses the folder instead of re-running the search.

Applied uniformly across the four folder-entry paths in MailApp — normal
mailboxes, per-account (Pro sidebar) folders, unified views, and cross
views — via one helper so no path diverges.
2026-09-01 21:26:08 +03:00
Linus Rath f2415d266c Merge pull request #948 from shukiv/fix/batch-toolbar-top-of-list
fix(list): stop the batch toolbar hiding the message you just selected
2026-09-01 20:18:28 +02:00
Linus Rath 3f2837277c Merge pull request #941 from ponchofiesta/bugfix/940-create-contact-in-selected-addressbook
fix: set default addressbook to selected one
2026-09-01 20:17:31 +02:00
Shuki Vaknin 71dd5f4fed fix(list): stop the batch toolbar hiding the message you just selected
Opening the toolbar feeds its height back into scrollTop so rows keep
their position on screen. At the top of the list there are no rows above
to hold steady, so that scroll instead slides the first message under the
toolbar — select the top message and it disappears behind the bar acting
on it.

Skip the compensation when the list is already at the top and let the
content move down there, which is what the toolbar taking up space
should do. Mid-list behaviour is unchanged, and the scroll is clamped at
zero so collapsing the toolbar cannot drive it negative.
2026-09-01 19:05:35 +03:00
Shuki Vaknin 4279c37bb7 feat(mobile): clear the query from the header search field
Once a search runs the header field shows the active query, but the only
way back to the full list was to open the panel and clear it there.

Put a clear button at the trailing end of the field, shown only while a
query is applied. The field is now a container rather than one button,
since nesting a button inside a button is invalid markup — the label
area and the clear control are siblings.
2026-09-01 18:44:44 +03:00
Shuki Vaknin b6af6c16d3 fix(mobile): dismiss the search panel once a search runs
The full-screen panel is fixed inset-0, so it sat on top of the very
list it filters: submitting a query worked, updated the list underneath,
and left the reader staring at an empty panel.

Close it on submit, on picking a contact suggestion, and on clear, so
the results it just produced are what you land on. The header field then
shows the active query rather than the placeholder, so a filtered list
does not look unexplained, and tapping it reopens the panel to refine.

SearchBox gains an autoFocus prop so the panel opens straight to the
keyboard instead of needing a second tap.
2026-09-01 18:37:01 +03:00
Shuki Vaknin 3924e30572 feat(mobile): search-first header, replacing the second toolbar row
Phones stacked two 56px bars: the mailbox title, then a row holding
select-all, the search box, the filter toggle and refresh. That is 112px
of chrome before the first message, and the title merely repeats what
the drawer already shows.

The header now carries the account avatar and a tappable search field,
and the second row is presented full-screen when that field is tapped —
so the same SearchBox, suggestions, history and filter chips appear,
none of it rebuilt. Select-all and refresh remain in that panel, and the
list already supports long-press selection and pull-to-refresh.

Tablet and desktop are unchanged; the row is only relocated on phones.
2026-09-01 18:24:07 +03:00
Shuki Vaknin fe948f7931 fix(account-switcher): add a header variant that stays on screen
The rail variant flies its popover out sideways, which is right for a
narrow vertical sidebar but wrong at the top of the screen: anchored to
a trigger at the inline-end edge it runs straight off the viewport. In
RTL that edge is the left one, so the menu opened past the left border
and was unreachable.

Add a "header" variant that drops the menu below the trigger and pins it
by its trailing edge, with a max-width so it can never exceed the
viewport. rail and expanded are untouched.
2026-09-01 18:24:07 +03:00