Commit Graph
45 Commits
Author SHA1 Message Date
Linus Rath 4af42858f0 Merge pull request #1168 from lucamzanon/feat/mountain-view-layout
feat: add Mountain View, an optional alternative layout
2026-10-10 19:23:59 +02:00
lucamzanon 9560a8b74f feat: optional encrypted backup of the account list
Users with several accounts can save the list of accounts in their browser
(address, server, label, colour) on the webmail server and bring it back on
another device. The list is encrypted in the browser with a password the user
chooses (PBKDF2-SHA-256, 600,000 iterations, AES-256-GCM, owner bound as
additional data); the server stores only the envelope, one file per owner
under SETTINGS_DATA_DIR/account-archives. No mailbox passwords are included.

Off by default behind the admin feature gate accountArchiveEnabled, pinned
off in Lite. Every method of /api/account-archive needs a session cookie of
the owner, matched without regard to case, whose credential is checked
against the mail server on each call; listing included.

Restored accounts this browser lacks are listed as awaiting sign-in: they
survive a reload, and opening one leads to the sign-in form with the address
filled in. Accounts already present get their archived label and colour.
2026-10-09 10:54:38 +02:00
lucamzanon ade30622f2 Merge remote-tracking branch 'origin/main' into up/feat/mountain-view-layout
# Conflicts:
#	components/mail/mail-app.tsx
2026-10-08 10:05:29 +02:00
Oğuz YılmazandClaude Opus 5.5 54ec8c6584 feat: let operators keep the message subject out of the tab title
Opening a message puts its subject in the browser-tab title. The title does
not stay in the tab strip: browsers keep it in history and in the
session-restore list, and screen shares show it. For mailboxes that hold
other people's correspondence that is a leak nobody audits.

A new feature gate, tabTitleSubjectEnabled ("Subject in Tab Title" under
Admin -> Policy), is on by default, so nothing changes for existing
installs. With it off, the title names the folder instead, so the tab still
says where you are. It is an admin gate rather than a user setting: whose
mail the boxes hold is a deployment decision.

The gate counts as off until the policy has loaded, so a subject never
slips into history while the setting is still unknown.

Refs #1159

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:26:51 +03:00
lucamzanon d632956231 feat: add Mountain View, an optional alternative layout
Add an "Interface layout" setting at the top of Settings > Layout with two
options, Default and Mountain View. Default is the existing layout and stays
the default, so nothing changes unless a user opts in.

Mountain View rearranges the chrome while keeping the theme's colours, icons
and type:

- desktop: a top bar with a wide search field (filters drop from the field),
  an apps grid and the account avatar instead of the left app rail; flat
  navigation with Starred under Inbox and the rest behind "More"; a Compose
  pill; one list toolbar with select-all, refresh, overflow menu, a 1-N of
  total range and the batch actions; rows led by a checkbox and a star; an
  icon-only message toolbar with Reply / Reply all / Forward under the
  message; compose in a docked window; an account popover with per-account
  unread counts; j/k cursor, x to select and g-sequences
- phone: rounded search header, compact rows with the star on the preview
  line, and Reply / Reply all / Forward at the end of a message instead of
  the bottom action bar

It is one setting (`interfaceLayout`), written to a data attribute on <html>
that keys a block of CSS tokens in globals.css, plus a few components gated
on the flag. The mail layout in force is derived (effectiveMailLayout), so
the user's saved mail layout is never rewritten. The setting is listed in
the admin policy tab, and the new strings are translated in all locales.

Includes unit tests and a Playwright smoke test against a running instance
(scripts/mountain-view-smoke/).
2026-10-05 10:36:20 +02:00
Linus Rath 7e1a659c06 fix: open the WOPI editor behind a reverse proxy with split addresses #1130 2026-10-04 22:16:29 +02:00
Linus Rath 5597eadc28 fix: give a server its own sign-in button only when it has a button label #1135
A server entry's OAuth client ID already has a meaning while OAuth is
off globally: it is the registered client that password and TOTP logins
use with Stalwart's require_client_registration. The PR made every such
entry offer OAuth on its own, so existing installs would suddenly run
discovery and show SSO buttons on the login page, and multi-IdP setups
would get one extra button per server.

