feat: add settings template for multi-email .zip filename

This commit is contained in:
Linus Rath
2026-05-22 15:30:15 +02:00
parent 52f5a5b42c
commit e3f6ae874d
21 changed files with 152 additions and 10 deletions
@@ -8,9 +8,12 @@ import { RotateCcw } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
DEFAULT_ATTACHMENT_TEMPLATE, DEFAULT_ATTACHMENT_TEMPLATE,
DEFAULT_BUNDLE_TEMPLATE,
DEFAULT_EMAIL_TEMPLATE, DEFAULT_EMAIL_TEMPLATE,
EMAIL_TOKENS, EMAIL_TOKENS,
ATTACHMENT_TOKENS, ATTACHMENT_TOKENS,
BUNDLE_TOKENS,
bundleExportFilename,
emailExportFilename, emailExportFilename,
attachmentDownloadFilename, attachmentDownloadFilename,
buildSampleEmail, buildSampleEmail,
@@ -130,6 +133,7 @@ export function DownloadsSettings() {
const { const {
emailDownloadTemplate, emailDownloadTemplate,
attachmentDownloadTemplate, attachmentDownloadTemplate,
bundleDownloadTemplate,
filenameSpaceReplacement, filenameSpaceReplacement,
filenameLowercase, filenameLowercase,
filenameStripDiacritics, filenameStripDiacritics,
@@ -158,6 +162,10 @@ export function DownloadsSettings() {
() => ({ ...transform, template: attachmentDownloadTemplate || DEFAULT_ATTACHMENT_TEMPLATE }), () => ({ ...transform, template: attachmentDownloadTemplate || DEFAULT_ATTACHMENT_TEMPLATE }),
[transform, attachmentDownloadTemplate], [transform, attachmentDownloadTemplate],
); );
const bundleOptions: EmailFilenameOptions = useMemo(
() => ({ ...transform, template: bundleDownloadTemplate || DEFAULT_BUNDLE_TEMPLATE }),
[transform, bundleDownloadTemplate],
);
const emlPreview = useMemo( const emlPreview = useMemo(
() => emailExportFilename(sampleEmail, emailOptions), () => emailExportFilename(sampleEmail, emailOptions),
@@ -167,6 +175,12 @@ export function DownloadsSettings() {
() => attachmentDownloadFilename(sampleEmail, sampleAttachment, attachmentOptions), () => attachmentDownloadFilename(sampleEmail, sampleAttachment, attachmentOptions),
[sampleEmail, sampleAttachment, attachmentOptions], [sampleEmail, sampleAttachment, attachmentOptions],
); );
const bundlePreview = useMemo(
// Render with the email's fixed sample date so the preview is stable as the
// user types in the template field.
() => bundleExportFilename(3, bundleOptions, sampleEmail.receivedAt ?? undefined),
[bundleOptions, sampleEmail],
);
return ( return (
<SettingsSection title={t("title")} description={t("description")}> <SettingsSection title={t("title")} description={t("description")}>
@@ -194,6 +208,18 @@ export function DownloadsSettings() {
previewLabel={t("preview")} previewLabel={t("preview")}
placeholder={DEFAULT_ATTACHMENT_TEMPLATE} placeholder={DEFAULT_ATTACHMENT_TEMPLATE}
/> />
<TemplateEditor
label={t("bundle_template.label")}
description={t("bundle_template.description")}
value={bundleDownloadTemplate}
defaultValue={DEFAULT_BUNDLE_TEMPLATE}
tokens={BUNDLE_TOKENS}
preview={bundlePreview}
onChange={(next) => updateSetting("bundleDownloadTemplate", next)}
resetLabel={t("reset")}
previewLabel={t("preview")}
placeholder={DEFAULT_BUNDLE_TEMPLATE}
/>
<SettingItem label={t("spaces.label")} description={t("spaces.description")}> <SettingItem label={t("spaces.label")} description={t("spaces.description")}>
<Select <Select
value={filenameSpaceReplacement} value={filenameSpaceReplacement}
+26 -10
View File
@@ -8,7 +8,13 @@ import { useAuthStore } from "@/stores/auth-store";
import { useDragDropContext } from "@/contexts/drag-drop-context"; import { useDragDropContext } from "@/contexts/drag-drop-context";
import { useUIStore } from "@/stores/ui-store"; import { useUIStore } from "@/stores/ui-store";
import { isDragOutSupported } from "@/hooks/use-attachment-drag"; import { isDragOutSupported } from "@/hooks/use-attachment-drag";
import { emailExportFilename, DEFAULT_EMAIL_TEMPLATE, type EmailFilenameOptions } from "@/lib/download-filename"; import {
bundleExportFilename,
DEFAULT_BUNDLE_TEMPLATE,
DEFAULT_EMAIL_TEMPLATE,
emailExportFilename,
type EmailFilenameOptions,
} from "@/lib/download-filename";
import { useSettingsStore } from "@/stores/settings-store"; import { useSettingsStore } from "@/stores/settings-store";
interface UseEmailDragOptions { interface UseEmailDragOptions {
@@ -50,8 +56,8 @@ function createDragPreview(count: number): HTMLElement {
return preview; return preview;
} }
function bundleFilename(count: number): string { function bundleFilename(count: number, options: EmailFilenameOptions): string {
return `emails-${count}.zip`; return bundleExportFilename(count, options);
} }
// Shared bundle cache. The .zip is keyed by the sorted list of email IDs in // Shared bundle cache. The .zip is keyed by the sorted list of email IDs in
@@ -95,7 +101,12 @@ async function buildEmailZip(client: IJMAPClient, emails: Email[], options: Emai
return URL.createObjectURL(zipBlob); return URL.createObjectURL(zipBlob);
} }
function prefetchEmailBundle(client: IJMAPClient, emails: Email[], options: EmailFilenameOptions): void { function prefetchEmailBundle(
client: IJMAPClient,
emails: Email[],
emailOptions: EmailFilenameOptions,
bundleOptions: EmailFilenameOptions,
): void {
const key = selectionKey(emails.map((e) => e.id)); const key = selectionKey(emails.map((e) => e.id));
if (currentBundle && currentBundle.key === key) return; if (currentBundle && currentBundle.key === key) return;
if (currentBundle?.url) { if (currentBundle?.url) {
@@ -104,11 +115,11 @@ function prefetchEmailBundle(client: IJMAPClient, emails: Email[], options: Emai
} }
const entry: BundleEntry = { const entry: BundleEntry = {
key, key,
name: bundleFilename(emails.length), name: bundleFilename(emails.length, bundleOptions),
url: null, url: null,
promise: null, promise: null,
}; };
entry.promise = buildEmailZip(client, emails, options) entry.promise = buildEmailZip(client, emails, emailOptions)
.then((url) => { .then((url) => {
if (url && currentBundle === entry) entry.url = url; if (url && currentBundle === entry) entry.url = url;
return url; return url;
@@ -131,6 +142,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
const isMobile = useUIStore((state) => state.isMobile); const isMobile = useUIStore((state) => state.isMobile);
const client = useAuthStore((state) => state.client); const client = useAuthStore((state) => state.client);
const template = useSettingsStore((s) => s.emailDownloadTemplate) || DEFAULT_EMAIL_TEMPLATE; const template = useSettingsStore((s) => s.emailDownloadTemplate) || DEFAULT_EMAIL_TEMPLATE;
const bundleTemplate = useSettingsStore((s) => s.bundleDownloadTemplate) || DEFAULT_BUNDLE_TEMPLATE;
const spaceReplacement = useSettingsStore((s) => s.filenameSpaceReplacement); const spaceReplacement = useSettingsStore((s) => s.filenameSpaceReplacement);
const lowercase = useSettingsStore((s) => s.filenameLowercase); const lowercase = useSettingsStore((s) => s.filenameLowercase);
const stripDiacritics = useSettingsStore((s) => s.filenameStripDiacritics); const stripDiacritics = useSettingsStore((s) => s.filenameStripDiacritics);
@@ -139,6 +151,10 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
() => ({ template, spaceReplacement, lowercase, stripDiacritics, collapseSeparators }), () => ({ template, spaceReplacement, lowercase, stripDiacritics, collapseSeparators }),
[template, spaceReplacement, lowercase, stripDiacritics, collapseSeparators], [template, spaceReplacement, lowercase, stripDiacritics, collapseSeparators],
); );
const bundleOptions: EmailFilenameOptions = useMemo(
() => ({ template: bundleTemplate, spaceReplacement, lowercase, stripDiacritics, collapseSeparators }),
[bundleTemplate, spaceReplacement, lowercase, stripDiacritics, collapseSeparators],
);
const dragOutEnabled = !isMobile && isDragOutSupported() && !!client; const dragOutEnabled = !isMobile && isDragOutSupported() && !!client;
const singleBlobUrlRef = useRef<string | null>(null); const singleBlobUrlRef = useRef<string | null>(null);
@@ -181,12 +197,12 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
const selected = emails.filter((em) => selectedEmailIds.has(em.id)); const selected = emails.filter((em) => selectedEmailIds.has(em.id));
// Only worth bundling when at least one selected email has a blobId. // Only worth bundling when at least one selected email has a blobId.
if (selected.some((em) => em.blobId)) { if (selected.some((em) => em.blobId)) {
prefetchEmailBundle(client, selected, filenameOptions); prefetchEmailBundle(client, selected, filenameOptions, bundleOptions);
} }
} else { } else {
prefetchSingle(); prefetchSingle();
} }
}, [dragOutEnabled, client, selectedEmailIds, email.id, emails, prefetchSingle, filenameOptions]); }, [dragOutEnabled, client, selectedEmailIds, email.id, emails, prefetchSingle, filenameOptions, bundleOptions]);
const handleDragStart = useCallback((e: DragEvent<HTMLDivElement>) => { const handleDragStart = useCallback((e: DragEvent<HTMLDivElement>) => {
// Determine which emails to drag: // Determine which emails to drag:
@@ -237,7 +253,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
); );
} else { } else {
// Kick off the bundle build for the next attempt. // Kick off the bundle build for the next attempt.
prefetchEmailBundle(client, emailsToDrag, filenameOptions); prefetchEmailBundle(client, emailsToDrag, filenameOptions, bundleOptions);
} }
} }
} }
@@ -252,7 +268,7 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
}); });
startDrag(emailsToDrag, sourceMailboxId); startDrag(emailsToDrag, sourceMailboxId);
}, [email, selectedEmailIds, emails, sourceMailboxId, startDrag, threadEmails, dragOutEnabled, client, prefetchSingle, filenameOptions]); }, [email, selectedEmailIds, emails, sourceMailboxId, startDrag, threadEmails, dragOutEnabled, client, prefetchSingle, filenameOptions, bundleOptions]);
const handleDragEnd = useCallback(() => { const handleDragEnd = useCallback(() => {
endDrag(); endDrag();
+30
View File
@@ -29,6 +29,7 @@ export const DEFAULT_TRANSFORM: Required<FilenameTransformOptions> = {
export const DEFAULT_EMAIL_TEMPLATE = "{date} ({from}-{to}) {subject}"; export const DEFAULT_EMAIL_TEMPLATE = "{date} ({from}-{to}) {subject}";
export const DEFAULT_ATTACHMENT_TEMPLATE = "{filename}"; export const DEFAULT_ATTACHMENT_TEMPLATE = "{filename}";
export const DEFAULT_BUNDLE_TEMPLATE = "emails-{count}";
export const EMAIL_TOKENS: { token: string; description: string }[] = [ export const EMAIL_TOKENS: { token: string; description: string }[] = [
{ token: "date", description: "Full date and time, e.g. 2026-05-22 14.05.33" }, { token: "date", description: "Full date and time, e.g. 2026-05-22 14.05.33" },
@@ -53,6 +54,16 @@ export const ATTACHMENT_TOKENS: { token: string; description: string }[] = [
{ token: "ext", description: "Attachment file extension without leading dot" }, { token: "ext", description: "Attachment file extension without leading dot" },
]; ];
export const BUNDLE_TOKENS: { token: string; description: string }[] = [
{ token: "count", description: "Number of emails in the bundle" },
{ token: "date", description: "Current date and time, e.g. 2026-05-22 14.05.33" },
{ token: "date_short", description: "Current date, e.g. 2026-05-22" },
{ token: "time", description: "Current time, e.g. 14.05.33" },
{ token: "year", description: "4-digit year" },
{ token: "month", description: "2-digit month" },
{ token: "day", description: "2-digit day" },
];
function sanitizePart(input: string, maxLen = 80): string { function sanitizePart(input: string, maxLen = 80): string {
const cleaned = input const cleaned = input
.replace(SAFE_CHARS, "_") .replace(SAFE_CHARS, "_")
@@ -211,6 +222,25 @@ export function attachmentDownloadFilename(
return `${transformedStem}.${transformedExt}`; return `${transformedStem}.${transformedExt}`;
} }
export function bundleVars(count: number, iso?: string): Record<string, string> {
const dp = dateParts(iso ?? new Date().toISOString());
return { ...dp, count: String(count) };
}
export function bundleExportFilename(
count: number,
options: EmailFilenameOptions | string = {},
iso?: string,
): string {
const opts = typeof options === "string" ? { template: options } : options;
const template = opts.template ?? DEFAULT_BUNDLE_TEMPLATE;
const rendered = renderRaw(template, bundleVars(count, iso));
const cleaned = sanitizePart(rendered, 200);
const transformed = applyTransforms(cleaned, opts);
const stem = transformed.slice(0, 200) || "emails";
return `${stem}.zip`;
}
// Build a synthetic email for previewing templates in the settings UI. // Build a synthetic email for previewing templates in the settings UI.
export function buildSampleEmail(): Email { export function buildSampleEmail(): Email {
// Use a fixed date so the preview doesn't churn as the user types. // Use a fixed date so the preview doesn't churn as the user types.
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Název souboru přílohy", "label": "Název souboru přílohy",
"description": "Šablona použitá při stahování nebo přetahování přílohy. Pokud vynecháte {filename} a {ext}, původní přípona se zachová." "description": "Šablona použitá při stahování nebo přetahování přílohy. Pokud vynecháte {filename} a {ext}, původní přípona se zachová."
}, },
"bundle_template": {
"label": "Název .zip souboru s více e-maily",
"description": "Šablona použitá při přetažení nebo stažení několika vybraných e-mailů jako jediného .zip archivu. Přípona .zip se přidává automaticky."
},
"spaces": { "spaces": {
"label": "Mezery", "label": "Mezery",
"description": "Nahradit mezery ve výsledném názvu souboru jiným znakem.", "description": "Nahradit mezery ve výsledném názvu souboru jiným znakem.",
+4
View File
@@ -1512,6 +1512,10 @@
"label": "Filnavn på vedhæftet fil", "label": "Filnavn på vedhæftet fil",
"description": "Skabelon som bruges når du downloader eller trækker en vedhæftet fil ud. Hvis du udelader {filename} og {ext}, bevares den oprindelige endelse." "description": "Skabelon som bruges når du downloader eller trækker en vedhæftet fil ud. Hvis du udelader {filename} og {ext}, bevares den oprindelige endelse."
}, },
"bundle_template": {
"label": "Filnavn for .zip med flere e-mails",
"description": "Skabelon som bruges når du trækker ud eller downloader flere valgte e-mails som ét .zip-arkiv. Endelsen .zip tilføjes automatisk."
},
"spaces": { "spaces": {
"label": "Mellemrum", "label": "Mellemrum",
"description": "Erstat mellemrum i det endelige filnavn med et andet tegn.", "description": "Erstat mellemrum i det endelige filnavn med et andet tegn.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Anhang-Dateiname", "label": "Anhang-Dateiname",
"description": "Vorlage zum Herunterladen oder Herausziehen eines Anhangs. Wenn {filename} und {ext} fehlen, bleibt die ursprüngliche Endung erhalten." "description": "Vorlage zum Herunterladen oder Herausziehen eines Anhangs. Wenn {filename} und {ext} fehlen, bleibt die ursprüngliche Endung erhalten."
}, },
"bundle_template": {
"label": "Mehrere E-Mails als .zip",
"description": "Vorlage, die beim Herausziehen oder Herunterladen mehrerer ausgewählter E-Mails als einzelnes .zip-Archiv verwendet wird. Die Endung .zip wird automatisch ergänzt."
},
"spaces": { "spaces": {
"label": "Leerzeichen", "label": "Leerzeichen",
"description": "Ersetze Leerzeichen im Dateinamen durch ein anderes Zeichen.", "description": "Ersetze Leerzeichen im Dateinamen durch ein anderes Zeichen.",
+4
View File
@@ -1512,6 +1512,10 @@
"label": "Attachment filename", "label": "Attachment filename",
"description": "Template used when downloading or dragging out an attachment. If you omit {filename} and {ext}, the original extension is preserved." "description": "Template used when downloading or dragging out an attachment. If you omit {filename} and {ext}, the original extension is preserved."
}, },
"bundle_template": {
"label": "Multi-email .zip filename",
"description": "Template used when you drag out or download several selected emails as a single .zip archive. The .zip extension is added automatically."
},
"spaces": { "spaces": {
"label": "Spaces", "label": "Spaces",
"description": "Replace spaces in the resulting filename with another character.", "description": "Replace spaces in the resulting filename with another character.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Nombre del archivo adjunto", "label": "Nombre del archivo adjunto",
"description": "Plantilla usada al descargar o arrastrar un adjunto. Si omites {filename} y {ext}, se conserva la extensión original." "description": "Plantilla usada al descargar o arrastrar un adjunto. Si omites {filename} y {ext}, se conserva la extensión original."
}, },
"bundle_template": {
"label": "Nombre de archivo .zip multi-correo",
"description": "Plantilla utilizada al arrastrar o descargar varios correos seleccionados como un único archivo .zip. La extensión .zip se añade automáticamente."
},
"spaces": { "spaces": {
"label": "Espacios", "label": "Espacios",
"description": "Reemplaza los espacios del nombre de archivo resultante por otro carácter.", "description": "Reemplaza los espacios del nombre de archivo resultante por otro carácter.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Nom du fichier de pièce jointe", "label": "Nom du fichier de pièce jointe",
"description": "Modèle utilisé lors du téléchargement ou du glisser-déposer d'une pièce jointe. Si vous omettez {filename} et {ext}, l'extension d'origine est conservée." "description": "Modèle utilisé lors du téléchargement ou du glisser-déposer d'une pièce jointe. Si vous omettez {filename} et {ext}, l'extension d'origine est conservée."
}, },
"bundle_template": {
"label": "Nom du fichier .zip multi-e-mails",
"description": "Modèle utilisé lorsque vous faites glisser ou téléchargez plusieurs e-mails sélectionnés sous forme d'archive .zip unique. L'extension .zip est ajoutée automatiquement."
},
"spaces": { "spaces": {
"label": "Espaces", "label": "Espaces",
"description": "Remplacer les espaces dans le nom de fichier final par un autre caractère.", "description": "Remplacer les espaces dans le nom de fichier final par un autre caractère.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Nome file dellallegato", "label": "Nome file dellallegato",
"description": "Modello usato quando scarichi o trascini un allegato. Se ometti {filename} e {ext}, l'estensione originale viene mantenuta." "description": "Modello usato quando scarichi o trascini un allegato. Se ometti {filename} e {ext}, l'estensione originale viene mantenuta."
}, },
"bundle_template": {
"label": "Nome del file .zip multi-email",
"description": "Modello usato quando trascini o scarichi più email selezionate come un singolo archivio .zip. L'estensione .zip viene aggiunta automaticamente."
},
"spaces": { "spaces": {
"label": "Spazi", "label": "Spazi",
"description": "Sostituisci gli spazi nel nome file risultante con un altro carattere.", "description": "Sostituisci gli spazi nel nome file risultante con un altro carattere.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "添付ファイルのファイル名", "label": "添付ファイルのファイル名",
"description": "添付ファイルをダウンロードまたはドラッグするときに使用するテンプレートです。{filename} と {ext} を省略すると、元の拡張子が保持されます。" "description": "添付ファイルをダウンロードまたはドラッグするときに使用するテンプレートです。{filename} と {ext} を省略すると、元の拡張子が保持されます。"
}, },
"bundle_template": {
"label": "複数メールの .zip ファイル名",
"description": "複数の選択したメールを 1 つの .zip としてドラッグまたはダウンロードするときに使用するテンプレートです。拡張子 .zip は自動的に付加されます。"
},
"spaces": { "spaces": {
"label": "スペース", "label": "スペース",
"description": "最終的なファイル名のスペースを別の文字に置き換えます。", "description": "最終的なファイル名のスペースを別の文字に置き換えます。",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "첨부 파일 이름", "label": "첨부 파일 이름",
"description": "첨부 파일을 다운로드하거나 드래그할 때 사용하는 템플릿입니다. {filename}과 {ext}를 생략하면 원래 확장자가 유지됩니다." "description": "첨부 파일을 다운로드하거나 드래그할 때 사용하는 템플릿입니다. {filename}과 {ext}를 생략하면 원래 확장자가 유지됩니다."
}, },
"bundle_template": {
"label": "다중 이메일 .zip 파일 이름",
"description": "여러 선택한 이메일을 하나의 .zip 아카이브로 드래그하거나 다운로드할 때 사용하는 템플릿입니다. .zip 확장자는 자동으로 추가됩니다."
},
"spaces": { "spaces": {
"label": "공백", "label": "공백",
"description": "결과 파일 이름의 공백을 다른 문자로 바꿉니다.", "description": "결과 파일 이름의 공백을 다른 문자로 바꿉니다.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Pielikuma faila nosaukums", "label": "Pielikuma faila nosaukums",
"description": "Veidne, ko izmanto, lejupielādējot vai velkot pielikumu. Ja izlaižat {filename} un {ext}, sākotnējais paplašinājums tiek saglabāts." "description": "Veidne, ko izmanto, lejupielādējot vai velkot pielikumu. Ja izlaižat {filename} un {ext}, sākotnējais paplašinājums tiek saglabāts."
}, },
"bundle_template": {
"label": "Vairāku e-pastu .zip faila nosaukums",
"description": "Veidne, ko izmanto, kad velkat vai lejupielādējat vairākus atlasītos e-pastus kā vienu .zip arhīvu. Paplašinājums .zip tiek pievienots automātiski."
},
"spaces": { "spaces": {
"label": "Atstarpes", "label": "Atstarpes",
"description": "Aizstāt atstarpes izvades faila nosaukumā ar citu rakstzīmi.", "description": "Aizstāt atstarpes izvades faila nosaukumā ar citu rakstzīmi.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Bestandsnaam van bijlage", "label": "Bestandsnaam van bijlage",
"description": "Sjabloon bij het downloaden of slepen van een bijlage. Als je {filename} en {ext} weglaat, blijft de oorspronkelijke extensie behouden." "description": "Sjabloon bij het downloaden of slepen van een bijlage. Als je {filename} en {ext} weglaat, blijft de oorspronkelijke extensie behouden."
}, },
"bundle_template": {
"label": "Bestandsnaam .zip met meerdere e-mails",
"description": "Sjabloon dat wordt gebruikt wanneer je meerdere geselecteerde e-mails als één .zip-archief sleept of downloadt. De extensie .zip wordt automatisch toegevoegd."
},
"spaces": { "spaces": {
"label": "Spaties", "label": "Spaties",
"description": "Vervang spaties in de uiteindelijke bestandsnaam door een ander teken.", "description": "Vervang spaties in de uiteindelijke bestandsnaam door een ander teken.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Nazwa pliku załącznika", "label": "Nazwa pliku załącznika",
"description": "Szablon używany przy pobieraniu lub przeciąganiu załącznika. Jeśli pominiesz {filename} i {ext}, oryginalne rozszerzenie zostanie zachowane." "description": "Szablon używany przy pobieraniu lub przeciąganiu załącznika. Jeśli pominiesz {filename} i {ext}, oryginalne rozszerzenie zostanie zachowane."
}, },
"bundle_template": {
"label": "Nazwa pliku .zip wielu wiadomości",
"description": "Szablon używany przy przeciąganiu lub pobieraniu kilku wybranych wiadomości jako jednego archiwum .zip. Rozszerzenie .zip jest dodawane automatycznie."
},
"spaces": { "spaces": {
"label": "Spacje", "label": "Spacje",
"description": "Zastąp spacje w nazwie pliku innym znakiem.", "description": "Zastąp spacje w nazwie pliku innym znakiem.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Nome do arquivo do anexo", "label": "Nome do arquivo do anexo",
"description": "Modelo usado ao baixar ou arrastar um anexo. Se você omitir {filename} e {ext}, a extensão original é preservada." "description": "Modelo usado ao baixar ou arrastar um anexo. Se você omitir {filename} e {ext}, a extensão original é preservada."
}, },
"bundle_template": {
"label": "Nome do arquivo .zip multi-e-mails",
"description": "Modelo usado ao arrastar ou baixar vários e-mails selecionados como um único arquivo .zip. A extensão .zip é adicionada automaticamente."
},
"spaces": { "spaces": {
"label": "Espaços", "label": "Espaços",
"description": "Substitui espaços no nome de arquivo resultante por outro caractere.", "description": "Substitui espaços no nome de arquivo resultante por outro caractere.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Имя файла вложения", "label": "Имя файла вложения",
"description": "Шаблон, используемый при загрузке или перетаскивании вложения. Если опустить {filename} и {ext}, исходное расширение сохраняется." "description": "Шаблон, используемый при загрузке или перетаскивании вложения. Если опустить {filename} и {ext}, исходное расширение сохраняется."
}, },
"bundle_template": {
"label": "Имя файла .zip с несколькими письмами",
"description": "Шаблон, используемый при перетаскивании или загрузке нескольких выбранных писем одним .zip-архивом. Расширение .zip добавляется автоматически."
},
"spaces": { "spaces": {
"label": "Пробелы", "label": "Пробелы",
"description": "Заменять пробелы в итоговом имени файла другим символом.", "description": "Заменять пробелы в итоговом имени файла другим символом.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Ek dosyası adı", "label": "Ek dosyası adı",
"description": "Bir eki indirirken veya sürüklerken kullanılan şablon. {filename} ve {ext} yazılmazsa orijinal uzantı korunur." "description": "Bir eki indirirken veya sürüklerken kullanılan şablon. {filename} ve {ext} yazılmazsa orijinal uzantı korunur."
}, },
"bundle_template": {
"label": "Çoklu e-posta .zip dosya adı",
"description": "Birden çok seçili e-postayı tek bir .zip arşivi olarak sürüklediğinizde veya indirdiğinizde kullanılan şablon. .zip uzantısı otomatik olarak eklenir."
},
"spaces": { "spaces": {
"label": "Boşluklar", "label": "Boşluklar",
"description": "Sonuçtaki dosya adındaki boşlukları başka bir karakterle değiştirin.", "description": "Sonuçtaki dosya adındaki boşlukları başka bir karakterle değiştirin.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "Ім'я файлу вкладення", "label": "Ім'я файлу вкладення",
"description": "Шаблон, що використовується при завантаженні або перетягуванні вкладення. Якщо пропустити {filename} та {ext}, оригінальне розширення зберігається." "description": "Шаблон, що використовується при завантаженні або перетягуванні вкладення. Якщо пропустити {filename} та {ext}, оригінальне розширення зберігається."
}, },
"bundle_template": {
"label": "Ім'я файлу .zip з кількома листами",
"description": "Шаблон, що використовується при перетягуванні або завантаженні кількох вибраних листів одним .zip-архівом. Розширення .zip додається автоматично."
},
"spaces": { "spaces": {
"label": "Пробіли", "label": "Пробіли",
"description": "Замінити пробіли в підсумковому імені файлу іншим символом.", "description": "Замінити пробіли в підсумковому імені файлу іншим символом.",
+4
View File
@@ -1509,6 +1509,10 @@
"label": "附件文件名", "label": "附件文件名",
"description": "下载或拖出附件时使用的模板。如果省略 {filename} 和 {ext},将保留原始扩展名。" "description": "下载或拖出附件时使用的模板。如果省略 {filename} 和 {ext},将保留原始扩展名。"
}, },
"bundle_template": {
"label": "多邮件 .zip 文件名",
"description": "将多个选中的邮件作为单个 .zip 拖出或下载时使用的模板。扩展名 .zip 会自动添加。"
},
"spaces": { "spaces": {
"label": "空格", "label": "空格",
"description": "用其他字符替换最终文件名中的空格。", "description": "用其他字符替换最终文件名中的空格。",
+2
View File
@@ -242,6 +242,7 @@ interface SettingsState {
// Downloads // Downloads
emailDownloadTemplate: string; emailDownloadTemplate: string;
attachmentDownloadTemplate: string; attachmentDownloadTemplate: string;
bundleDownloadTemplate: string;
filenameSpaceReplacement: 'keep' | 'underscore' | 'dash'; filenameSpaceReplacement: 'keep' | 'underscore' | 'dash';
filenameLowercase: boolean; filenameLowercase: boolean;
filenameStripDiacritics: boolean; filenameStripDiacritics: boolean;
@@ -435,6 +436,7 @@ const DEFAULT_SETTINGS = {
// Downloads // Downloads
emailDownloadTemplate: '{date} ({from}-{to}) {subject}', emailDownloadTemplate: '{date} ({from}-{to}) {subject}',
attachmentDownloadTemplate: '{filename}', attachmentDownloadTemplate: '{filename}',
bundleDownloadTemplate: 'emails-{count}',
filenameSpaceReplacement: 'keep' as 'keep' | 'underscore' | 'dash', filenameSpaceReplacement: 'keep' as 'keep' | 'underscore' | 'dash',
filenameLowercase: false, filenameLowercase: false,
filenameStripDiacritics: false, filenameStripDiacritics: false,