From eb2e3b2baa9f22486cf2dce9df7a26973e1f55a6 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 23:35:54 +0100 Subject: [PATCH] feat: make the attachment bar the default and retire attachments beside the sender --- .../viewer-attachment-layout.test.tsx | 10 + components/email/email-viewer.tsx | 175 +----------------- components/settings/reading-settings.tsx | 3 +- locales/ar/common.json | 1 - locales/ca/common.json | 1 - locales/cs/common.json | 1 - locales/da/common.json | 1 - locales/de/common.json | 3 +- locales/en/common.json | 3 +- locales/es/common.json | 1 - locales/et/common.json | 1 - locales/fa/common.json | 1 - locales/fr/common.json | 1 - locales/he/common.json | 1 - locales/hu/common.json | 1 - locales/it/common.json | 1 - locales/ja/common.json | 1 - locales/ko/common.json | 1 - locales/lv/common.json | 1 - locales/mn/common.json | 1 - locales/nb/common.json | 1 - locales/nl/common.json | 1 - locales/pl/common.json | 1 - locales/pt/common.json | 1 - locales/ro/common.json | 1 - locales/ru/common.json | 1 - locales/sk/common.json | 1 - locales/th/common.json | 1 - locales/tr/common.json | 1 - locales/uk/common.json | 1 - locales/vi/common.json | 1 - locales/zh-TW/common.json | 1 - locales/zh/common.json | 1 - .../settings-store-attachments.test.ts | 24 ++- stores/settings-store.ts | 23 ++- 35 files changed, 58 insertions(+), 211 deletions(-) diff --git a/components/email/__tests__/viewer-attachment-layout.test.tsx b/components/email/__tests__/viewer-attachment-layout.test.tsx index a2c8104b1..89189b5ab 100644 --- a/components/email/__tests__/viewer-attachment-layout.test.tsx +++ b/components/email/__tests__/viewer-attachment-layout.test.tsx @@ -73,6 +73,16 @@ describe('attachment position in the reader', () => { expect(screen.queryByTestId('attachments-jump')).toBeNull(); }); + it('draws the bar, and nothing beside the sender, for the retired beside-sender value', async () => { + useSettingsStore.setState({ attachmentPosition: 'beside-sender' as never }); + const { container } = render(); + await screen.findByText('Here is the invoice.'); + expect(container.querySelectorAll('[data-testid="attachment"]')).toHaveLength(2); + const sections = container.querySelectorAll('[data-testid="reader-attachments"]'); + expect(sections).toHaveLength(1); + expect((sections[0] as HTMLElement).dataset.layout).toBe('bar'); + }); + it('draws cards after the body, with a jump link in the header', async () => { useSettingsStore.setState({ attachmentPosition: 'below-message' }); const { container } = render(); diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index ddb75f8b4..d2f066a5b 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -13,7 +13,6 @@ import { applyMailTextSize, mailFitOptions } from "@/lib/mail-reading"; import { useMailReading } from "@/hooks/use-mail-reading"; import { useReadAloud } from "@/hooks/use-read-aloud"; import { ReadAloudBar } from "./read-aloud-bar"; -import { attachmentIcon } from "./attachment-chips"; import { ReaderAttachments, type AttachmentDragProps } from "./reader-attachments"; import { ReaderViewDialog } from "./reader-view-dialog"; import { ReadingMask } from "./reading-mask"; @@ -70,15 +69,12 @@ import { ChevronUp, ChevronLeft, ChevronRight, - Download, Mail, MailOpen, Loader2, Printer, FileText, - FileArchive, File, - Eye, Shield, Image, Tag, @@ -1545,7 +1541,6 @@ export function EmailViewer({ const resolvedTheme = useThemeStore((state) => state.resolvedTheme); const { startTour } = useTour(); const [showFullHeaders, setShowFullHeaders] = useState(false); - const [showAllBesideAttachments, setShowAllBesideAttachments] = useState(false); const [isDownloadingAll, setIsDownloadingAll] = useState(false); const [imageThumbUrls, setImageThumbUrls] = useState>({}); const [allowExternalContent, setAllowExternalContent] = useState(false); @@ -3063,21 +3058,6 @@ export function EmailViewer({ ), }; - // Shared "Download all" chip, shown only when bundling is worthwhile (2+). - const downloadAllButton = effectiveAttachments.length > 1 ? ( - - ) : null; - // Pre-fetch URLs for image attachments so their actual contents can be // rendered as thumbnails inside the chip. Skips images larger than 10 MB. // The thumbnail can be opened on its own (context menu, drag to the tab @@ -5068,154 +5048,6 @@ export function EmailViewer({ {effectiveAttachments.length} )} - {/* Attachments on the right (beside-sender mode) */} - {attachmentPosition === 'beside-sender' && effectiveAttachments.length > 0 && ( -
- {effectiveAttachments.slice(0, 2).map((attachment) => { - const { icon: FileIcon, className: fileIconClass } = attachmentIcon(attachment.type, attachment.name); - const isPreviewable = isAttachmentPreviewable(attachment); - const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; - const thumbUrl = imageThumbUrls[attachment.id]; - return ( - - {(dragProps) => (<> -
handleEffectiveAttachmentOpen(attachment)} - data-testid="attachment" - data-attachment-name={attachment.name} - draggable={dragProps.draggable} - onPointerEnter={dragProps.onPointerEnter} - onDragStart={dragProps.onDragStart} - onDragEnd={dragProps.onDragEnd} - > - {thumbUrl && ( -
- -
- )} -
- - - {getAttachmentDisplayName(attachment.name, attachment.type)} - - - {formatFileSize(attachment.size)} - -
-
- { e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); }} - > - - - {opensPreview && ( - { e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); }} - > - - - )} - {canSaveToFiles && attachment.blobId && ( - { e.stopPropagation(); setSaveToFilesTarget(attachment); }}> - - - )} - -
-
- - )} -
- ); - })} - {effectiveAttachments.length > 2 && ( - - )} - {downloadAllButton} - {/* Floating popup for remaining attachments */} - {showAllBesideAttachments && effectiveAttachments.length > 2 && ( - <> -
setShowAllBesideAttachments(false)} /> -
- {effectiveAttachments.slice(2).map((attachment) => { - const { icon: FileIcon, className: fileIconClass } = attachmentIcon(attachment.type, attachment.name); - const isPreviewable = isAttachmentPreviewable(attachment); - const opensPreview = isPreviewable && mailAttachmentAction === 'preview'; - return ( - - {(dragProps) => (<> -
{ handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }} - draggable={dragProps.draggable} - onPointerEnter={dragProps.onPointerEnter} - onDragStart={dragProps.onDragStart} - onDragEnd={dragProps.onDragEnd} - > - - - {getAttachmentDisplayName(attachment.name, attachment.type)} - - - {formatFileSize(attachment.size)} - -
- { e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); setShowAllBesideAttachments(false); }} - > - - - {opensPreview && ( - { e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }} - > - - - )} - {canSaveToFiles && attachment.blobId && ( - { e.stopPropagation(); setSaveToFilesTarget(attachment); }}> - - - )} - -
-
- - )} -
- ); - })} -
- - )} -
- )}
@@ -5906,11 +5738,10 @@ export function EmailViewer({ - {/* === ATTACHMENTS: a bar of tiles under the header. Beside-sender - mode keeps its own column on wide screens; cards mode draws them - after the body instead. === */} + {/* === ATTACHMENTS: a bar of tiles under the header, unless cards + mode draws them after the body. === */} {attachmentPosition !== 'below-message' && effectiveAttachments.length > 0 && ( -
+
)} diff --git a/components/settings/reading-settings.tsx b/components/settings/reading-settings.tsx index 39cbd2790..7b782ec74 100644 --- a/components/settings/reading-settings.tsx +++ b/components/settings/reading-settings.tsx @@ -162,10 +162,9 @@ export function ReadingSettings() {