A server now gets its own button only with both a client ID and a button
label, which no existing config has. The rule lives in `offersOwnOAuth`
with a test. Also:
- the OAuth-only login form uses the selected server's label
- the public config validator checks `buttonLabel` is a string
- the admin hint and `.env.example` say the label enables the button
2026-10-01 11:01:17 +02:00
Linus Rath d2855695a6 Merge pull request #1135 from lucamzanon/feat/per-server-oauth-sign-in
feat(login): let a server in the list sign in with its own OAuth client
2026-10-01 11:00:26 +02:00
Linus Rath 29c74c3b77 feat: sign in with an access token 2026-10-01 00:24:47 +02:00
lucamzanon 1fac44832a feat(auth): let one server in the list sign in with OAuth on its own
A server entry that names its own OAuth client now offers the OAuth
button even while OAuth is off globally, so a Gmail bridge can sign in
with Google next to a server that uses passwords. The entry can also
name the button ("Sign in with Google"); the generic SSO label stays
the default.
2026-09-30 21:28:06 +02:00
Linus Rath fee0e7c9e7 fix: show default oauth scopes in the admin dashboard #1126 2026-09-30 12:17:25 +02:00
Linus Rath f97218375e fix: end the identity provider session on sign-out #905 2026-09-23 20:44:46 +02:00
Linus Rath f8592bc6b7 fix: accept oauth endpoints on the configured issuer's own host #1028 2026-09-23 19:26:07 +02:00
Linus Rath a4eac38834 feat: add login page toggles, including hiding the version, to admin settings #1068 2026-09-23 18:21:21 +02:00
Aaron Guise 887b9e7f92 feat(notifications): add toggleable inbox-only push notifications ✨ 2026-09-23 11:07:58 +12:00
Linus Rath 77ef60caae feat: resolve deep links to the local instance 2026-09-20 19:59:37 +02:00
Linus Rath d7548a4bab refactor: migrate icon set from Lucide to Tabler Icons 2026-09-19 13:32:41 +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 ca7fbbe416 feat: WOPI office document editing in Files (Collabora/OnlyOffice/EuroOffice) #425 2026-08-31 23:16:19 +02:00
Linus Rath 4fe5701b3b feat: default sidebar apps for all users #931 2026-08-29 12:31:17 +02:00
Linus Rath ed340cc2c1 feat: make Stalwart admin access to the dashboard configurable #870 2026-08-25 18:39:07 +02:00
Linus Rath 548200131b feat: pick push relay from an admin-defined list instead of a URL field 2026-08-16 23:13:36 +02:00
Linus Rath 5b8ddc352b feat: pick push relay from admin-defined list instead of a URL field 2026-08-16 23:04:22 +02:00
Linus Rath e2da05c130 feat: deep links for mail, calendar, contacts, files, settings and improve screen reader support
#733 #722 #721  #720  #719
2026-08-06 16:47:20 +02:00
Stefan Hildebrandt 7c221c4a4a feat(unified-mailbox): account-bounded Unified Mailbox with opt-in cross-account
Rework the sidebar "All accounts" section into a "Unified Mailbox" that, by
default, stays within the active login account and its shared/group folders.
Merging across multiple logged-in accounts becomes an opt-in sub-option instead
of the default, and the standalone per-account "All Mail" virtual folder is
folded into the unified All mail / Unread / Starred entries (its folder selection
now narrows those lists).

