mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: stop the mail list flashing a reload right after the page opens
This commit is contained in:
@@ -115,6 +115,7 @@ import {
|
||||
parseScheduledMailboxId,
|
||||
appPath,
|
||||
buildMailPath,
|
||||
isFolderLinkOpen,
|
||||
parseMailPath,
|
||||
resolveFolderRef,
|
||||
type MailDeepLink,
|
||||
@@ -1363,7 +1364,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
// session and the mailbox list are up. Runs at most once per mount: after
|
||||
// this, the URL is an output of the view (see buildMailUrl), not an input.
|
||||
const deepLinkHandledRef = useRef(false);
|
||||
const applyMailDeepLink = async (link: MailDeepLink) => {
|
||||
const applyMailDeepLink = async (link: MailDeepLink, opts?: { onLoad?: boolean }) => {
|
||||
// A permalink can name the account it belongs to. Ids are only meaningful
|
||||
// within their account, so switch first - but only to a login that is
|
||||
// actually connected; we can't authenticate on someone's behalf. A push
|
||||
@@ -1371,7 +1372,8 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
const linkAccountId = link.accountId ?? (link.slot !== undefined
|
||||
? useAccountStore.getState().accounts.find((a) => a.cookieSlot === link.slot)?.id ?? `slot:${link.slot}`
|
||||
: undefined);
|
||||
if (linkAccountId && linkAccountId !== useAuthStore.getState().activeAccountId) {
|
||||
const switchesAccount = !!linkAccountId && linkAccountId !== useAuthStore.getState().activeAccountId;
|
||||
if (switchesAccount) {
|
||||
const target = useAccountStore.getState().accounts.find(
|
||||
(a) => a.id === linkAccountId && a.isConnected,
|
||||
);
|
||||
@@ -1386,11 +1388,24 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
if (!activeClient) return;
|
||||
|
||||
if (link.kind === 'folder') {
|
||||
const mailboxId = resolveFolderRef(link.ref, useEmailStore.getState().mailboxes);
|
||||
const state = useEmailStore.getState();
|
||||
const mailboxId = resolveFolderRef(link.ref, state.mailboxes);
|
||||
if (!mailboxId) {
|
||||
toast.error(t('deep_link.folder_not_found'));
|
||||
return;
|
||||
}
|
||||
// Reloading the page lands here with the folder the boot fetch has just
|
||||
// loaded; selecting it again re-fetched it under the loading overlay,
|
||||
// so the list flashed right after it appeared.
|
||||
if (opts?.onLoad && !switchesAccount && isFolderLinkOpen(mailboxId, {
|
||||
selectedMailbox: state.selectedMailbox,
|
||||
isUnifiedView: state.isUnifiedView,
|
||||
isScheduledView: state.isScheduledView,
|
||||
selectedKeyword: state.selectedKeyword,
|
||||
hasSearch: !!state.searchQuery || !isFilterEmpty(state.searchFilters),
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
await handleMailboxSelect(mailboxId);
|
||||
return;
|
||||
}
|
||||
@@ -1548,7 +1563,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
deepLinkHandledRef.current = true;
|
||||
|
||||
if (link) {
|
||||
void applyMailDeepLinkRef.current(link);
|
||||
void applyMailDeepLinkRef.current(link, { onLoad: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import {
|
||||
buildFolderRef,
|
||||
resolveFolderRef,
|
||||
isFolderLinkOpen,
|
||||
buildMailPath,
|
||||
parseMailPath,
|
||||
buildCalendarPath,
|
||||
@@ -121,6 +122,37 @@ describe('mail folder references', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFolderLinkOpen', () => {
|
||||
const plainInbox = {
|
||||
selectedMailbox: 'a',
|
||||
isUnifiedView: false,
|
||||
isScheduledView: false,
|
||||
selectedKeyword: null,
|
||||
hasSearch: false,
|
||||
};
|
||||
|
||||
it('treats the folder the list already shows as open (page reload)', () => {
|
||||
expect(isFolderLinkOpen(resolveFolderRef('inbox', MAILBOXES)!, plainInbox)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not treat another folder as open', () => {
|
||||
expect(isFolderLinkOpen('b', plainInbox)).toBe(false);
|
||||
});
|
||||
|
||||
it('does not treat the folder as open while a tag, search or special view covers it', () => {
|
||||
expect(isFolderLinkOpen('a', { ...plainInbox, selectedKeyword: 'work' })).toBe(false);
|
||||
expect(isFolderLinkOpen('a', { ...plainInbox, hasSearch: true })).toBe(false);
|
||||
expect(isFolderLinkOpen('a', { ...plainInbox, isUnifiedView: true })).toBe(false);
|
||||
expect(isFolderLinkOpen('a', { ...plainInbox, isScheduledView: true })).toBe(false);
|
||||
});
|
||||
|
||||
it('never treats a virtual view as open - the boot fetch does not load them', () => {
|
||||
for (const id of [UNIFIED_MAILBOX_IDS.inbox, CROSS_VIEW_IDS.unread, SCHEDULED_MAILBOX_ID]) {
|
||||
expect(isFolderLinkOpen(id, { ...plainInbox, selectedMailbox: id })).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('mail paths', () => {
|
||||
it('round-trips folder, message and thread links', () => {
|
||||
const folder = buildMailPath({ mailboxId: 'c', emailId: null, threadId: null }, MAILBOXES);
|
||||
|
||||
@@ -190,6 +190,35 @@ export function resolveFolderRef(ref: string, mailboxes: Mailbox[] = []): string
|
||||
return decoded || null;
|
||||
}
|
||||
|
||||
/** The parts of the mail list's state that decide what a folder link shows. */
|
||||
export interface OpenFolderView {
|
||||
selectedMailbox: string;
|
||||
isUnifiedView: boolean;
|
||||
isScheduledView: boolean;
|
||||
selectedKeyword: string | null;
|
||||
hasSearch: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a folder link names the plain folder the list already shows.
|
||||
*
|
||||
* The address bar carries the open folder (`buildMailPath`), so reloading the
|
||||
* page hands that folder back as a link once the boot fetch has loaded it.
|
||||
* Selecting it again would only fetch the same list a second time under the
|
||||
* loading overlay. Virtual views are never "already open" at boot: the boot
|
||||
* fetch does not load them.
|
||||
*/
|
||||
export function isFolderLinkOpen(mailboxId: string, view: OpenFolderView): boolean {
|
||||
if (VIRTUAL_ALIAS_BY_ID[mailboxId]) return false;
|
||||
return (
|
||||
mailboxId === view.selectedMailbox &&
|
||||
!view.isUnifiedView &&
|
||||
!view.isScheduledView &&
|
||||
!view.selectedKeyword &&
|
||||
!view.hasSearch
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The canonical path for the current mail view. Returns `/mail` for the bare
|
||||
* list so the address bar stays clean when nothing is open.
|
||||
|
||||
Reference in New Issue
Block a user