fix(mail): remove duplicate toolbar, fix iframe height, move attachments into scrollable body

- Remove duplicate action bar from MailDetail (actions are in global plugin toolbar)
- Change iframe sandbox from "" to "allow-same-origin" so height auto-resize works
- Move attachments inside the scrollable body container so they are visible
- Remove unused imports (clsx, Badge) and unused destructured props
This commit is contained in:
Agent Zero
2026-07-16 09:09:15 +02:00
parent 467f73113c
commit 560e4ac69d
+8 -98
View File
@@ -1,14 +1,13 @@
/**
* Mail detail reading pane.
* Shows mail headers, sanitized HTML body, attachments, reply/forward/create-event actions.
* Shows mail headers, sanitized HTML body, and attachments.
* Actions (reply/forward/delete/etc.) are in the global plugin toolbar.
*/
import React, { useMemo, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import clsx from 'clsx';
import type { Mail, MailAttachment } from '@/api/mail';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { EmptyState } from '@/components/ui/EmptyState';
export interface MailDetailProps {
@@ -44,15 +43,8 @@ function formatBytes(bytes: number): string {
export function MailDetail({
mail,
loading,
onReply,
onForward,
onCreateEvent,
onToggleFlag,
onDownloadAttachment,
downloadingAttachmentId,
onDelete,
onMove,
onEditDraft,
}: MailDetailProps) {
const { t } = useTranslation();
@@ -102,90 +94,6 @@ export function MailDetail({
return (
<div className="flex flex-col h-full" data-testid="mail-detail">
{/* Toolbar */}
<div className="flex items-center gap-1 sm:gap-2 px-3 py-2 md:px-4 md:py-3 border-b border-secondary-200 overflow-x-auto" data-testid="mail-detail-toolbar">
{mail.is_draft && onEditDraft ? (
<Button variant="secondary" size="sm" onClick={() => onEditDraft(mail)} icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
}>
{t('mail.editDraft')}
</Button>
) : (
<>
<Button variant="secondary" size="sm" onClick={() => onReply(mail)} icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
</svg>
}>
{t('mail.reply')}
</Button>
<Button variant="secondary" size="sm" onClick={() => onForward(mail)} icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 10H11a8 8 0 00-8 8v2m18-10l-6 6m6-6l-6-6" />
</svg>
}>
{t('mail.forward')}
</Button>
</>
)}
<Button
variant="ghost"
size="sm"
onClick={() => onToggleFlag(mail)}
aria-label={t('mail.toggleFlag')}
icon={
<svg className={clsx('w-4 h-4', mail.is_flagged ? 'text-warning-500' : 'text-secondary-400')} fill={mail.is_flagged ? 'currentColor' : 'none'} viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
}
>
{mail.is_flagged ? t('mail.unflag') : t('mail.flag')}
</Button>
{onMove && (
<Button
variant="ghost"
size="sm"
onClick={() => onMove(mail)}
icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
}
>
{t('mail.move')}
</Button>
)}
<div className="flex-1" />
<Button
variant="ghost"
size="sm"
onClick={() => onCreateEvent(mail)}
icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
}
>
{t('mail.createEvent')}
</Button>
{onDelete && (
<Button
variant="ghost"
size="sm"
onClick={() => onDelete(mail)}
icon={
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
}
>
{t('mail.delete')}
</Button>
)}
</div>
{/* Headers */}
<div className="px-3 py-2 md:px-4 md:py-3 border-b border-secondary-200" data-testid="mail-detail-headers">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2 sm:gap-4">
@@ -222,13 +130,14 @@ export function MailDetail({
</div>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto px-3 py-3 md:px-4 md:py-4" data-testid="mail-detail-body">
{/* Body + Attachments — scrollable together */}
<div className="flex-1 overflow-y-auto" data-testid="mail-detail-body">
<div className="px-3 py-3 md:px-4 md:py-4">
{safeHtml ? (
<iframe
ref={iframeRef}
srcDoc={safeHtml}
sandbox=""
sandbox="allow-same-origin"
onLoad={handleIframeLoad}
className="w-full border-0"
data-testid="mail-html-body"
@@ -241,7 +150,7 @@ export function MailDetail({
{/* Attachments */}
{mail.attachments && mail.attachments.length > 0 && (
<div className="px-3 py-2 md:px-4 md:py-3 border-t border-secondary-200" data-testid="mail-detail-attachments">
<div className="px-3 py-3 md:px-4 md:py-4 border-t border-secondary-200" data-testid="mail-detail-attachments">
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('mail.attachments')}</h3>
<ul className="space-y-1 md:space-y-2">
{mail.attachments.map((att) => (
@@ -268,5 +177,6 @@ export function MailDetail({
</div>
)}
</div>
</div>
);
}