feat: add setting to hide inline images from attachment list

This commit is contained in:
Linus Rath
2026-04-18 01:07:34 +02:00
parent d4f7ae522e
commit 1689315c3a
18 changed files with 96 additions and 12 deletions
+15 -9
View File
@@ -896,6 +896,7 @@ export function EmailViewer({
const showToolbarLabels = useSettingsStore((state) => state.showToolbarLabels);
const mailLayout = useSettingsStore((state) => state.mailLayout);
const calendarInvitationParsingEnabled = useSettingsStore((state) => state.calendarInvitationParsingEnabled);
const hideInlineImageAttachments = useSettingsStore((state) => state.hideInlineImageAttachments);
const timeFormat = useSettingsStore((state) => state.timeFormat);
const isFocusedMailLayout = mailLayout === 'focus';
@@ -2074,14 +2075,16 @@ export function EmailViewer({
const effectiveAttachments = useMemo<EffectiveAttachment[]>(() => {
if (smimeDecryptedAttachments.length > 0) {
return smimeDecryptedAttachments.map((attachment, index) => ({
id: `smime-${index}-${attachment.filename || attachment.mimeType}`,
name: attachment.filename,
type: attachment.mimeType || 'application/octet-stream',
size: getPostalMimeAttachmentSize(attachment),
cid: attachment.contentId,
decryptedAttachment: attachment,
}));
return smimeDecryptedAttachments
.filter(att => !(hideInlineImageAttachments && att.contentId && (att.mimeType || '').startsWith('image/')))
.map((attachment, index) => ({
id: `smime-${index}-${attachment.filename || attachment.mimeType}`,
name: attachment.filename,
type: attachment.mimeType || 'application/octet-stream',
size: getPostalMimeAttachmentSize(attachment),
cid: attachment.contentId,
decryptedAttachment: attachment,
}));
}
const hasCalInvitation = calendarInvitationParsingEnabled && !!email && !!findCalendarAttachment(email);
@@ -2093,6 +2096,9 @@ export function EmailViewer({
// Hide calendar MIME parts (text/calendar, application/ics) when the invitation
// banner is shown - prevents raw ICS files appearing as spurious attachments.
.filter(att => !hasCalInvitation || !isCalendarMimeType(att.type))
// Hide inline cid-referenced images when the user has opted to keep them
// out of the attachment list (default on): these are embedded in the body.
.filter(att => !(hideInlineImageAttachments && att.cid && att.disposition === 'inline' && (att.type || '').startsWith('image/')))
.map((attachment, index) => ({
id: attachment.blobId || `${attachment.name || 'attachment'}-${index}`,
name: attachment.name || null,
@@ -2123,7 +2129,7 @@ export function EmailViewer({
}));
return [...jmapAttachments, ...tnefExtracted, ...embeddedExtracted];
}, [email?.attachments, smimeDecryptedAttachments, tnefHtml, tnefText, tnefAttachments, embeddedEmailUnwrapped, embeddedEmailAttachments, calendarInvitationParsingEnabled]);
}, [email?.attachments, smimeDecryptedAttachments, tnefHtml, tnefText, tnefAttachments, embeddedEmailUnwrapped, embeddedEmailAttachments, calendarInvitationParsingEnabled, hideInlineImageAttachments]);
// Generate email source for viewing
const generateEmailSource = (email: Email): string => {
@@ -237,6 +237,7 @@ function EmailCard({
const resolvedTheme = useThemeStore((state) => state.resolvedTheme);
const density = useSettingsStore((state) => state.density);
const mailAttachmentAction = useSettingsStore((state) => state.mailAttachmentAction);
const hideInlineImageAttachments = useSettingsStore((state) => state.hideInlineImageAttachments);
const emailAlwaysLightMode = useSettingsStore((state) => state.emailAlwaysLightMode);
const sender = email.from?.[0];
const isUnread = !email.keywords?.$seen;
@@ -544,10 +545,14 @@ function EmailCard({
</div>
{/* Attachments */}
{email.attachments && email.attachments.length > 0 && (
{(() => {
const visibleAttachments = (email.attachments ?? []).filter(
att => !(hideInlineImageAttachments && att.cid && att.disposition === 'inline' && (att.type || '').startsWith('image/'))
);
return visibleAttachments.length > 0 && (
<div className="px-4 pb-4">
<div className="flex flex-wrap gap-2">
{email.attachments.map((attachment, idx) => {
{visibleAttachments.map((attachment, idx) => {
const Icon = getFileIcon(attachment.name, attachment.type);
const isPreviewable = isFilePreviewable(attachment.name, attachment.type);
const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
@@ -576,7 +581,8 @@ function EmailCard({
})}
</div>
</div>
)}
);
})()}
{/* Action Buttons */}
<div className="px-4 pb-4 flex gap-2">
+9
View File
@@ -135,6 +135,7 @@ export function EmailSettings() {
trustedSendersAddressBook,
attachmentReminderEnabled,
attachmentReminderKeywords,
hideInlineImageAttachments,
updateSetting,
} = useSettingsStore();
const { trustedSenderEmails } = useContactStore();
@@ -401,6 +402,14 @@ export function EmailSettings() {
</div>
)}
{/* Hide inline images from attachment list */}
<SettingItem label={t('hide_inline_image_attachments.label')} description={t('hide_inline_image_attachments.description')}>
<ToggleSwitch
checked={hideInlineImageAttachments}
onChange={(checked) => updateSetting('hideInlineImageAttachments', checked)}
/>
</SettingItem>
{/* Quick Hover Actions */}
{isFeatureEnabled('hoverActionsConfigEnabled') && (
<div className="py-3 border-b border-border space-y-3">
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Schlüsselwort hinzufügen...",
"add": "Hinzufügen",
"remove": "Entfernen"
},
"hide_inline_image_attachments": {
"label": "Eingebettete Bilder in Anhangsliste ausblenden",
"description": "Im Nachrichtentext eingebettete Bilder werden nicht als separate Anhänge aufgeführt"
}
},
"composer": {
+4
View File
@@ -966,6 +966,10 @@
"add_placeholder": "Add keyword...",
"add": "Add",
"remove": "Remove"
},
"hide_inline_image_attachments": {
"label": "Hide inline images from attachments",
"description": "Images embedded in the message body are not listed as separate attachments"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Añadir palabra clave...",
"add": "Add",
"remove": "Eliminar"
},
"hide_inline_image_attachments": {
"label": "Ocultar imágenes incrustadas de los adjuntos",
"description": "Las imágenes incrustadas en el cuerpo del mensaje no se listan como adjuntos separados"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Ajouter un mot-clé...",
"add": "Add",
"remove": "Supprimer"
},
"hide_inline_image_attachments": {
"label": "Masquer les images intégrées des pièces jointes",
"description": "Les images intégrées au corps du message ne sont pas listées comme pièces jointes séparées"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Aggiungi parola chiave...",
"add": "Add",
"remove": "Rimuovi"
},
"hide_inline_image_attachments": {
"label": "Nascondi le immagini inline dagli allegati",
"description": "Le immagini incorporate nel corpo del messaggio non vengono elencate come allegati separati"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "キーワードを追加...",
"add": "Add",
"remove": "削除"
},
"hide_inline_image_attachments": {
"label": "添付ファイル一覧からインライン画像を隠す",
"description": "本文に埋め込まれた画像を個別の添付ファイルとして表示しません"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "키워드 추가...",
"add": "Add",
"remove": "삭제"
},
"hide_inline_image_attachments": {
"label": "첨부 파일 목록에서 인라인 이미지 숨기기",
"description": "메시지 본문에 포함된 이미지는 별도의 첨부 파일로 표시되지 않습니다"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Pievienot atslēgvārdu...",
"add": "Add",
"remove": "Noņemt"
},
"hide_inline_image_attachments": {
"label": "Slēpt iegultos attēlus no pielikumu saraksta",
"description": "Ziņojuma pamattekstā iegultie attēli netiek rādīti kā atsevišķi pielikumi"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Trefwoord toevoegen...",
"add": "Add",
"remove": "Verwijderen"
},
"hide_inline_image_attachments": {
"label": "Inline-afbeeldingen verbergen uit bijlagenlijst",
"description": "Afbeeldingen die in de berichttekst zijn ingesloten worden niet als aparte bijlagen weergegeven"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Dodaj słowo kluczowe...",
"add": "Add",
"remove": "Usuń"
},
"hide_inline_image_attachments": {
"label": "Ukryj obrazy osadzone z listy załączników",
"description": "Obrazy osadzone w treści wiadomości nie są wyświetlane jako osobne załączniki"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Adicionar palavra-chave...",
"add": "Add",
"remove": "Remover"
},
"hide_inline_image_attachments": {
"label": "Ocultar imagens incorporadas dos anexos",
"description": "As imagens incorporadas no corpo da mensagem não são listadas como anexos separados"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Добавить ключевое слово...",
"add": "Add",
"remove": "Удалить"
},
"hide_inline_image_attachments": {
"label": "Скрывать встроенные изображения из вложений",
"description": "Изображения, встроенные в тело сообщения, не отображаются как отдельные вложения"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "Додати ключове слово...",
"add": "додати",
"remove": "видалити"
},
"hide_inline_image_attachments": {
"label": "Приховувати вбудовані зображення з вкладень",
"description": "Зображення, вбудовані в тіло повідомлення, не відображаються як окремі вкладення"
}
},
"composer": {
+4
View File
@@ -961,6 +961,10 @@
"add_placeholder": "添加关键词...",
"add": "Add",
"remove": "删除"
},
"hide_inline_image_attachments": {
"label": "在附件列表中隐藏内嵌图片",
"description": "嵌入到邮件正文中的图片不会作为单独的附件显示"
}
},
"composer": {
+7
View File
@@ -198,6 +198,10 @@ interface SettingsState {
attachmentReminderEnabled: boolean;
attachmentReminderKeywords: string[];
// Hide inline images (images referenced by cid in the HTML body) from the
// attachment list shown above the message body.
hideInlineImageAttachments: boolean;
// Sidebar Apps
sidebarApps: SidebarApp[];
keepAppsLoaded: boolean;
@@ -365,6 +369,8 @@ const DEFAULT_SETTINGS = {
'pielikumā',
] as string[],
hideInlineImageAttachments: true,
// Sidebar Apps
sidebarApps: [] as SidebarApp[],
keepAppsLoaded: false,
@@ -467,6 +473,7 @@ export const useSettingsStore = create<SettingsState>()(
emailKeywords: state.emailKeywords,
attachmentReminderEnabled: state.attachmentReminderEnabled,
attachmentReminderKeywords: state.attachmentReminderKeywords,
hideInlineImageAttachments: state.hideInlineImageAttachments,
sidebarApps: state.sidebarApps,
keepAppsLoaded: state.keepAppsLoaded,
debugMode: state.debugMode,