intermittent commit

This commit is contained in:
2026-07-14 13:22:11 +02:00
parent 4865de5007
commit b0337b2d26
28 changed files with 2264 additions and 490 deletions

View File

@@ -11,8 +11,8 @@ import {
type ApiSettings
} from "@govoplan/core-webui";
import {
bootstrapMailbox,
getMailboxMessage,
listMailboxFolders,
listMailboxMessages,
listMailServerProfiles,
type MailImapFolderResponse,
@@ -50,6 +50,7 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
const messageListRequestRef = useRef(0);
const messageDetailRequestRef = useRef(0);
const mailboxPageCursorsRef = useRef<Record<string, string | null>>({});
const skipNextMessageLoadRef = useRef(false);
const selectedProfile = profiles.find((profile) => profile.id === selectedProfileId) ?? null;
const imapProfiles = useMemo(() => profiles.filter((profile) => profile.is_active && profile.imap), [profiles]);
@@ -70,8 +71,15 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
useEffect(() => {void loadProfiles();}, [settings.apiBaseUrl, settings.apiKey, settings.accessToken]);
useEffect(() => {selectedMessageKeyRef.current = selectedMessageKeyState;}, [selectedMessageKeyState]);
useEffect(() => {if (messagePage > messagePageCount) setMessagePage(messagePageCount);}, [messagePage, messagePageCount]);
useEffect(() => {if (selectedProfileId) void loadFolders(selectedProfileId);}, [selectedProfileId]);
useEffect(() => {if (selectedProfileId && selectedFolder && foldersReady) void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize);}, [foldersReady, messagePage, messagePageSize, selectedProfileId, selectedFolder]);
useEffect(() => {if (selectedProfileId) void loadMailboxBootstrap(selectedProfileId);}, [selectedProfileId]);
useEffect(() => {
if (!selectedProfileId || !selectedFolder || !foldersReady) return;
if (skipNextMessageLoadRef.current) {
skipNextMessageLoadRef.current = false;
return;
}
void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize);
}, [foldersReady, messagePage, messagePageSize, selectedProfileId, selectedFolder]);
useEffect(() => {
const handlePreviewShortcut = (event: KeyboardEvent) => {
@@ -130,6 +138,8 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
setSelectedMessage(null);
setSelectedMessageKeyState("");
setPendingMessageKey("");
mailboxPageCursorsRef.current = {};
skipNextMessageLoadRef.current = false;
}
} catch (err) {
setError(errorText(err));
@@ -138,52 +148,70 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
}
}
async function loadFolders(profileId = selectedProfileId) {
async function loadMailboxBootstrap(profileId = selectedProfileId, refresh = false) {
if (!profileId) return;
const requestId = ++folderRequestRef.current;
messageListRequestRef.current += 1;
const folderRequestId = ++folderRequestRef.current;
const messageRequestId = ++messageListRequestRef.current;
messageDetailRequestRef.current += 1;
setLoadingFolders(true);
setLoadingMessages(true);
setFoldersLoadedForProfile("");
setMessageTotalCount(null);
setMessagePage(1);
setSelectedMessage(null);
setSelectedMessageKeyState("");
selectedMessageKeyRef.current = "";
setPendingMessageKey("");
setFolderError("");
setMessageError("");
setDetailError("");
setError("");
try {
const response = await listMailboxFolders(settings, profileId);
if (requestId !== folderRequestRef.current) return;
if (!response.ok) throw new Error(response.message || "i18n:govoplan-mail.mailbox_folders_could_not_be_loaded.c3e3880e");
const loaded = response.folders?.length ? response.folders : [{ name: "INBOX", flags: [] }];
setFolders(loaded);
setExpandedFolders(new Set());
let nextFolder = selectedFolder;
if (!nextFolder || !loaded.some((folder) => folder.name === nextFolder)) {
nextFolder = loaded.some((folder) => folder.name === "INBOX") ? "INBOX" : response.detected_sent_folder || loaded[0]?.name || "INBOX";
const response = await bootstrapMailbox(settings, profileId, selectedFolder || "INBOX", messagePageSize, 0, refresh);
if (folderRequestId !== folderRequestRef.current || messageRequestId !== messageListRequestRef.current) return;
if (!response.folders.ok) throw new Error(response.folders.message || "i18n:govoplan-mail.mailbox_folders_could_not_be_loaded.c3e3880e");
const loadedFolders = response.folders.folders?.length ? response.folders.folders : [{ name: "INBOX", flags: [] }];
const loadedMessages = response.messages.messages ?? [];
const total = response.messages.total_count ?? loadedMessages.length;
let nextFolder = response.folder || response.messages.folder || selectedFolder || "INBOX";
if (!loadedFolders.some((folder) => folder.name === nextFolder)) {
nextFolder = loadedFolders.some((folder) => folder.name === "INBOX") ? "INBOX" : response.folders.detected_sent_folder || loadedFolders[0]?.name || "INBOX";
}
const foldersWithCounts = loadedFolders.map((folder) => folder.name === nextFolder ? { ...folder, message_count: total } : folder);
const cursorKey = mailboxCursorKey(profileId, nextFolder, messagePageSize);
mailboxPageCursorsRef.current = {
[`${cursorKey}:1`]: null,
[`${cursorKey}:2`]: response.messages.next_cursor ?? null
};
skipNextMessageLoadRef.current = foldersLoadedForProfile !== profileId || nextFolder !== selectedFolder || messagePage !== 1;
setFolders(foldersWithCounts);
setExpandedFolders(new Set());
setSelectedFolder(nextFolder);
setFoldersLoadedForProfile(profileId);
setMessages(loadedMessages);
setMessageTotalCount(total);
} catch (err) {
if (requestId !== folderRequestRef.current) return;
setFolderError(errorText(err));
setError(errorText(err));
if (folderRequestId !== folderRequestRef.current || messageRequestId !== messageListRequestRef.current) return;
const message = errorText(err);
skipNextMessageLoadRef.current = false;
setFolderError(message);
setMessageError(message);
setError(message);
setFolders([]);
setFoldersLoadedForProfile("");
setMessages([]);
setMessageTotalCount(null);
setSelectedMessage(null);
setSelectedMessageKeyState("");
selectedMessageKeyRef.current = "";
setPendingMessageKey("");
} finally {
if (requestId === folderRequestRef.current) setLoadingFolders(false);
if (folderRequestId === folderRequestRef.current) setLoadingFolders(false);
if (messageRequestId === messageListRequestRef.current) setLoadingMessages(false);
}
}
async function loadMessages(profileId = selectedProfileId, folder = selectedFolder, page = messagePage, pageSize = messagePageSize) {
async function loadMessages(profileId = selectedProfileId, folder = selectedFolder, page = messagePage, pageSize = messagePageSize, refresh = false) {
if (!profileId || !folder) return;
const requestId = ++messageListRequestRef.current;
const offset = (Math.max(1, page) - 1) * pageSize;
@@ -194,12 +222,12 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
setDetailError("");
setError("");
try {
const response = await listMailboxMessages(settings, profileId, folder, pageSize, offset, cursor);
const response = await listMailboxMessages(settings, profileId, folder, pageSize, offset, cursor, refresh);
if (requestId !== messageListRequestRef.current) return;
const loaded = response.messages ?? [];
const total = response.total_count ?? loaded.length;
if (page <= 1) mailboxPageCursorsRef.current[`${cursorKey}:1`] = null;
if (response.next_cursor) mailboxPageCursorsRef.current[`${cursorKey}:${page + 1}`] = response.next_cursor;
mailboxPageCursorsRef.current[`${cursorKey}:${page + 1}`] = response.next_cursor ?? null;
setMessages(loaded);
setMessageTotalCount(total);
setFolderMessageCount(folder, total);
@@ -271,6 +299,8 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
setLoadingFolders(false);
setLoadingMessages(false);
setLoadingMessage(false);
mailboxPageCursorsRef.current = {};
skipNextMessageLoadRef.current = false;
}
function openFolderNode(node: MailFolderNode) {
@@ -365,11 +395,11 @@ export default function MailboxPage({ settings }: {settings: ApiSettings;}) {
<RefreshCw size={16} aria-hidden="true" />
i18n:govoplan-mail.profiles.0c2a9300
</Button>
<Button onClick={() => void loadFolders(selectedProfileId)} disabled={!selectedProfileId || loadingFolders} title="i18n:govoplan-mail.refresh_mailbox_folders.d9af9963">
<Button onClick={() => void loadMailboxBootstrap(selectedProfileId, true)} disabled={!selectedProfileId || loadingFolders || loadingMessages} title="i18n:govoplan-mail.refresh_mailbox_folders.d9af9963">
<RefreshCw size={16} aria-hidden="true" />
i18n:govoplan-mail.folders.19adc47b
</Button>
<Button onClick={() => void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize)} disabled={!selectedProfileId || !selectedFolder || !foldersReady || loadingMessages} title="i18n:govoplan-mail.refresh_messages_in_the_current_folder.b6546a2c">
<Button onClick={() => void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize, true)} disabled={!selectedProfileId || !selectedFolder || !foldersReady || loadingMessages} title="i18n:govoplan-mail.refresh_messages_in_the_current_folder.b6546a2c">
<RefreshCw size={16} aria-hidden="true" />
i18n:govoplan-mail.messages.f1702b46
</Button>