feat: add "Download all" button to bundle attachments into a zip #466

This commit is contained in:
Linus Rath
2026-06-24 18:46:41 +02:00
parent 7a022596c3
commit ae5d397512
3 changed files with 115 additions and 3 deletions
+19
View File
@@ -6,6 +6,7 @@ import type { Email } from '@/lib/jmap/types';
import {
emailExportFilename,
attachmentDownloadFilename,
attachmentsBundleFilename,
bundleExportFilename,
emailVars,
attachmentVars,
@@ -93,6 +94,24 @@ describe('bundleExportFilename', () => {
});
});
describe('attachmentsBundleFilename', () => {
it('embeds the sanitised subject', () => {
expect(attachmentsBundleFilename(makeEmail({ subject: 'Invoice March' }))).toBe('attachments_Invoice March.zip');
});
it('sanitises filesystem-reserved characters', () => {
expect(attachmentsBundleFilename(makeEmail({ subject: 'Q1/Q2: report' }))).toBe('attachments_Q1_Q2_ report.zip');
});
it('falls back when the subject is empty', () => {
expect(attachmentsBundleFilename(makeEmail({ subject: '' }))).toBe('attachments.zip');
});
it('falls back when there is no email', () => {
expect(attachmentsBundleFilename(null)).toBe('attachments.zip');
});
});
describe('emailVars (date + address labels)', () => {
it('returns the invalid-date sentinel for an unparseable date', () => {
const v = emailVars(makeEmail({ receivedAt: 'not-a-date', sentAt: undefined }));
+9
View File
@@ -227,6 +227,15 @@ export function attachmentDownloadFilename(
return `${transformedStem}.${transformedExt}`;
}
// Name for a .zip bundling every attachment of a single email, e.g.
// `attachments_Invoice March.zip`. Falls back to `attachments.zip` when the
// subject is empty or sanitises away to nothing.
export function attachmentsBundleFilename(email: Email | null | undefined): string {
const subject = email?.subject?.trim();
const stem = subject ? sanitizePart(subject, FILENAME_MAX_LEN) : "";
return stem ? `attachments_${stem}.zip` : "attachments.zip";
}
export function bundleVars(count: number, iso?: string): Record<string, string> {
const dp = dateParts(iso ?? new Date().toISOString());
return { ...dp, count: String(count) };