|
|
|
|
@@ -31,7 +31,7 @@ export default function MailboxPage({ settings }: { settings: ApiSettings }) {
|
|
|
|
|
const [messages, setMessages] = useState<MailMailboxMessageSummary[]>([]);
|
|
|
|
|
const [messageTotalCount, setMessageTotalCount] = useState<number | null>(null);
|
|
|
|
|
const [messagePage, setMessagePage] = useState(1);
|
|
|
|
|
const [messagePageSize, setMessagePageSize] = useState(50);
|
|
|
|
|
const [messagePageSize, setMessagePageSize] = useState(10);
|
|
|
|
|
const [messageQuery, setMessageQuery] = useState("");
|
|
|
|
|
const [selectedMessage, setSelectedMessage] = useState<MailMailboxMessageDetail | null>(null);
|
|
|
|
|
const [selectedMessageKeyState, setSelectedMessageKeyState] = useState("");
|
|
|
|
|
@@ -71,6 +71,37 @@ export default function MailboxPage({ settings }: { settings: ApiSettings }) {
|
|
|
|
|
useEffect(() => { if (selectedProfileId) void loadFolders(selectedProfileId); }, [selectedProfileId]);
|
|
|
|
|
useEffect(() => { if (selectedProfileId && selectedFolder && foldersReady) void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize); }, [foldersReady, messagePage, messagePageSize, selectedProfileId, selectedFolder]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handlePreviewShortcut = (event: KeyboardEvent) => {
|
|
|
|
|
if (isEditableTarget(event.target)) return;
|
|
|
|
|
if (event.key === "Escape") {
|
|
|
|
|
if (selectedMessage || selectedMessageKey || pendingMessageKey || detailError) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
setSelectedMessage(null);
|
|
|
|
|
setSelectedMessageKeyState("");
|
|
|
|
|
selectedMessageKeyRef.current = "";
|
|
|
|
|
setPendingMessageKey("");
|
|
|
|
|
setDetailError("");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (shellBusy || loadingMessage || filteredMessages.length === 0) return;
|
|
|
|
|
const selectedIndex = filteredMessages.findIndex((message) => mailboxMessageKey(message.folder || selectedFolder, message.uid) === selectedMessageKey);
|
|
|
|
|
let nextIndex: number | null = null;
|
|
|
|
|
if (event.key === "ArrowLeft") nextIndex = selectedIndex > 0 ? selectedIndex - 1 : 0;
|
|
|
|
|
else if (event.key === "ArrowRight") nextIndex = selectedIndex >= 0 ? Math.min(filteredMessages.length - 1, selectedIndex + 1) : 0;
|
|
|
|
|
else if (event.key === "Home") nextIndex = 0;
|
|
|
|
|
else if (event.key === "End") nextIndex = filteredMessages.length - 1;
|
|
|
|
|
if (nextIndex === null) return;
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
const nextMessage = filteredMessages[nextIndex];
|
|
|
|
|
if (nextMessage) void openMessage(nextMessage);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener("keydown", handlePreviewShortcut);
|
|
|
|
|
return () => window.removeEventListener("keydown", handlePreviewShortcut);
|
|
|
|
|
}, [detailError, filteredMessages, loadingMessage, pendingMessageKey, selectedFolder, selectedMessage, selectedMessageKey, shellBusy]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const ancestorIds = folderAncestorIds(folderTree, selectedFolder);
|
|
|
|
|
if (ancestorIds.length === 0) return;
|
|
|
|
|
@@ -458,7 +489,7 @@ function MailboxPagination({ page, pageSize, totalRows, disabled, onPageChange,
|
|
|
|
|
const pageCount = Math.max(1, Math.ceil(totalRows / pageSize));
|
|
|
|
|
const first = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
|
|
|
const last = Math.min(totalRows, page * pageSize);
|
|
|
|
|
const options = [25, 50, 100];
|
|
|
|
|
const options = [10, 25, 50, 100];
|
|
|
|
|
return (
|
|
|
|
|
<div className="data-grid-pagination mailbox-pagination" aria-label="Mailbox message pagination">
|
|
|
|
|
<div className="data-grid-pagination-summary">{first}-{last} of {totalRows}</div>
|
|
|
|
|
@@ -549,3 +580,8 @@ function formatBytes(value?: number | null): string {
|
|
|
|
|
function errorText(error: unknown): string {
|
|
|
|
|
return error instanceof Error ? error.message : String(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isEditableTarget(target: EventTarget | null): boolean {
|
|
|
|
|
if (!(target instanceof HTMLElement)) return false;
|
|
|
|
|
return target.isContentEditable || ["INPUT", "SELECT", "TEXTAREA"].includes(target.tagName);
|
|
|
|
|
}
|
|
|
|
|
|