Scope:
- lib/unified-mailbox.ts: UnifiedAccountClient.crossIncludedMailboxIds; the cross
  views honor the per-account folder selection (union across accounts = the sum
  of each account's selection), falling back to inbox+custom when unset.
- stores/email-store.ts: buildUnifiedAccountClients gains scopeToClientAccountId
  (the account boundary) and populates crossIncludedMailboxIds from
  allMailFolderIds; remove the standalone __all_mail__ fetch/search/load-more
  branches.
- page.tsx: scope to the active account unless cross-account is active (per-user
  opt-in AND admin gate); the per-role unified mailboxes obey the same scope.

Folding:
- Drop ALL_MAIL_MAILBOX_ID (lib/jmap/types.ts); thread-list source-folder column
  now keys on isUnifiedView only; settings folder picker moves under the unified
  group and shows once any unified entry is enabled.

Config:
- User: new unifiedCrossAccount (default false); includeGroupInUnified default
  flips to true; enableAllMailView retired; the three cross-view toggles now gate
  the unified Unread/Starred/All mail entries.
- Admin: new unifiedCrossAccountEnabled gate, default FALSE (cross-account is an
  admin opt-in; when off the per-user toggle is hidden and the scope is forced
  account-bounded at runtime). allMailViewEnabled deprecated and normalized
  forward into crossAllViewEnabled on policy load; cross-view gate labels reworded
  to "Unified Mailbox: ...".

Header: the sidebar section shows "All accounts" when cross-account is active
(opt-in AND admin gate AND >1 connected account), else "Unified Mailbox".

Migration:
- Settings persist v5 -> v6 (exported migrateSettings) - cross-active users keep
  cross-account; All-Mail-only users get the account-bounded unified All mail
  entry with folder ids preserved; includeGroupInUnified enabled for every
  migrated config; fresh installs are account-bounded.
- Admin policy: one-shot, marker-guarded migratePolicyUnifiedMailbox (run before
  configManager.load) enables unifiedCrossAccountEnabled when a cross view was
  active, so existing cross-account installs keep the behaviour despite the
  default-false gate. Skipped on read-only config dirs.

Locales: sidebar all_accounts (original label) + unified_mailbox (translated, per
locale) keys; dead standalone all_mail strings removed across all 20 locales.

Docs: FEATURES.md updated to the account-bounded model, the cross-account gate,
and the folder-narrowed aggregate entries.

Verification: tsc clean, eslint clean, full vitest suite green (incl. translations
completeness, cross-view/migration coverage, and the admin policy migration test).
2026-07-11 21:14:34 +02:00
Shuki Vaknin 22418c17cf feat(i18n): Hebrew locale + full RTL support 2026-07-08 00:07:34 +02:00
Linus Rath 1f4afe082b fix: show all built-in themes in admin theme controls #496 2026-06-28 20:31:00 +02:00
Linus Rath 7a022596c3 feat: add option to disable calendar 2026-06-24 18:39:38 +02:00
Stefan Hildebrandt a29c33b50a feat: cross-account "All accounts" views + full group/shared-account support
Add cross-account aggregate mail views and make group/shared (delegated)
accounts first-class in every aggregate view. (The unified mailbox, the "All
Mail" view, and "include group inboxes" already exist on main; this branch adds
the cross-account views and the shared-account correctness work.)

New views (admin-gated + per-user toggle, nested under Unified Mailbox):
- Cross-account "All accounts": All unread / All starred / All mail across every
  connected account, including shared/group folders. Each list labels the source
  folder of every message.

Source reference on aggregated emails (the core of the shared-account work):
- Replace the overloaded `accountId` with two explicit, always-set fields:
  `sourceClientAccountId` (the login the mail is reachable through) and
  `sourceAccountId` (the owning JMAP account). `accountId` stays display-only.
- Resolution is branch-free everywhere: pick the client by sourceClientAccountId,
  pass sourceAccountId as the JMAP accountId (no-op for personal), read the
  owner's mailbox list cached by JMAP id. No capability scan.

Shared/group-account correctness across all aggregate views:
- Route open (click + auto-fetch), thread/conversation open + reply-refresh,
  mark read, star, move, delete (account-scoped trash), archive (owner-routed
  createMailbox / fetchAccountMailboxes), and spam + undo via the source ref.
- Add accountId params to toggleStar / batchMarkAsRead / batchDeleteEmails /
  createMailbox where missing.
- Fix local unread/total counter math for shared folders via emailInMailbox()
  (matches namespaced shared ids and bare own ids).
- Keep the unified/cross virtual selection on background mailbox refresh (no
  jump back to inbox after deleting in All Drafts/Junk).

Junk UX:
- In "All Junk" the spam action becomes "not spam" in the viewer, context menu,
  and list hover icons; undo routes shared mail back to its own inbox.

Admin:
- Policy gates crossUnread/Starred/AllViewEnabled, each noting the matching
  per-user toggle (allMailViewEnabled clarified too).

i18n / docs / tests:
- locales (19): cross-view labels + descriptions and hover not_spam, translated
  in all shipped languages.
- FEATURES.md + README.md document the new views and group-account support.
- Tests for shared-account routing (single + batch + undoSpam), decoration, and
  unified-selection preservation.
2026-06-23 19:16:22 +02:00
Linus Rath c51c3655d5 feat: add "All Mail" view 2026-06-15 18:42:23 +02:00
Linus Rath 798a33495e refactor: remove JMAP status from admin dashboard 2026-06-14 17:15:52 +02:00
Linus Rath 3e12fca517 fix: make telemetry opt-in 2026-06-14 14:31:26 +02:00
dealerweb 55be19ede7 Feature: admin toggle for search-engine indexing (robots)
Add a 'Search Engine Indexing' toggle under Settings -> General. Off (the
default) emits robots noindex/nofollow in the document head - the safe default
for a private webmail; on lets an admin opt the deployment into indexing.
Backed by the existing admin config-manager (SEARCH_ENGINE_INDEXING env var /
admin override / revert), read server-side in the root generateMetadata().
2026-05-31 14:45:32 +02:00
dealerweb 66c5f0f52c Feature: configurable PWA install screenshots (per-domain)
Admins can upload custom mobile/desktop screenshots shown in the browser's
PWA install dialog, replacing the hardcoded Bulwark ones. Two new config keys
(pwaScreenshotMobileUrl/DesktopUrl), upload widgets in the admin Branding tab,
a sharp-based /api/pwa-screenshot/[variant] resize route, and manifest.ts picks
the custom screenshots when configured.

Like the other branding fields, screenshots are per-domain: they are
BRANDING_OVERRIDE_KEYS, the manifest and the /api/pwa-screenshot route resolve
them from the request host (domain override -> global -> Bulwark default), and
the admin Branding tab + upload/delete route handle them in a per-domain scope,
mirroring pwaIconUrl/faviconUrl.
2026-05-30 15:45:59 +02:00
dealerweb f0d87d594a Fix: honour basePath in plugin sandbox, http.post proxy, and branding
Upstream 1.7.2 prefixes most hand-written URLs with basePath via apiFetch /
withBasePath, but four subpath-relevant spots were missed:

- host-bridge: the sandbox iframe src was a bare "/plugin-sandbox" -> 404
  under NEXT_PUBLIC_BASE_PATH, breaking all plugins. Wrap in withBasePath.
- host-api doHttpPost: the same-origin /api/* plugin proxy used raw fetch on
  url.pathname -> 404 under a subpath. Route it through apiFetch.
- admin branding preview <img>: unprefixed src -> broken thumbnail.
- (sandbox) layout: drop the Geist font + globals.css imports. The sandbox
  runs with an opaque origin, so those assets are CORS-blocked; the plugin
  bundle and all host API calls travel over the postMessage bridge, so no
  same-origin asset fetch happens there.
2026-05-30 15:31:08 +02:00
Linus Rath eb7eeae1ac feat: per-domain branding editor in admin panel #332 2026-05-28 20:21:25 +02:00
Linus Rath 8ae5ecba41 feat: policy-controlled push relay URL with optional user lock 2026-05-28 19:45:14 +02:00
Chuyen Nguyen e86183b44a Fix bug where editing any field closed the form 2026-05-26 08:39:24 +02:00
Linus Rath e2abc8dee9 fix: prefix remaining <img>, favicon, and WebDAV URLs with basePath #319 2026-05-25 16:45:13 +02:00
Linus Rath acc90eb455 feat: add "Move to Trash and mark as read" delete action #323 2026-05-22 19:08:02 +02:00
Linus Rath 63f2169ae7 fix: add OAUTH_ALLOW_PRIVATE_ENDPOINTS for split-DNS setups 2026-05-22 17:22:14 +02:00
Linus Rath 66b2036e37 feat: expose PWA branding fields in admin Branding tab 2026-05-22 11:22:07 +02:00
Linus Rath ba4781910d feat: marketplace update flow for installed plugins/themes 2026-05-22 00:11:10 +02:00
Linus Rath fc5f6f43d6 feat: expose PWA, app identity, and extension directory keys in JSON config #312 2026-05-21 23:35:58 +02:00
Linus Rath 628966d3b5 fix: split app into (main)/(sandbox) route groups so plugin iframe hydrates properly 2026-05-20 23:41:49 +02:00