feat: add Downloads settings tab with template editor for .eml and attachment filenames
This commit is contained in:
+14
-12
@@ -8,7 +8,8 @@ import { useAuthStore } from "@/stores/auth-store";
|
||||
import { useDragDropContext } from "@/contexts/drag-drop-context";
|
||||
import { useUIStore } from "@/stores/ui-store";
|
||||
import { isDragOutSupported } from "@/hooks/use-attachment-drag";
|
||||
import { emailExportFilename } from "@/lib/email-filename";
|
||||
import { emailExportFilename, DEFAULT_EMAIL_TEMPLATE } from "@/lib/download-filename";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
|
||||
interface UseEmailDragOptions {
|
||||
email: Email;
|
||||
@@ -70,7 +71,7 @@ function selectionKey(ids: string[]): string {
|
||||
return [...ids].sort().join(",");
|
||||
}
|
||||
|
||||
async function buildEmailZip(client: IJMAPClient, emails: Email[]): Promise<string | null> {
|
||||
async function buildEmailZip(client: IJMAPClient, emails: Email[], template: string): Promise<string | null> {
|
||||
const eligible = emails.filter((em) => !!em.blobId);
|
||||
if (eligible.length === 0) return null;
|
||||
const { default: JSZip } = await import("jszip");
|
||||
@@ -78,7 +79,7 @@ async function buildEmailZip(client: IJMAPClient, emails: Email[]): Promise<stri
|
||||
const used = new Set<string>();
|
||||
await Promise.all(
|
||||
eligible.map(async (em) => {
|
||||
const base = emailExportFilename(em).replace(/\.eml$/, "");
|
||||
const base = emailExportFilename(em, template).replace(/\.eml$/, "");
|
||||
let name = `${base}.eml`;
|
||||
while (used.has(name)) name = `${base} [${em.id.slice(0, 6)}].eml`;
|
||||
used.add(name);
|
||||
@@ -94,7 +95,7 @@ async function buildEmailZip(client: IJMAPClient, emails: Email[]): Promise<stri
|
||||
return URL.createObjectURL(zipBlob);
|
||||
}
|
||||
|
||||
function prefetchEmailBundle(client: IJMAPClient, emails: Email[]): void {
|
||||
function prefetchEmailBundle(client: IJMAPClient, emails: Email[], template: string): void {
|
||||
const key = selectionKey(emails.map((e) => e.id));
|
||||
if (currentBundle && currentBundle.key === key) return;
|
||||
if (currentBundle?.url) {
|
||||
@@ -107,7 +108,7 @@ function prefetchEmailBundle(client: IJMAPClient, emails: Email[]): void {
|
||||
url: null,
|
||||
promise: null,
|
||||
};
|
||||
entry.promise = buildEmailZip(client, emails)
|
||||
entry.promise = buildEmailZip(client, emails, template)
|
||||
.then((url) => {
|
||||
if (url && currentBundle === entry) entry.url = url;
|
||||
return url;
|
||||
@@ -129,6 +130,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
const { startDrag, endDrag, isDragging, draggedEmails } = useDragDropContext();
|
||||
const isMobile = useUIStore((state) => state.isMobile);
|
||||
const client = useAuthStore((state) => state.client);
|
||||
const emailTemplate = useSettingsStore((s) => s.emailDownloadTemplate) || DEFAULT_EMAIL_TEMPLATE;
|
||||
|
||||
const dragOutEnabled = !isMobile && isDragOutSupported() && !!client;
|
||||
const singleBlobUrlRef = useRef<string | null>(null);
|
||||
@@ -150,7 +152,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
const prefetchSingle = useCallback(() => {
|
||||
if (!dragOutEnabled || !client || !email.blobId) return;
|
||||
if (singleBlobUrlRef.current || inFlightRef.current) return;
|
||||
const name = emailExportFilename(email);
|
||||
const name = emailExportFilename(email, emailTemplate);
|
||||
inFlightRef.current = client
|
||||
.fetchBlobAsObjectUrl(email.blobId, name, "message/rfc822")
|
||||
.then((url) => {
|
||||
@@ -161,7 +163,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
.finally(() => {
|
||||
inFlightRef.current = null;
|
||||
});
|
||||
}, [dragOutEnabled, client, email]);
|
||||
}, [dragOutEnabled, client, email, emailTemplate]);
|
||||
|
||||
const handlePointerEnter = useCallback(() => {
|
||||
if (!dragOutEnabled || !client) return;
|
||||
@@ -171,12 +173,12 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
const selected = emails.filter((em) => selectedEmailIds.has(em.id));
|
||||
// Only worth bundling when at least one selected email has a blobId.
|
||||
if (selected.some((em) => em.blobId)) {
|
||||
prefetchEmailBundle(client, selected);
|
||||
prefetchEmailBundle(client, selected, emailTemplate);
|
||||
}
|
||||
} else {
|
||||
prefetchSingle();
|
||||
}
|
||||
}, [dragOutEnabled, client, selectedEmailIds, email.id, emails, prefetchSingle]);
|
||||
}, [dragOutEnabled, client, selectedEmailIds, email.id, emails, prefetchSingle, emailTemplate]);
|
||||
|
||||
const handleDragStart = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
// Determine which emails to drag:
|
||||
@@ -203,7 +205,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
if (emailsToDrag.length === 1 && emailsToDrag[0].blobId) {
|
||||
const url = singleBlobUrlRef.current;
|
||||
if (url) {
|
||||
const name = emailExportFilename(emailsToDrag[0]);
|
||||
const name = emailExportFilename(emailsToDrag[0], emailTemplate);
|
||||
// `DownloadURL` format: <mime>:<filename>:<url>. Chromium reads this
|
||||
// on drop and writes a real file; Firefox/Safari ignore it.
|
||||
e.dataTransfer.setData(
|
||||
@@ -224,7 +226,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
);
|
||||
} else {
|
||||
// Kick off the bundle build for the next attempt.
|
||||
prefetchEmailBundle(client, emailsToDrag);
|
||||
prefetchEmailBundle(client, emailsToDrag, emailTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +241,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
});
|
||||
|
||||
startDrag(emailsToDrag, sourceMailboxId);
|
||||
}, [email, selectedEmailIds, emails, sourceMailboxId, startDrag, threadEmails, dragOutEnabled, client, prefetchSingle]);
|
||||
}, [email, selectedEmailIds, emails, sourceMailboxId, startDrag, threadEmails, dragOutEnabled, client, prefetchSingle, emailTemplate]);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
endDrag();
|
||||
|
||||
Reference in New Issue
Block a user