feat: add setting to show avatars in junk folder, disabled by default

This commit is contained in:
Linus Rath
2026-04-23 18:23:48 +02:00
parent 6c3529b368
commit 081e8a0310
19 changed files with 81 additions and 5 deletions
+3
View File
@@ -37,6 +37,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
const density = useSettingsStore((state) => state.density);
const mailLayout = useSettingsStore((state) => state.mailLayout);
const emailKeywords = useSettingsStore((state) => state.emailKeywords);
const showAvatarsInJunk = useSettingsStore((state) => state.showAvatarsInJunk);
const { identities } = useAuthStore();
const isChecked = selectedEmailIds.has(email.id);
const isUnread = !email.keywords?.$seen;
@@ -49,6 +50,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
const showRecipient = currentMailboxRole === 'sent' || currentMailboxRole === 'drafts';
const sender = showRecipient ? (email.to?.[0] ?? email.from?.[0]) : email.from?.[0];
const isFocusedMailLayout = mailLayout === 'focus';
const hideJunkAvatarImages = currentMailboxRole === 'junk' && !showAvatarsInJunk;
const inlinePreview = showPreview && email.preview ? ` ${email.preview}` : '';
// Resolve color tags using keyword definitions from settings; unknown tags fall back to gray
@@ -164,6 +166,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
email={sender?.email}
size="md"
className="flex-shrink-0 shadow-sm"
disableImages={hideJunkAvatarImages}
/>
)}
+6
View File
@@ -64,6 +64,8 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
const emailKeywords = useSettingsStore((state) => state.emailKeywords);
const density = useSettingsStore((state) => state.density);
const mailLayout = useSettingsStore((state) => state.mailLayout);
const showAvatarsInJunk = useSettingsStore((state) => state.showAvatarsInJunk);
const hideJunkAvatarImages = currentMailboxRole === 'junk' && !showAvatarsInJunk;
const isUnifiedView = useEmailStore((state) => state.isUnifiedView);
const getAccountById = useAccountStore((state) => state.getAccountById);
const accountColor = email.accountId ? getAccountById(email.accountId)?.avatarColor : undefined;
@@ -182,6 +184,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
email={sender?.email}
size="md"
className="flex-shrink-0 shadow-sm"
disableImages={hideJunkAvatarImages}
/>
)}
@@ -359,6 +362,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
const showPreview = useSettingsStore((state) => state.showPreview);
const density = useSettingsStore((state) => state.density);
const mailLayout = useSettingsStore((state) => state.mailLayout);
const showAvatarsInJunk = useSettingsStore((state) => state.showAvatarsInJunk);
const isMobile = useUIStore((state) => state.isMobile);
const { latestEmail, participantNames, hasUnread, hasStarred, hasAttachment, hasAnswered, hasForwarded, emailCount } = thread;
const isFocusedMailLayout = mailLayout === 'focus';
@@ -376,6 +380,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
)).slice(0, 4)
: participantNames;
const avatarPerson = showRecipient ? latestEmail.to?.[0] : latestEmail.from?.[0];
const hideJunkAvatarImages = currentMailboxRole === 'junk' && !showAvatarsInJunk;
const { dragHandlers, isDragging: isThreadDragging } = useEmailDrag({
email: latestEmail,
@@ -563,6 +568,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
email={avatarPerson?.email}
size="md"
className="flex-shrink-0 shadow-sm"
disableImages={hideJunkAvatarImages}
/>
)}
+5 -1
View File
@@ -67,7 +67,7 @@ export function AppearanceSettings() {
const tAdvanced = useTranslations('settings.advanced');
const tTour = useTranslations('tour');
const { theme, setTheme } = useThemeStore();
const { fontSize, density, animationsEnabled, senderFavicons, updateSetting } = useSettingsStore();
const { fontSize, density, animationsEnabled, senderFavicons, showAvatarsInJunk, updateSetting } = useSettingsStore();
const { startTour, resetTourCompletion } = useTour();
const { isSettingLocked, isSettingHidden } = usePolicyStore();
@@ -130,6 +130,10 @@ export function AppearanceSettings() {
<ToggleSwitch checked={senderFavicons} onChange={(checked) => updateSetting('senderFavicons', checked)} />
</SettingItem>
<SettingItem label={tAdvanced('show_avatars_in_junk.label')} description={tAdvanced('show_avatars_in_junk.description')}>
<ToggleSwitch checked={showAvatarsInJunk} onChange={(checked) => updateSetting('showAvatarsInJunk', checked)} />
</SettingItem>
<SettingItem label={tTour('restart_title')} description={tTour('restart_desc')}>
<Button
variant="outline"
+8 -4
View File
@@ -140,9 +140,11 @@ interface AvatarProps {
contactPhotoUri?: string;
size?: "sm" | "md" | "lg";
className?: string;
/** When true, suppress all image sources (favicons, plugin avatars, profile pics, contact photos) and render initials only. */
disableImages?: boolean;
}
export function Avatar({ name, email, contactPhotoUri, size = "md", className }: AvatarProps) {
export function Avatar({ name, email, contactPhotoUri, size = "md", className, disableImages = false }: AvatarProps) {
const [imgError, setImgError] = useState(false);
const [pluginAvatarUrl, setPluginAvatarUrl] = useState<string | null>(null);
const [pluginAvatarFailed, setPluginAvatarFailed] = useState(false);
@@ -222,9 +224,11 @@ export function Avatar({ name, email, contactPhotoUri, size = "md", className }:
// Priority: contact photo > plugin avatar (e.g. Gravatar) > custom avatar > profile picture > company favicon > initials
const customAvatar = devMode && email ? CUSTOM_AVATARS[email.toLowerCase()] : null;
const pluginAvatar = pluginAvatarFailed ? null : pluginAvatarUrl;
const imgSrc = !imgError && !domainFailed
? resolvedContactPhoto || pluginAvatar || customAvatar || profilePic || (showFavicon ? `/api/favicon?domain=${encodeURIComponent(faviconDomain!)}` : null)
: (resolvedContactPhoto || pluginAvatar || customAvatar || profilePic || null);
const imgSrc = disableImages
? null
: !imgError && !domainFailed
? resolvedContactPhoto || pluginAvatar || customAvatar || profilePic || (showFavicon ? `/api/favicon?domain=${encodeURIComponent(faviconDomain!)}` : null)
: (resolvedContactPhoto || pluginAvatar || customAvatar || profilePic || null);
const handleImgError = useCallback(() => {
// If the plugin avatar just failed, mark it and fall through to the next source
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Absender-Favicons (Experimentell)",
"description": "Website-Symbole als Profilbilder für geschäftliche Absender anzeigen"
},
"show_avatars_in_junk": {
"label": "Avatare im Spam-Ordner anzeigen",
"description": "Profilbilder und Favicons für Absender im Spam-Ordner anzeigen. Standardmäßig deaktiviert, damit Phishing-Versuche nicht durch vertraute Logos legitim wirken."
},
"keyboard_shortcuts": {
"label": "Tastaturkürzel",
"description": "Verfügbare Tastaturkürzel anzeigen",
+4
View File
@@ -1287,6 +1287,10 @@
"label": "Sender Favicons (Experimental)",
"description": "Show website icons as profile pictures for business senders"
},
"show_avatars_in_junk": {
"label": "Show Avatars in Junk Folder",
"description": "Show profile images and favicons for senders in the junk folder. Disabled by default to avoid lending visual legitimacy to phishing attempts."
},
"keyboard_shortcuts": {
"label": "Keyboard Shortcuts",
"description": "View available keyboard shortcuts",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Favicons de remitente (Experimental)",
"description": "Mostrar iconos de sitios web como fotos de perfil para remitentes empresariales"
},
"show_avatars_in_junk": {
"label": "Mostrar avatares en la carpeta de spam",
"description": "Mostrar imágenes de perfil y favicons de remitentes en la carpeta de spam. Desactivado por defecto para no dar apariencia legítima a los intentos de phishing."
},
"keyboard_shortcuts": {
"label": "Atajos de Teclado",
"description": "Ver atajos de teclado disponibles",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Favicons des expéditeurs (Expérimental)",
"description": "Afficher les icônes de sites web comme photos de profil pour les expéditeurs professionnels"
},
"show_avatars_in_junk": {
"label": "Afficher les avatars dans le dossier indésirable",
"description": "Afficher les photos de profil et favicons des expéditeurs dans le dossier indésirable. Désactivé par défaut pour éviter de donner une apparence légitime aux tentatives d'hameçonnage."
},
"keyboard_shortcuts": {
"label": "Raccourcis clavier",
"description": "Voir les raccourcis clavier disponibles",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Favicon dei mittenti (Sperimentale)",
"description": "Mostra le icone dei siti web come immagini profilo per i mittenti aziendali"
},
"show_avatars_in_junk": {
"label": "Mostra avatar nella cartella spam",
"description": "Mostra immagini profilo e favicon dei mittenti nella cartella spam. Disattivato per impostazione predefinita per non dare apparenza legittima ai tentativi di phishing."
},
"keyboard_shortcuts": {
"label": "Scorciatoie da tastiera",
"description": "Visualizza le scorciatoie da tastiera disponibili",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "送信者ファビコン(実験的)",
"description": "ビジネス送信者のプロフィール画像としてウェブサイトアイコンを表示"
},
"show_avatars_in_junk": {
"label": "迷惑メールフォルダでアバターを表示",
"description": "迷惑メールフォルダの送信者にプロフィール画像とファビコンを表示します。フィッシング詐欺に正規のような見た目を与えないため、既定では無効です。"
},
"keyboard_shortcuts": {
"label": "キーボードショートカット",
"description": "利用可能なキーボードショートカットを表示",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "보낸 사람 파비콘 (실험적 기능)",
"description": "비즈니스 메일의 경우 해당 웹사이트의 아이콘을 프로필 사진으로 보여줘요"
},
"show_avatars_in_junk": {
"label": "스팸함에서 아바타 표시",
"description": "스팸함의 보낸 사람에 대한 프로필 이미지와 파비콘을 표시해요. 피싱 메일이 정상적인 메일처럼 보이지 않도록 기본적으로 꺼져 있어요."
},
"keyboard_shortcuts": {
"label": "단축키",
"description": "사용 가능한 키보드 단축키를 확인해 보세요",
+4
View File
@@ -1266,6 +1266,10 @@
"label": "Sūtītāju ikonas (Eksperimentāli)",
"description": "Rādīt vietņu ikonas kā sūtītāju avatarus"
},
"show_avatars_in_junk": {
"label": "Rādīt avatarus mēstuļu mapē",
"description": "Rādīt sūtītāju profila attēlus un ikonas mēstuļu mapē. Pēc noklusējuma izslēgts, lai pikšķerēšanas mēģinājumi neizskatītos uzticami."
},
"keyboard_shortcuts": {
"label": "Īsinājumtaustiņi",
"description": "Skatīt pieejamos tastatūras īsinājumtaustiņus",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Afzender-favicons (Experimenteel)",
"description": "Toon websitepictogrammen als profielfoto's voor zakelijke afzenders"
},
"show_avatars_in_junk": {
"label": "Avatars tonen in de map ongewenst",
"description": "Toon profielfoto's en favicons van afzenders in de map ongewenst. Standaard uitgeschakeld zodat phishingpogingen geen vertrouwd uiterlijk krijgen."
},
"keyboard_shortcuts": {
"label": "Sneltoetsen",
"description": "Bekijk beschikbare sneltoetsen",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Favikony nadawców (eksperymentalne)",
"description": "Pokazuj ikony stron internetowych jako zdjęcia profilowe nadawców firmowych"
},
"show_avatars_in_junk": {
"label": "Pokazuj awatary w folderze spam",
"description": "Pokazuj zdjęcia profilowe i favikony nadawców w folderze spam. Domyślnie wyłączone, aby próby phishingu nie wyglądały na wiarygodne."
},
"keyboard_shortcuts": {
"label": "Skróty klawiszowe",
"description": "Wyświetl dostępne skróty klawiszowe",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Favicons de remetente (Experimental)",
"description": "Exibir ícones de sites como fotos de perfil para remetentes empresariais"
},
"show_avatars_in_junk": {
"label": "Mostrar avatares na pasta de spam",
"description": "Exibir imagens de perfil e favicons dos remetentes na pasta de spam. Desativado por padrão para não dar aparência legítima a tentativas de phishing."
},
"keyboard_shortcuts": {
"label": "Atalhos de Teclado",
"description": "Visualizar atalhos de teclado disponíveis",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Фавиконы отправителей (Экспериментально)",
"description": "Показывать иконки сайтов как аватары для корпоративных отправителей"
},
"show_avatars_in_junk": {
"label": "Показывать аватары в папке «Спам»",
"description": "Показывать аватары и фавиконы отправителей в папке «Спам». По умолчанию отключено, чтобы фишинговые письма не выглядели правдоподобно."
},
"keyboard_shortcuts": {
"label": "Сочетания клавиш",
"description": "Просмотреть доступные сочетания клавиш",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "Favicons відправника (експериментальний)",
"description": "Показувати піктограми веб-сайтів як зображення профілю для бізнес-відправників"
},
"show_avatars_in_junk": {
"label": "Показувати аватари в папці «Спам»",
"description": "Показувати зображення профілю та фавікони відправників у папці «Спам». Типово вимкнено, щоб фішингові листи не виглядали достовірно."
},
"keyboard_shortcuts": {
"label": "Комбінації клавіш",
"description": "Переглянути доступні комбінації клавіш",
+4
View File
@@ -1285,6 +1285,10 @@
"label": "发件人图标(实验性)",
"description": "使用企业发件人的网站图标作为头像"
},
"show_avatars_in_junk": {
"label": "在垃圾邮件文件夹中显示头像",
"description": "在垃圾邮件文件夹中显示发件人的头像和网站图标。默认关闭,避免钓鱼邮件因熟悉的图标看起来更可信。"
},
"keyboard_shortcuts": {
"label": "键盘快捷键",
"description": "查看可用快捷键",
+3
View File
@@ -187,6 +187,7 @@ interface SettingsState {
// Experimental
senderFavicons: boolean;
showAvatarsInJunk: boolean; // Show profile images/favicons in the junk folder
// Sidebar
colorfulSidebarIcons: boolean; // Tint folder icons by role (inbox blue, junk red, etc.)
@@ -334,6 +335,7 @@ const DEFAULT_SETTINGS = {
// Experimental
senderFavicons: true,
showAvatarsInJunk: false,
// Sidebar
colorfulSidebarIcons: true,
@@ -475,6 +477,7 @@ export const useSettingsStore = create<SettingsState>()(
showRailAccountList: state.showRailAccountList,
enableUnifiedMailbox: state.enableUnifiedMailbox,
senderFavicons: state.senderFavicons,
showAvatarsInJunk: state.showAvatarsInJunk,
colorfulSidebarIcons: state.colorfulSidebarIcons,
folderIcons: state.folderIcons,
emailKeywords: state.emailKeywords,