feat: project EmailReadView for email-banner slot, expose auth results
This commit is contained in:
+1
-19
@@ -60,25 +60,7 @@ import { useConfig } from "@/hooks/use-config";
|
|||||||
import { usePluginStore } from "@/stores/plugin-store";
|
import { usePluginStore } from "@/stores/plugin-store";
|
||||||
import { useThemeStore } from "@/stores/theme-store";
|
import { useThemeStore } from "@/stores/theme-store";
|
||||||
import { appLifecycleHooks, uiHooks, routerHooks, toastHooks, emailHooks } from "@/lib/plugin-hooks";
|
import { appLifecycleHooks, uiHooks, routerHooks, toastHooks, emailHooks } from "@/lib/plugin-hooks";
|
||||||
import type { EmailReadView } from "@/lib/plugin-types";
|
import { emailToReadView } from "@/lib/plugin-projection";
|
||||||
|
|
||||||
function emailToReadView(email: Email): EmailReadView {
|
|
||||||
return {
|
|
||||||
id: email.id,
|
|
||||||
threadId: email.threadId,
|
|
||||||
mailboxIds: Object.keys(email.mailboxIds || {}).filter(k => email.mailboxIds[k]),
|
|
||||||
from: (email.from || []).map(a => ({ name: a.name || '', email: a.email })),
|
|
||||||
to: (email.to || []).map(a => ({ name: a.name || '', email: a.email })),
|
|
||||||
cc: (email.cc || []).map(a => ({ name: a.name || '', email: a.email })),
|
|
||||||
subject: email.subject || '',
|
|
||||||
receivedAt: email.receivedAt,
|
|
||||||
isRead: !!email.keywords?.['$seen'],
|
|
||||||
isFlagged: !!email.keywords?.['$flagged'],
|
|
||||||
hasAttachment: email.hasAttachment,
|
|
||||||
preview: email.preview || '',
|
|
||||||
keywords: Object.keys(email.keywords || {}).filter(k => email.keywords[k]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Avatar } from "@/components/ui/avatar";
|
import { Avatar } from "@/components/ui/avatar";
|
||||||
import { formatFileSize, cn, buildMailboxTree, MailboxNode, formatDateTime, generateUUID } from "@/lib/utils";
|
import { formatFileSize, cn, buildMailboxTree, MailboxNode, formatDateTime, generateUUID } from "@/lib/utils";
|
||||||
import { getSecurityStatus, extractListHeaders } from "@/lib/email-headers";
|
import { getSecurityStatus, extractListHeaders } from "@/lib/email-headers";
|
||||||
|
import { emailToReadView } from "@/lib/plugin-projection";
|
||||||
import {
|
import {
|
||||||
Reply,
|
Reply,
|
||||||
ReplyAll,
|
ReplyAll,
|
||||||
@@ -4886,7 +4887,7 @@ export function EmailViewer({
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<PluginSlot name="email-banner" extraProps={{ email }} />
|
<PluginSlot name="email-banner" extraProps={{ email: emailToReadView(email) }} />
|
||||||
|
|
||||||
{/* Email Body */}
|
{/* Email Body */}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Projection helpers — convert host-internal types into the read-only views
|
||||||
|
// that plugins consume. Keeping this in one place ensures every slot/hook
|
||||||
|
// hands plugins the same shape declared in plugin-types.ts.
|
||||||
|
|
||||||
|
import type { Email } from '@/lib/jmap/types';
|
||||||
|
import type { EmailReadView } from '@/lib/plugin-types';
|
||||||
|
|
||||||
|
export function emailToReadView(email: Email): EmailReadView {
|
||||||
|
return {
|
||||||
|
id: email.id,
|
||||||
|
threadId: email.threadId,
|
||||||
|
mailboxIds: Object.keys(email.mailboxIds || {}).filter(k => email.mailboxIds[k]),
|
||||||
|
from: (email.from || []).map(a => ({ name: a.name || '', email: a.email })),
|
||||||
|
to: (email.to || []).map(a => ({ name: a.name || '', email: a.email })),
|
||||||
|
cc: (email.cc || []).map(a => ({ name: a.name || '', email: a.email })),
|
||||||
|
subject: email.subject || '',
|
||||||
|
receivedAt: email.receivedAt,
|
||||||
|
isRead: !!email.keywords?.['$seen'],
|
||||||
|
isFlagged: !!email.keywords?.['$flagged'],
|
||||||
|
hasAttachment: email.hasAttachment,
|
||||||
|
preview: email.preview || '',
|
||||||
|
keywords: Object.keys(email.keywords || {}).filter(k => email.keywords[k]),
|
||||||
|
auth: email.authenticationResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -300,6 +300,17 @@ export interface EmailReadView {
|
|||||||
hasAttachment: boolean;
|
hasAttachment: boolean;
|
||||||
preview: string;
|
preview: string;
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
|
/**
|
||||||
|
* Parsed Authentication-Results header (SPF, DKIM, DMARC, reverse-DNS).
|
||||||
|
* Absent on stores that didn't parse the header (e.g. bodies not yet
|
||||||
|
* fetched). Mirrors the structured shape exposed by the host.
|
||||||
|
*/
|
||||||
|
auth?: {
|
||||||
|
spf?: { result: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none' | 'temperror' | 'permerror'; domain?: string };
|
||||||
|
dkim?: { result: 'pass' | 'fail' | 'policy' | 'neutral' | 'temperror' | 'permerror'; domain?: string; selector?: string };
|
||||||
|
dmarc?: { result: 'pass' | 'fail' | 'none'; policy?: 'reject' | 'quarantine' | 'none'; domain?: string };
|
||||||
|
iprev?: { result: 'pass' | 'fail'; ip?: string };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DraftView {
|
export interface DraftView {
|
||||||
|
|||||||
Reference in New Issue
Block a user