- respect charset from Content-Type when decoding body and headers - detect inline attachments with filenames (not just disposition: attachment) - fix broken decodeQuotedPrintable reference in parseAttachments - white background for email iframe (emails designed for light bg) - sticky header in email list, scrollable message area - arrow key navigation between emails with scroll-into-view - /email/:id routing for deep linking to specific emails - empty folder shows "No emails in this folder" instead of losing header - fix attachment viewer header overlap with dialog close button Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { useMemo, useCallback, useEffect } from 'react';
|
|
import { useParams, useNavigate } from 'react-router';
|
|
import type { LayoutNode, PanelComponents } from 'officerdev';
|
|
import { WorkspaceView } from 'officerdev';
|
|
import { useDashboardState } from 'state/useDashboardState';
|
|
import { useIsMobile } from 'hooks/useIsMobile';
|
|
import { useGlobal } from 'hooks/useGlobal';
|
|
import { defaultLayout } from './defaultLayout';
|
|
import { EmailList } from './EmailList';
|
|
import { EmailReader } from './EmailReader';
|
|
|
|
const PROMPT_PREFIX = `You are an email assistant. The user has a local SQLite email database available via the "email-db" tool — use it for all email queries (search, count, stats, aggregations, deletions) unless the user explicitly asks you to use Gmail. Do not use the Gmail integration for questions about existing emails.`;
|
|
|
|
export const EmailScreen = () => {
|
|
const isMobile = useIsMobile();
|
|
const { emailId } = useParams<{ emailId?: string }>();
|
|
const navigate = useNavigate();
|
|
const [selectedId, setSelectedId] = useGlobal<string | null>('EMAIL_SELECTED', null);
|
|
|
|
// Sync URL param → global state
|
|
useEffect(() => {
|
|
if (emailId && emailId !== selectedId) setSelectedId(emailId);
|
|
}, [emailId]);
|
|
|
|
// Sync global state → URL
|
|
useEffect(() => {
|
|
if (selectedId && selectedId !== emailId) {
|
|
navigate(`/email/${selectedId}`, { replace: true });
|
|
} else if (!selectedId && emailId) {
|
|
navigate('/email', { replace: true });
|
|
}
|
|
}, [selectedId]);
|
|
const workspace = useDashboardState<LayoutNode>('screens/email', defaultLayout);
|
|
|
|
const components: PanelComponents = useMemo(
|
|
() => ({
|
|
'email-list': EmailList,
|
|
'email-reader': EmailReader,
|
|
}),
|
|
[],
|
|
);
|
|
|
|
const mobilePanelId = isMobile && selectedId ? 'email-reader' : undefined;
|
|
const onMobileBack = useCallback(() => setSelectedId(null), [setSelectedId]);
|
|
|
|
return (
|
|
<div className="h-full w-full pt-2">
|
|
<WorkspaceView
|
|
workspace={workspace}
|
|
locked
|
|
components={components}
|
|
promptPrefix={PROMPT_PREFIX}
|
|
mobilePanelId={mobilePanelId}
|
|
onMobilePanelChange={mobilePanelId ? () => onMobileBack() : undefined}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|