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:
@@ -1,14 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Mail detail reading pane.
|
* 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 React, { useMemo, useRef, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import clsx from 'clsx';
|
|
||||||
import type { Mail, MailAttachment } from '@/api/mail';
|
import type { Mail, MailAttachment } from '@/api/mail';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Badge } from '@/components/ui/Badge';
|
|
||||||
import { EmptyState } from '@/components/ui/EmptyState';
|
import { EmptyState } from '@/components/ui/EmptyState';
|
||||||
|
|
||||||
export interface MailDetailProps {
|
export interface MailDetailProps {
|
||||||
@@ -44,15 +43,8 @@ function formatBytes(bytes: number): string {
|
|||||||
export function MailDetail({
|
export function MailDetail({
|
||||||
mail,
|
mail,
|
||||||
loading,
|
loading,
|
||||||
onReply,
|
|
||||||
onForward,
|
|
||||||
onCreateEvent,
|
|
||||||
onToggleFlag,
|
|
||||||
onDownloadAttachment,
|
onDownloadAttachment,
|
||||||
downloadingAttachmentId,
|
downloadingAttachmentId,
|
||||||
onDelete,
|
|
||||||
onMove,
|
|
||||||
onEditDraft,
|
|
||||||
}: MailDetailProps) {
|
}: MailDetailProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -102,90 +94,6 @@ export function MailDetail({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full" data-testid="mail-detail">
|
<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 */}
|
{/* 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="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">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body + Attachments — scrollable together */}
|
||||||
<div className="flex-1 overflow-y-auto px-3 py-3 md:px-4 md:py-4" data-testid="mail-detail-body">
|
<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 ? (
|
{safeHtml ? (
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
srcDoc={safeHtml}
|
srcDoc={safeHtml}
|
||||||
sandbox=""
|
sandbox="allow-same-origin"
|
||||||
onLoad={handleIframeLoad}
|
onLoad={handleIframeLoad}
|
||||||
className="w-full border-0"
|
className="w-full border-0"
|
||||||
data-testid="mail-html-body"
|
data-testid="mail-html-body"
|
||||||
@@ -241,7 +150,7 @@ export function MailDetail({
|
|||||||
|
|
||||||
{/* Attachments */}
|
{/* Attachments */}
|
||||||
{mail.attachments && mail.attachments.length > 0 && (
|
{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>
|
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('mail.attachments')}</h3>
|
||||||
<ul className="space-y-1 md:space-y-2">
|
<ul className="space-y-1 md:space-y-2">
|
||||||
{mail.attachments.map((att) => (
|
{mail.attachments.map((att) => (
|
||||||
@@ -268,5 +177,6 @@ export function MailDetail({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user