refactor(mail): 精简邮件通知与元信息展示
- 点击新邮件提示可直接打开首封新邮件,并兼容键盘回车与空格操作。 - 移除邮件详情中的认证结果展示及相关类型定义,简化消息元信息结构。
This commit is contained in:
@@ -59,11 +59,9 @@ export type Alias = { id: string; domainId: string; source: string; destination:
|
|||||||
export type MailFolder = { id: string; name: string; role: string; unreadCount: number; totalCount: number; uidValidity: number; uidNext: number; highestModseq: number }
|
export type MailFolder = { id: string; name: string; role: string; unreadCount: number; totalCount: number; uidValidity: number; uidNext: number; highestModseq: number }
|
||||||
export type Attachment = { id: string; messageId: string; filename: string; contentType: string; sizeBytes: number; createdAt: string }
|
export type Attachment = { id: string; messageId: string; filename: string; contentType: string; sizeBytes: number; createdAt: string }
|
||||||
export type MailLabel = { id: string; mailboxId?: string; name: string; color: string; messageCount?: number }
|
export type MailLabel = { id: string; mailboxId?: string; name: string; color: string; messageCount?: number }
|
||||||
export type MailAuthentication = { authenticationResults: string; receivedSpf: string; spf: string; dkim: string; dmarc: string }
|
|
||||||
export type MailMessage = {
|
export type MailMessage = {
|
||||||
id: string; mailboxId?: string; mailboxAddress?: string; ownerEmail?: string; recipientAddress?: string; folderId: string; folder: string; messageUid: string; imapUid: number; imapModseq: number; messageId: string; subject: string; from: string; fromName?: string; to: string[]; cc: string[]; bcc?: string[]; sentAt: string; receivedAt: string; snippet: string; bodyText?: string; bodyHtml?: string; isRead: boolean; isStarred: boolean; hasAttachments: boolean; sizeBytes: number; attachments?: Attachment[]
|
id: string; mailboxId?: string; mailboxAddress?: string; ownerEmail?: string; recipientAddress?: string; folderId: string; folder: string; messageUid: string; imapUid: number; imapModseq: number; messageId: string; subject: string; from: string; fromName?: string; to: string[]; cc: string[]; bcc?: string[]; sentAt: string; receivedAt: string; snippet: string; bodyText?: string; bodyHtml?: string; isRead: boolean; isStarred: boolean; hasAttachments: boolean; sizeBytes: number; attachments?: Attachment[]
|
||||||
labels?: MailLabel[]
|
labels?: MailLabel[]
|
||||||
authentication?: MailAuthentication
|
|
||||||
}
|
}
|
||||||
export type DNSRecord = { type: string; name: string; value: string; ttl: number }
|
export type DNSRecord = { type: string; name: string; value: string; ttl: number }
|
||||||
export type DNSCheckResult = { domain: string; status: string; checks: Record<string, { ok: boolean; message: string; found?: string[] }> }
|
export type DNSCheckResult = { domain: string; status: string; checks: Record<string, { ok: boolean; message: string; found?: string[] }> }
|
||||||
|
|||||||
+19
-40
@@ -394,7 +394,24 @@ export function MailPage() {
|
|||||||
const firstSender = senderDisplayName(first)
|
const firstSender = senderDisplayName(first)
|
||||||
const title = newMessages.length > 1 ? `收到 ${newMessages.length} 封新邮件` : `新邮件:${first.subject || "(无主题)"}`
|
const title = newMessages.length > 1 ? `收到 ${newMessages.length} 封新邮件` : `新邮件:${first.subject || "(无主题)"}`
|
||||||
const description = newMessages.length > 1 ? `${firstSender} 等发来新邮件` : `${firstSender}${first.snippet ? ` · ${first.snippet}` : ""}`
|
const description = newMessages.length > 1 ? `${firstSender} 等发来新邮件` : `${firstSender}${first.snippet ? ` · ${first.snippet}` : ""}`
|
||||||
toast({ title, description })
|
const openFirstMessage = () => {
|
||||||
|
setMailView("folder")
|
||||||
|
setFolder("Inbox")
|
||||||
|
setSelectedId(first.id)
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
onClick: openFirstMessage,
|
||||||
|
onKeyDown: (event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault()
|
||||||
|
openFirstMessage()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
role: "button",
|
||||||
|
tabIndex: 0,
|
||||||
|
})
|
||||||
playIncomingMailSound(mailAudioContextRef)
|
playIncomingMailSound(mailAudioContextRef)
|
||||||
if ("Notification" in window && Notification.permission === "granted") {
|
if ("Notification" in window && Notification.permission === "granted") {
|
||||||
const notification = new Notification(title, {
|
const notification = new Notification(title, {
|
||||||
@@ -403,9 +420,7 @@ export function MailPage() {
|
|||||||
})
|
})
|
||||||
notification.onclick = () => {
|
notification.onclick = () => {
|
||||||
window.focus()
|
window.focus()
|
||||||
setMailView("folder")
|
openFirstMessage()
|
||||||
setFolder("Inbox")
|
|
||||||
setSelectedId(first.id)
|
|
||||||
notification.close()
|
notification.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1963,7 +1978,6 @@ function MessageMetaPanel({ message, availableLabels, onAddLabel, onRemoveLabel,
|
|||||||
<MessageMetaRow label="接收时间">
|
<MessageMetaRow label="接收时间">
|
||||||
<span>{formatDateTime(message.receivedAt)}</span>
|
<span>{formatDateTime(message.receivedAt)}</span>
|
||||||
</MessageMetaRow>
|
</MessageMetaRow>
|
||||||
<AuthenticationResultRow message={message} />
|
|
||||||
{availableLabels && onAddLabel && onRemoveLabel && (
|
{availableLabels && onAddLabel && onRemoveLabel && (
|
||||||
<MessageMetaRow label="标签">
|
<MessageMetaRow label="标签">
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
@@ -2025,41 +2039,6 @@ function MessageMetaPanel({ message, availableLabels, onAddLabel, onRemoveLabel,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AuthenticationResultRow({ message }: { message: MailMessage }) {
|
|
||||||
const auth = message.authentication || { authenticationResults: "", receivedSpf: "", spf: "unknown", dkim: "unknown", dmarc: "unknown" }
|
|
||||||
const title = [auth.authenticationResults, auth.receivedSpf].filter(Boolean).join("\n\n")
|
|
||||||
return (
|
|
||||||
<MessageMetaRow label="Auth">
|
|
||||||
<div className="flex flex-wrap gap-1.5" title={title || undefined}>
|
|
||||||
<AuthStatusBadge label="SPF" value={auth.spf} />
|
|
||||||
<AuthStatusBadge label="DKIM" value={auth.dkim} />
|
|
||||||
<AuthStatusBadge label="DMARC" value={auth.dmarc} />
|
|
||||||
</div>
|
|
||||||
</MessageMetaRow>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AuthStatusBadge({ label, value }: { label: string; value?: string }) {
|
|
||||||
const status = normalizeAuthStatus(value)
|
|
||||||
return (
|
|
||||||
<Badge variant="outline" className={cn("rounded-md font-mono text-[11px] font-normal", authStatusClassName(status))}>
|
|
||||||
{label}:{status}
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeAuthStatus(value?: string) {
|
|
||||||
const status = (value || "").trim().toLowerCase()
|
|
||||||
if (["pass", "fail", "softfail", "neutral", "temperror", "permerror", "none"].includes(status)) return status
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
function authStatusClassName(status: string) {
|
|
||||||
if (status === "pass") return "border-emerald-300 bg-emerald-50 text-emerald-700"
|
|
||||||
if (["fail", "softfail", "permerror"].includes(status)) return "border-red-300 bg-red-50 text-red-700"
|
|
||||||
return "border-slate-300 bg-slate-50 text-slate-600"
|
|
||||||
}
|
|
||||||
|
|
||||||
function MessageMetaRow({ label, children }: { label: string; children: React.ReactNode }) {
|
function MessageMetaRow({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-1 sm:grid-cols-[5rem_minmax(0,1fr)]">
|
<div className="grid gap-1 sm:grid-cols-[5rem_minmax(0,1fr)]">
|
||||||
|
|||||||
Reference in New Issue
Block a user