diff --git a/components/settings/email-settings.tsx b/components/settings/email-settings.tsx index baac6ff..595eb00 100644 --- a/components/settings/email-settings.tsx +++ b/components/settings/email-settings.tsx @@ -18,6 +18,7 @@ export function EmailSettings() { emailsPerPage, externalContentPolicy, trustedSenders, + domainFaviconAvatars, updateSetting, } = useSettingsStore(); @@ -101,6 +102,14 @@ export function EmailSettings() { + {/* Domain Favicon Avatars */} + + updateSetting('domainFaviconAvatars', checked)} + /> + + {/* Trusted Senders Modal */} s.domainFaviconAvatars); + const [faviconOk, setFaviconOk] = useState(false); + const domain = domainFaviconAvatars ? extractDomain(email) : null; + + useEffect(() => { + // Reset load state when the domain changes so we retry for a new sender. + setFaviconOk(false); + }, [domain]); + const getInitials = () => { if (name) { const parts = name.trim().split(/\s+/); @@ -41,14 +74,30 @@ export function Avatar({ name, email, size = "md", className }: AvatarProps) { return (
- {getInitials()} + {/* Initials render behind so there's no flash if the favicon loads late or fails */} + {getInitials()} + {domain && ( + setFaviconOk(true)} + onError={() => setFaviconOk(false)} + className={cn( + "absolute inset-0 w-full h-full object-cover transition-opacity duration-150", + faviconOk ? "opacity-100" : "opacity-0" + )} + /> + )}
); -} \ No newline at end of file +} diff --git a/locales/de/common.json b/locales/de/common.json index 24b2bcf..c4b51a8 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -668,6 +668,10 @@ "close": "Schließen", "invalid_email": "Bitte geben Sie eine gültige E-Mail-Adresse ein", "already_added": "Dieser Absender ist bereits vertrauenswürdig" + }, + "domain_favicon_avatars": { + "label": "Domain-Avatare", + "description": "Wenn ein Kontakt kein Bild hat, wird das Favicon der Absenderdomain anstelle der Initialen angezeigt. Wird von einem öffentlichen Favicon-Dienst nur anhand des Domain-Namens bezogen (keine E-Mail-Adresse, kein Hashing). Standardmäßig deaktiviert." } }, "composer": { diff --git a/locales/en/common.json b/locales/en/common.json index f637a8e..ccaf6f7 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -676,6 +676,10 @@ "close": "Close", "invalid_email": "Please enter a valid email address", "already_added": "This sender is already trusted" + }, + "domain_favicon_avatars": { + "label": "Domain avatars", + "description": "When a contact has no picture, show the sender domain's favicon instead of initials. Fetched from a public favicon service using only the domain name (no email address, no hashing). Off by default." } }, "composer": { diff --git a/locales/es/common.json b/locales/es/common.json index 96128ec..5afa8bd 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -668,6 +668,10 @@ "close": "Cerrar", "invalid_email": "Por favor ingrese una dirección de correo válida", "already_added": "Este remitente ya es de confianza" + }, + "domain_favicon_avatars": { + "label": "Avatares por dominio", + "description": "Cuando un contacto no tiene foto, se muestra el favicon del dominio del remitente en lugar de las iniciales. Se obtiene de un servicio público de favicons usando únicamente el nombre de dominio (sin dirección de correo ni hash). Desactivado por defecto." } }, "composer": { diff --git a/locales/fr/common.json b/locales/fr/common.json index 621e8c0..8ed20f8 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -668,6 +668,10 @@ "close": "Fermer", "invalid_email": "Veuillez entrer une adresse email valide", "already_added": "Cet expéditeur est déjà de confiance" + }, + "domain_favicon_avatars": { + "label": "Avatars par domaine", + "description": "Quand un contact n'a pas de photo, afficher le favicon du domaine de l'expéditeur au lieu des initiales. Récupéré via un service public de favicons en utilisant uniquement le nom de domaine (sans adresse e-mail ni hachage). Désactivé par défaut." } }, "composer": { diff --git a/locales/it/common.json b/locales/it/common.json index 2dc406f..6659b72 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -668,6 +668,10 @@ "close": "Chiudi", "invalid_email": "Inserisci un indirizzo email valido", "already_added": "Questo mittente è già attendibile" + }, + "domain_favicon_avatars": { + "label": "Avatar per dominio", + "description": "Quando un contatto non ha una foto, viene mostrato il favicon del dominio del mittente invece delle iniziali. Recuperato da un servizio pubblico di favicon usando solo il nome di dominio (nessun indirizzo email, nessun hash). Disattivato per impostazione predefinita." } }, "composer": { diff --git a/locales/ja/common.json b/locales/ja/common.json index 576e258..46d4958 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -668,6 +668,10 @@ "close": "閉じる", "invalid_email": "有効なメールアドレスを入力してください", "already_added": "この送信者はすでに信頼されています" + }, + "domain_favicon_avatars": { + "label": "ドメインアバター", + "description": "連絡先に写真がない場合、イニシャルの代わりに送信者ドメインのファビコンを表示します。公開ファビコンサービスからドメイン名のみを使って取得します(メールアドレスやハッシュは送信しません)。既定では無効です。" } }, "composer": { diff --git a/locales/nl/common.json b/locales/nl/common.json index 45e5517..dc9a8f2 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -668,6 +668,10 @@ "close": "Sluiten", "invalid_email": "Voer een geldig e-mailadres in", "already_added": "Deze afzender wordt al vertrouwd" + }, + "domain_favicon_avatars": { + "label": "Domein-avatars", + "description": "Als een contact geen foto heeft, wordt het favicon van het domein van de afzender getoond in plaats van initialen. Opgehaald van een openbare favicon-dienst op basis van alleen de domeinnaam (geen e-mailadres, geen hashing). Standaard uitgeschakeld." } }, "composer": { diff --git a/locales/pt/common.json b/locales/pt/common.json index 9d97bbd..0a2a937 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -668,6 +668,10 @@ "close": "Fechar", "invalid_email": "Por favor, digite um endereço de e-mail válido", "already_added": "Este remetente já é confiável" + }, + "domain_favicon_avatars": { + "label": "Avatares por domínio", + "description": "Quando um contacto não tem fotografia, o favicon do domínio do remetente é apresentado em vez das iniciais. Obtido de um serviço público de favicons usando apenas o nome de domínio (sem endereço de e-mail nem hash). Desativado por omissão." } }, "composer": { diff --git a/locales/ru/common.json b/locales/ru/common.json index 8e1c418..c05d7b3 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -676,6 +676,10 @@ "close": "Закрыть", "invalid_email": "Пожалуйста, введите действительный адрес почты", "already_added": "Этому отправителю уже доверяют" + }, + "domain_favicon_avatars": { + "label": "Аватары по домену", + "description": "Если у контакта нет фотографии, вместо инициалов показывается фавикон домена отправителя. Берётся из публичного сервиса фавиконов только по имени домена (без адреса электронной почты и хеша). По умолчанию выключено." } }, "composer": { diff --git a/locales/uk/common.json b/locales/uk/common.json index 2a17375..58d814e 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -676,6 +676,10 @@ "close": "Закрити", "invalid_email": "Введіть дійсну адресу пошти", "already_added": "Цей відправник уже є надійним" + }, + "domain_favicon_avatars": { + "label": "Аватари за доменом", + "description": "Якщо в контакта немає фотографії, замість ініціалів показується фавікон домену відправника. Отримується з публічного сервісу фавіконів лише за іменем домену (без адреси електронної пошти й хешу). Стандартно вимкнено." } }, "composer": { diff --git a/stores/settings-store.ts b/stores/settings-store.ts index d56c942..abfaccd 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -36,6 +36,7 @@ interface SettingsState { // Privacy & Security sessionTimeout: number; // minutes (0 = never) trustedSenders: string[]; // Email addresses that can load external content + domainFaviconAvatars: boolean; // Use sender domain favicons as avatars (off by default; opt-in to avoid any third-party request) // Calendar Notifications calendarNotificationsEnabled: boolean; @@ -88,6 +89,7 @@ const DEFAULT_SETTINGS = { // Privacy & Security sessionTimeout: 0, // Never trustedSenders: [] as string[], + domainFaviconAvatars: false, // Calendar Notifications calendarNotificationsEnabled: true,