feat: configurable sub-addressing delimiter #239

This commit is contained in:
Linus Rath
2026-05-01 01:42:06 +02:00
parent a8db02e881
commit c555973b6b
23 changed files with 252 additions and 41 deletions
+5 -4
View File
@@ -124,6 +124,7 @@ export function EmailComposer({
const tCommon = useTranslations('common'); const tCommon = useTranslations('common');
const timeFormat = useSettingsStore((state) => state.timeFormat); const timeFormat = useSettingsStore((state) => state.timeFormat);
const plainTextMode = useSettingsStore((state) => state.plainTextMode); const plainTextMode = useSettingsStore((state) => state.plainTextMode);
const subAddressDelimiter = useSettingsStore((state) => state.subAddressDelimiter);
const autoSelectReplyIdentity = useSettingsStore((state) => state.autoSelectReplyIdentity); const autoSelectReplyIdentity = useSettingsStore((state) => state.autoSelectReplyIdentity);
const attachmentReminderEnabled = useSettingsStore((state) => state.attachmentReminderEnabled); const attachmentReminderEnabled = useSettingsStore((state) => state.attachmentReminderEnabled);
const attachmentReminderKeywords = useSettingsStore((state) => state.attachmentReminderKeywords); const attachmentReminderKeywords = useSettingsStore((state) => state.attachmentReminderKeywords);
@@ -729,7 +730,7 @@ export function EmailComposer({
// Generate sub-addressed email if tag is set // Generate sub-addressed email if tag is set
const fromEmail = currentIdentity?.email const fromEmail = currentIdentity?.email
? subAddressTag ? subAddressTag
? generateSubAddress(currentIdentity.email, subAddressTag) ? generateSubAddress(currentIdentity.email, subAddressTag, subAddressDelimiter)
: currentIdentity.email : currentIdentity.email
: undefined; : undefined;
@@ -906,7 +907,7 @@ export function EmailComposer({
const fromEmail = currentIdentity?.email const fromEmail = currentIdentity?.email
? subAddressTag ? subAddressTag
? generateSubAddress(currentIdentity.email, subAddressTag) ? generateSubAddress(currentIdentity.email, subAddressTag, subAddressDelimiter)
: currentIdentity.email : currentIdentity.email
: undefined; : undefined;
@@ -1215,7 +1216,7 @@ export function EmailComposer({
> >
{identities.map((identity) => { {identities.map((identity) => {
const displayEmail = subAddressTag const displayEmail = subAddressTag
? generateSubAddress(identity.email, subAddressTag) ? generateSubAddress(identity.email, subAddressTag, subAddressDelimiter)
: identity.email; : identity.email;
return ( return (
<option key={identity.id} value={identity.id}> <option key={identity.id} value={identity.id}>
@@ -1228,7 +1229,7 @@ export function EmailComposer({
<span className="text-sm text-foreground flex-1 truncate"> <span className="text-sm text-foreground flex-1 truncate">
{subAddressTag ? ( {subAddressTag ? (
<span className="font-mono"> <span className="font-mono">
{generateSubAddress(primaryIdentity?.email || '', subAddressTag)} {generateSubAddress(primaryIdentity?.email || '', subAddressTag, subAddressDelimiter)}
</span> </span>
) : ( ) : (
<> <>
+6 -4
View File
@@ -5,6 +5,7 @@ import { Mail, Tag } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { Email, Identity } from '@/lib/jmap/types'; import type { Email, Identity } from '@/lib/jmap/types';
import { parseSubAddress } from '@/lib/sub-addressing'; import { parseSubAddress } from '@/lib/sub-addressing';
import { useSettingsStore } from '@/stores/settings-store';
interface EmailIdentityBadgeProps { interface EmailIdentityBadgeProps {
email: Email; email: Email;
@@ -20,12 +21,13 @@ export function EmailIdentityBadge({
className, className,
}: EmailIdentityBadgeProps) { }: EmailIdentityBadgeProps) {
const t = useTranslations('identities.badge'); const t = useTranslations('identities.badge');
const subAddressDelimiter = useSettingsStore((state) => state.subAddressDelimiter);
const fromAddress = email.from?.[0]?.email; const fromAddress = email.from?.[0]?.email;
if (!fromAddress) return null; if (!fromAddress) return null;
// Parse the from address to check for sub-addressing // Parse the from address to check for sub-addressing
const parsedFrom = parseSubAddress(fromAddress); const parsedFrom = parseSubAddress(fromAddress, subAddressDelimiter);
// Find matching identity (email sent BY the user) // Find matching identity (email sent BY the user)
const matchingIdentity = identities.find( const matchingIdentity = identities.find(
@@ -37,7 +39,7 @@ export function EmailIdentityBadge({
if (!matchingIdentity) { if (!matchingIdentity) {
// Check all TO addresses for sub-address tags matching user's identities // Check all TO addresses for sub-address tags matching user's identities
for (const recipient of email.to || []) { for (const recipient of email.to || []) {
const parsedTo = parseSubAddress(recipient.email); const parsedTo = parseSubAddress(recipient.email, subAddressDelimiter);
if (parsedTo.tag) { if (parsedTo.tag) {
// Check if this base email matches any of the user's identities // Check if this base email matches any of the user's identities
const matchingToIdentity = identities.find( const matchingToIdentity = identities.find(
@@ -70,7 +72,7 @@ export function EmailIdentityBadge({
title={t('sub_address_tag', { tag: displayTag })} title={t('sub_address_tag', { tag: displayTag })}
> >
<Tag className="w-3 h-3" /> <Tag className="w-3 h-3" />
<span className="font-mono">+{displayTag}</span> <span className="font-mono">{subAddressDelimiter}{displayTag}</span>
</div> </div>
); );
} }
@@ -114,7 +116,7 @@ export function EmailIdentityBadge({
aria-label={t('sub_address_tag', { tag: displayTag })} aria-label={t('sub_address_tag', { tag: displayTag })}
> >
<Tag className="w-3 h-3" /> <Tag className="w-3 h-3" />
<span className="font-mono">{t('subaddress_tag', { tag: displayTag })}</span> <span className="font-mono">{subAddressDelimiter}{displayTag}</span>
</div> </div>
)} )}
+4 -2
View File
@@ -7,6 +7,7 @@ import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { useIdentityStore } from '@/stores/identity-store'; import { useIdentityStore } from '@/stores/identity-store';
import { useSettingsStore } from '@/stores/settings-store';
import { import {
generateSubAddress, generateSubAddress,
extractDomain, extractDomain,
@@ -35,6 +36,7 @@ export function SubAddressHelper({
const popoverRef = useRef<HTMLDivElement>(null); const popoverRef = useRef<HTMLDivElement>(null);
const { subAddress, addRecentTag, addTagSuggestion } = useIdentityStore(); const { subAddress, addRecentTag, addTagSuggestion } = useIdentityStore();
const subAddressDelimiter = useSettingsStore((state) => state.subAddressDelimiter);
// Get suggestions based on recipient (memoized for performance) // Get suggestions based on recipient (memoized for performance)
const suggestions = useMemo(() => { const suggestions = useMemo(() => {
@@ -47,7 +49,7 @@ export function SubAddressHelper({
}, [recipientEmails]); }, [recipientEmails]);
// Generate preview // Generate preview
const preview = tag ? generateSubAddress(baseEmail, tag) : baseEmail; const preview = tag ? generateSubAddress(baseEmail, tag, subAddressDelimiter) : baseEmail;
// Close popover when clicking outside // Close popover when clicking outside
useEffect(() => { useEffect(() => {
@@ -226,7 +228,7 @@ export function SubAddressHelper({
{/* Help Text */} {/* Help Text */}
<div className="mb-3 text-xs text-muted-foreground"> <div className="mb-3 text-xs text-muted-foreground">
{t('help_text')} {t('help_text', { delimiter: subAddressDelimiter })}
</div> </div>
{/* Use Address Button */} {/* Use Address Button */}
+20 -1
View File
@@ -4,8 +4,12 @@ import { useState, useCallback } from 'react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useConfig } from '@/hooks/use-config'; import { useConfig } from '@/hooks/use-config';
import { useSettingsStore } from '@/stores/settings-store'; import { useSettingsStore } from '@/stores/settings-store';
import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
import { Mail, X } from 'lucide-react'; import { Mail, X } from 'lucide-react';
import {
SUPPORTED_SUB_ADDRESS_DELIMITERS,
type SubAddressDelimiter,
} from '@/lib/sub-addressing';
export function ComposingSettings() { export function ComposingSettings() {
const t = useTranslations('settings.email_behavior'); const t = useTranslations('settings.email_behavior');
@@ -17,6 +21,7 @@ export function ComposingSettings() {
autoSelectReplyIdentity, autoSelectReplyIdentity,
attachmentReminderEnabled, attachmentReminderEnabled,
attachmentReminderKeywords, attachmentReminderKeywords,
subAddressDelimiter,
updateSetting, updateSetting,
} = useSettingsStore(); } = useSettingsStore();
@@ -40,6 +45,20 @@ export function ComposingSettings() {
/> />
</SettingItem> </SettingItem>
<SettingItem
label={t('sub_address_delimiter.label')}
description={t('sub_address_delimiter.description', { delimiter: subAddressDelimiter })}
>
<Select
value={subAddressDelimiter}
onChange={(value) => updateSetting('subAddressDelimiter', value as SubAddressDelimiter)}
options={SUPPORTED_SUB_ADDRESS_DELIMITERS.map((delim) => ({
value: delim,
label: t('sub_address_delimiter.option', { delimiter: delim }),
}))}
/>
</SettingItem>
<SettingItem label={t('attachment_reminder.label')} description={t('attachment_reminder.description')}> <SettingItem label={t('attachment_reminder.label')} description={t('attachment_reminder.description')}>
<ToggleSwitch <ToggleSwitch
checked={attachmentReminderEnabled} checked={attachmentReminderEnabled}
+74
View File
@@ -6,6 +6,9 @@ import {
suggestTagsForDomain, suggestTagsForDomain,
isValidTag, isValidTag,
getTagValidationError, getTagValidationError,
isSupportedSubAddressDelimiter,
SUPPORTED_SUB_ADDRESS_DELIMITERS,
DEFAULT_SUB_ADDRESS_DELIMITER,
MAX_TAG_LENGTH, MAX_TAG_LENGTH,
} from '../sub-addressing'; } from '../sub-addressing';
@@ -354,3 +357,74 @@ describe('getTagValidationError', () => {
expect(getTagValidationError('日本語')).toBe('INVALID_CHARS'); expect(getTagValidationError('日本語')).toBe('INVALID_CHARS');
}); });
}); });
describe('custom delimiter', () => {
describe('parseSubAddress with non-default delimiter', () => {
it('should parse with "-" delimiter', () => {
const result = parseSubAddress('user-shopping@example.com', '-');
expect(result.baseUser).toBe('user');
expect(result.tag).toBe('shopping');
});
it('should parse with "." delimiter', () => {
const result = parseSubAddress('user.shopping@example.com', '.');
expect(result.baseUser).toBe('user');
expect(result.tag).toBe('shopping');
});
it('should parse with "=" delimiter', () => {
const result = parseSubAddress('user=shopping@example.com', '=');
expect(result.baseUser).toBe('user');
expect(result.tag).toBe('shopping');
});
it('should ignore "+" when "-" is configured as the delimiter', () => {
const result = parseSubAddress('user+shopping@example.com', '-');
expect(result.baseUser).toBe('user+shopping');
expect(result.tag).toBeNull();
});
it('should split on first occurrence when delimiter appears multiple times', () => {
const result = parseSubAddress('alice-shop-orders@example.com', '-');
expect(result.baseUser).toBe('alice');
expect(result.tag).toBe('shop-orders');
});
});
describe('generateSubAddress with non-default delimiter', () => {
it('should generate using "-" delimiter', () => {
expect(generateSubAddress('user@example.com', 'shopping', '-')).toBe('user-shopping@example.com');
});
it('should generate using "." delimiter', () => {
expect(generateSubAddress('user@example.com', 'shopping', '.')).toBe('user.shopping@example.com');
});
it('should replace existing tag using the configured delimiter', () => {
expect(generateSubAddress('user-old@example.com', 'new', '-')).toBe('user-new@example.com');
});
it('should not strip a "+" sign in the local part when delimiter is "-"', () => {
// "+" is not the delimiter so it should remain part of the base user
expect(generateSubAddress('user+plus@example.com', 'tag', '-')).toBe('user+plus-tag@example.com');
});
});
describe('isSupportedSubAddressDelimiter', () => {
it('accepts every supported delimiter', () => {
for (const delim of SUPPORTED_SUB_ADDRESS_DELIMITERS) {
expect(isSupportedSubAddressDelimiter(delim)).toBe(true);
}
});
it('rejects unsupported characters', () => {
expect(isSupportedSubAddressDelimiter('_')).toBe(false);
expect(isSupportedSubAddressDelimiter('++')).toBe(false);
expect(isSupportedSubAddressDelimiter('')).toBe(false);
});
it('default delimiter is supported', () => {
expect(isSupportedSubAddressDelimiter(DEFAULT_SUB_ADDRESS_DELIMITER)).toBe(true);
});
});
});
+36 -14
View File
@@ -1,12 +1,23 @@
/** /**
* Sub-addressing utilities for user+tag@domain.com format * Sub-addressing utilities for user{delimiter}tag@domain.com format
* Works server-side automatically - no JMAP API calls needed * Works server-side automatically - no JMAP API calls needed
*
* The delimiter character is configurable per server (RFC 5233). Common
* choices: "+" (Postfix, Stalwart default), "-" (qmail), ".", "=".
*/ */
// Constants for tag validation // Constants for tag validation
const MAX_TAG_LENGTH = 30; const MAX_TAG_LENGTH = 30;
const TAG_REGEX = /^[a-zA-Z0-9-]{1,30}$/; const TAG_REGEX = /^[a-zA-Z0-9-]{1,30}$/;
export const DEFAULT_SUB_ADDRESS_DELIMITER = '+';
export const SUPPORTED_SUB_ADDRESS_DELIMITERS = ['+', '-', '.', '='] as const;
export type SubAddressDelimiter = (typeof SUPPORTED_SUB_ADDRESS_DELIMITERS)[number];
export function isSupportedSubAddressDelimiter(value: string): value is SubAddressDelimiter {
return (SUPPORTED_SUB_ADDRESS_DELIMITERS as readonly string[]).includes(value);
}
export type TagValidationErrorCode = export type TagValidationErrorCode =
| 'EMPTY' | 'EMPTY'
| 'TOO_LONG' | 'TOO_LONG'
@@ -22,10 +33,14 @@ export interface ParsedAddress {
} }
/** /**
* Parse an email address to extract sub-address tag * Parse an email address to extract sub-address tag.
* Example: "user+shopping@example.com" -> { baseUser: "user", tag: "shopping" } * The first occurrence of the delimiter in the local part separates the
* base user from the tag, matching the behavior of Postfix/qmail/Sieve.
*/ */
export function parseSubAddress(email: string): ParsedAddress { export function parseSubAddress(
email: string,
delimiter: string = DEFAULT_SUB_ADDRESS_DELIMITER,
): ParsedAddress {
const [localPart, domain] = email.split('@'); const [localPart, domain] = email.split('@');
if (!localPart || !domain) { if (!localPart || !domain) {
@@ -38,9 +53,9 @@ export function parseSubAddress(email: string): ParsedAddress {
}; };
} }
const plusIndex = localPart.indexOf('+'); const delimiterIndex = localPart.indexOf(delimiter);
if (plusIndex === -1) { if (delimiterIndex === -1) {
return { return {
localPart, localPart,
baseUser: localPart, baseUser: localPart,
@@ -50,8 +65,8 @@ export function parseSubAddress(email: string): ParsedAddress {
}; };
} }
const baseUser = localPart.substring(0, plusIndex); const baseUser = localPart.substring(0, delimiterIndex);
const tag = localPart.substring(plusIndex + 1); const tag = localPart.substring(delimiterIndex + delimiter.length);
return { return {
localPart, localPart,
@@ -63,18 +78,25 @@ export function parseSubAddress(email: string): ParsedAddress {
} }
/** /**
* Generate a sub-addressed email * Generate a sub-addressed email.
* Example: generateSubAddress("user@example.com", "shopping") -> "user+shopping@example.com" * Example: generateSubAddress("user@example.com", "shopping", "+") -> "user+shopping@example.com"
*/ */
export function generateSubAddress(baseEmail: string, tag: string): string { export function generateSubAddress(
baseEmail: string,
tag: string,
delimiter: string = DEFAULT_SUB_ADDRESS_DELIMITER,
): string {
const [localPart, domain] = baseEmail.split('@'); const [localPart, domain] = baseEmail.split('@');
if (!localPart || !domain || !tag) { if (!localPart || !domain || !tag) {
return baseEmail; return baseEmail;
} }
// Remove existing tag if present // Strip an existing tag if one is already present
const cleanLocal = localPart.split('+')[0]; const existingDelimiterIndex = localPart.indexOf(delimiter);
const cleanLocal = existingDelimiterIndex === -1
? localPart
: localPart.substring(0, existingDelimiterIndex);
// Sanitize tag (alphanumeric and dash only) // Sanitize tag (alphanumeric and dash only)
const cleanTag = tag.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase(); const cleanTag = tag.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
@@ -83,7 +105,7 @@ export function generateSubAddress(baseEmail: string, tag: string): string {
return baseEmail; return baseEmail;
} }
return `${cleanLocal}+${cleanTag}@${domain}`; return `${cleanLocal}${delimiter}${cleanTag}@${domain}`;
} }
/** /**
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "Automaticky vybírat adresu pro odpověď", "label": "Automaticky vybírat adresu pro odpověď",
"description": "Při odpovídání automaticky přepnout adresu odesílatele na identitu, která původně obdržela zprávu" "description": "Při odpovídání automaticky přepnout adresu odesílatele na identitu, která původně obdržela zprávu"
}, },
"sub_address_delimiter": {
"label": "Oddělovač sub-adresy",
"description": "Znak oddělující uživatelské jméno od sub-adresy. Zvolte oddělovač používaný vaším poštovním serverem (např. uzivatel{delimiter}stitek@domena.cz).",
"option": "{delimiter} (uzivatel{delimiter}stitek@domena.cz)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "Akce po kliknutí na přílohu", "label": "Akce po kliknutí na přílohu",
"description": "Vyberte, zda má kliknutí na přílohu zobrazit náhled, nebo ji ihned stáhnout", "description": "Vyberte, zda má kliknutí na přílohu zobrazit náhled, nebo ji ihned stáhnout",
@@ -1756,7 +1761,7 @@
"use_address": "Použít tuto adresu", "use_address": "Použít tuto adresu",
"invalid_tag": "Štítek může obsahovat pouze písmena, číslice a pomlčky", "invalid_tag": "Štítek může obsahovat pouze písmena, číslice a pomlčky",
"tag_too_long": "Štítek může mít maximálně 30 znaků", "tag_too_long": "Štítek může mít maximálně 30 znaků",
"help_text": "Zprávy odeslané na adresu uzivatel+stitek@domena.cz budou doručeny do vaší doručené pošty", "help_text": "Zprávy odeslané na adresu uzivatel{delimiter}stitek@domena.cz budou doručeny do vaší doručené pošty",
"validation": { "validation": {
"empty": "Štítek nesmí být prázdný", "empty": "Štítek nesmí být prázdný",
"too_long": "Štítek může mít maximálně {max} znaků", "too_long": "Štítek může mít maximálně {max} znaků",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "Antwortadresse automatisch wählen", "label": "Antwortadresse automatisch wählen",
"description": "Beim Antworten die Absenderadresse automatisch auf die Identität umstellen, die die ursprüngliche Nachricht erhalten hat" "description": "Beim Antworten die Absenderadresse automatisch auf die Identität umstellen, die die ursprüngliche Nachricht erhalten hat"
}, },
"sub_address_delimiter": {
"label": "Sub-Adress-Trennzeichen",
"description": "Zeichen, das Ihren Benutzernamen vom Sub-Adress-Tag trennt. Verwenden Sie das von Ihrem Mailserver verwendete Trennzeichen (z. B. benutzer{delimiter}tag@domain.de).",
"option": "{delimiter} (benutzer{delimiter}tag@domain.de)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "Aktion beim Klick auf Anhänge", "label": "Aktion beim Klick auf Anhänge",
"description": "Festlegen, ob ein Dateianhang beim Anklicken in der Vorschau geöffnet oder sofort heruntergeladen wird", "description": "Festlegen, ob ein Dateianhang beim Anklicken in der Vorschau geöffnet oder sofort heruntergeladen wird",
@@ -1756,7 +1761,7 @@
"use_address": "Diese Adresse verwenden", "use_address": "Diese Adresse verwenden",
"invalid_tag": "Tag darf nur alphanumerisch und Bindestriche enthalten", "invalid_tag": "Tag darf nur alphanumerisch und Bindestriche enthalten",
"tag_too_long": "Tag darf maximal 30 Zeichen lang sein", "tag_too_long": "Tag darf maximal 30 Zeichen lang sein",
"help_text": "E-Mails an benutzer+tag@domain.de werden in Ihrem Posteingang ankommen", "help_text": "E-Mails an benutzer{delimiter}tag@domain.de werden in Ihrem Posteingang ankommen",
"validation": { "validation": {
"empty": "Tag darf nicht leer sein", "empty": "Tag darf nicht leer sein",
"too_long": "Tag darf maximal {max} Zeichen lang sein", "too_long": "Tag darf maximal {max} Zeichen lang sein",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "Auto-select Reply Address", "label": "Auto-select Reply Address",
"description": "When replying, automatically switch the From address to the identity that originally received the message" "description": "When replying, automatically switch the From address to the identity that originally received the message"
}, },
"sub_address_delimiter": {
"label": "Sub-Address Delimiter",
"description": "Character separating your username from a sub-address tag. Match the delimiter your mail server uses (e.g. user{delimiter}tag@domain.com).",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "Attachment Click Action", "label": "Attachment Click Action",
"description": "Choose whether clicking a file attachment previews it or downloads it immediately", "description": "Choose whether clicking a file attachment previews it or downloads it immediately",
@@ -1756,7 +1761,7 @@
"use_address": "Use This Address", "use_address": "Use This Address",
"invalid_tag": "Tag must be alphanumeric and dashes only", "invalid_tag": "Tag must be alphanumeric and dashes only",
"tag_too_long": "Tag must be 30 characters or less", "tag_too_long": "Tag must be 30 characters or less",
"help_text": "Emails sent to user+tag@domain.com will arrive in your inbox", "help_text": "Emails sent to user{delimiter}tag@domain.com will arrive in your inbox",
"validation": { "validation": {
"empty": "Tag cannot be empty", "empty": "Tag cannot be empty",
"too_long": "Tag must be {max} characters or less", "too_long": "Tag must be {max} characters or less",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Seleccionar dirección de respuesta automáticamente", "label": "Seleccionar dirección de respuesta automáticamente",
"description": "Al responder, cambia automáticamente la dirección del remitente a la identidad que recibió el mensaje original" "description": "Al responder, cambia automáticamente la dirección del remitente a la identidad que recibió el mensaje original"
}, },
"sub_address_delimiter": {
"label": "Delimitador de sub-dirección",
"description": "Carácter que separa tu nombre de usuario de la etiqueta de sub-dirección. Usa el delimitador que utilice tu servidor de correo (por ejemplo, usuario{delimiter}etiqueta@dominio.com).",
"option": "{delimiter} (usuario{delimiter}etiqueta@dominio.com)"
},
"show_preview": { "show_preview": {
"label": "Mostrar Vista Previa", "label": "Mostrar Vista Previa",
"description": "Mostrar vista previa del correo en la lista", "description": "Mostrar vista previa del correo en la lista",
@@ -1756,7 +1761,7 @@
"use_address": "Usar Esta Dirección", "use_address": "Usar Esta Dirección",
"invalid_tag": "La etiqueta debe ser solo alfanumérica y guiones", "invalid_tag": "La etiqueta debe ser solo alfanumérica y guiones",
"tag_too_long": "La etiqueta debe tener 30 caracteres o menos", "tag_too_long": "La etiqueta debe tener 30 caracteres o menos",
"help_text": "Los correos enviados a usuario+etiqueta@dominio.com llegarán a su bandeja de entrada", "help_text": "Los correos enviados a usuario{delimiter}etiqueta@dominio.com llegarán a su bandeja de entrada",
"validation": { "validation": {
"empty": "La etiqueta no puede estar vacía", "empty": "La etiqueta no puede estar vacía",
"too_long": "La etiqueta debe tener {max} caracteres o menos", "too_long": "La etiqueta debe tener {max} caracteres o menos",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Sélection automatique de l'adresse de réponse", "label": "Sélection automatique de l'adresse de réponse",
"description": "Lors d'une réponse, bascule automatiquement l'adresse d'expédition vers l'identité qui a reçu le message d'origine" "description": "Lors d'une réponse, bascule automatiquement l'adresse d'expédition vers l'identité qui a reçu le message d'origine"
}, },
"sub_address_delimiter": {
"label": "Délimiteur de sous-adresse",
"description": "Caractère séparant votre nom d'utilisateur de l'étiquette de sous-adresse. Utilisez le délimiteur configuré sur votre serveur de messagerie (par ex. utilisateur{delimiter}tag@domaine.com).",
"option": "{delimiter} (utilisateur{delimiter}tag@domaine.com)"
},
"show_preview": { "show_preview": {
"label": "Afficher l'aperçu", "label": "Afficher l'aperçu",
"description": "Afficher l'aperçu de l'email dans la liste", "description": "Afficher l'aperçu de l'email dans la liste",
@@ -1756,7 +1761,7 @@
"use_address": "Utiliser cette adresse", "use_address": "Utiliser cette adresse",
"invalid_tag": "Le tag doit contenir uniquement des lettres, chiffres et tirets", "invalid_tag": "Le tag doit contenir uniquement des lettres, chiffres et tirets",
"tag_too_long": "Le tag doit faire 30 caractères ou moins", "tag_too_long": "Le tag doit faire 30 caractères ou moins",
"help_text": "Les emails envoyés à utilisateur+tag@domaine.com arriveront dans votre boîte de réception", "help_text": "Les emails envoyés à utilisateur{delimiter}tag@domaine.com arriveront dans votre boîte de réception",
"validation": { "validation": {
"empty": "Le tag ne peut pas être vide", "empty": "Le tag ne peut pas être vide",
"too_long": "Le tag doit faire {max} caractères ou moins", "too_long": "Le tag doit faire {max} caractères ou moins",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Seleziona automaticamente l'indirizzo di risposta", "label": "Seleziona automaticamente l'indirizzo di risposta",
"description": "Quando rispondi, passa automaticamente l'indirizzo mittente all'identità che ha ricevuto il messaggio originale" "description": "Quando rispondi, passa automaticamente l'indirizzo mittente all'identità che ha ricevuto il messaggio originale"
}, },
"sub_address_delimiter": {
"label": "Delimitatore sub-indirizzo",
"description": "Carattere che separa il tuo nome utente dall'etichetta del sub-indirizzo. Usa il delimitatore configurato sul tuo server di posta (es. utente{delimiter}tag@dominio.com).",
"option": "{delimiter} (utente{delimiter}tag@dominio.com)"
},
"show_preview": { "show_preview": {
"label": "Mostra anteprima testo", "label": "Mostra anteprima testo",
"description": "Visualizza l'anteprima del messaggio nell'elenco", "description": "Visualizza l'anteprima del messaggio nell'elenco",
@@ -1756,7 +1761,7 @@
"use_address": "Usa questo indirizzo", "use_address": "Usa questo indirizzo",
"invalid_tag": "Il tag deve contenere solo caratteri alfanumerici e trattini", "invalid_tag": "Il tag deve contenere solo caratteri alfanumerici e trattini",
"tag_too_long": "Il tag deve essere di massimo 30 caratteri", "tag_too_long": "Il tag deve essere di massimo 30 caratteri",
"help_text": "I messaggi inviati a utente+tag@dominio.com arriveranno nella tua casella di posta", "help_text": "I messaggi inviati a utente{delimiter}tag@dominio.com arriveranno nella tua casella di posta",
"validation": { "validation": {
"empty": "Il tag non può essere vuoto", "empty": "Il tag non può essere vuoto",
"too_long": "Il tag deve essere di massimo {max} caratteri", "too_long": "Il tag deve essere di massimo {max} caratteri",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "返信元アドレスを自動選択", "label": "返信元アドレスを自動選択",
"description": "返信時に、元のメッセージを受信したIDへ差出人アドレスを自動的に切り替えます" "description": "返信時に、元のメッセージを受信したIDへ差出人アドレスを自動的に切り替えます"
}, },
"sub_address_delimiter": {
"label": "サブアドレス区切り文字",
"description": "ユーザー名とサブアドレスタグを区切る文字です。お使いのメールサーバーが使用する区切り文字に合わせてください(例: user{delimiter}tag@domain.com)。",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"show_preview": { "show_preview": {
"label": "プレビューテキストを表示", "label": "プレビューテキストを表示",
"description": "リストにメールのプレビューを表示", "description": "リストにメールのプレビューを表示",
@@ -1756,7 +1761,7 @@
"use_address": "このアドレスを使用", "use_address": "このアドレスを使用",
"invalid_tag": "タグは英数字とハイフンのみ使用できます", "invalid_tag": "タグは英数字とハイフンのみ使用できます",
"tag_too_long": "タグは30文字以内にしてください", "tag_too_long": "タグは30文字以内にしてください",
"help_text": "user+tag@domain.comに送信されたメールは受信トレイに届きます", "help_text": "user{delimiter}tag@domain.comに送信されたメールは受信トレイに届きます",
"validation": { "validation": {
"empty": "タグは空にできません", "empty": "タグは空にできません",
"too_long": "タグは{max}文字以内にしてください", "too_long": "タグは{max}文字以内にしてください",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "답장 시 보내는 사람 자동 선택", "label": "답장 시 보내는 사람 자동 선택",
"description": "답장할 때 메일을 받았던 주소로 보내는 사람을 자동으로 변경해요" "description": "답장할 때 메일을 받았던 주소로 보내는 사람을 자동으로 변경해요"
}, },
"sub_address_delimiter": {
"label": "서브 주소 구분자",
"description": "사용자 이름과 서브 주소 태그를 나누는 문자예요. 메일 서버가 사용하는 구분자에 맞춰 주세요 (예: user{delimiter}tag@domain.com).",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "첨부파일 클릭 동작", "label": "첨부파일 클릭 동작",
"description": "파일을 클릭했을 때 미리보기를 할지, 바로 다운로드할지 선택해 주세요", "description": "파일을 클릭했을 때 미리보기를 할지, 바로 다운로드할지 선택해 주세요",
@@ -1756,7 +1761,7 @@
"use_address": "이 주소 사용하기", "use_address": "이 주소 사용하기",
"invalid_tag": "태그는 알파벳, 숫자, 대시(-)만 쓸 수 있어요", "invalid_tag": "태그는 알파벳, 숫자, 대시(-)만 쓸 수 있어요",
"tag_too_long": "태그는 30자 이하여야 해요", "tag_too_long": "태그는 30자 이하여야 해요",
"help_text": "user+tag@domain.com 으로 보낸 메일은 내 받은편지함으로 들어와요", "help_text": "user{delimiter}tag@domain.com 으로 보낸 메일은 내 받은편지함으로 들어와요",
"validation": { "validation": {
"empty": "태그를 비워둘 수 없어요", "empty": "태그를 비워둘 수 없어요",
"too_long": "태그는 {max}자 이하여야 해요", "too_long": "태그는 {max}자 이하여야 해요",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Automātiski izvēlēties atbildes adresi", "label": "Automātiski izvēlēties atbildes adresi",
"description": "Atbildot automātiski izmantot to kontu, uz kuru vēstule tika saņemta" "description": "Atbildot automātiski izmantot to kontu, uz kuru vēstule tika saņemta"
}, },
"sub_address_delimiter": {
"label": "Apakšadreses atdalītājs",
"description": "Zīme, kas atdala lietotājvārdu no apakšadreses tagu. Izvēlieties atdalītāju, ko lieto jūsu pasta serveris (piem. lietotajs{delimiter}tags@domens.lv).",
"option": "{delimiter} (lietotajs{delimiter}tags@domens.lv)"
},
"show_preview": { "show_preview": {
"label": "Rādīt priekšskatījuma tekstu", "label": "Rādīt priekšskatījuma tekstu",
"description": "Rādīt vēstules fragmentu sarakstā", "description": "Rādīt vēstules fragmentu sarakstā",
@@ -1756,7 +1761,7 @@
"use_address": "Izmantot šo adresi", "use_address": "Izmantot šo adresi",
"invalid_tag": "Tags var saturēt tikai burtus, ciparus un domuzīmes", "invalid_tag": "Tags var saturēt tikai burtus, ciparus un domuzīmes",
"tag_too_long": "Tags nedrīkst pārsniegt 30 rakstzīmes", "tag_too_long": "Tags nedrīkst pārsniegt 30 rakstzīmes",
"help_text": "Vēstules uz lietotajs+tags@domens.lv nonāks jūsu pastkastē", "help_text": "Vēstules uz lietotajs{delimiter}tags@domens.lv nonāks jūsu pastkastē",
"validation": { "validation": {
"empty": "Tags nevar būt tukšs", "empty": "Tags nevar būt tukšs",
"too_long": "Tags nedrīkst pārsniegt {max} rakstzīmes", "too_long": "Tags nedrīkst pārsniegt {max} rakstzīmes",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Antwoordadres automatisch selecteren", "label": "Antwoordadres automatisch selecteren",
"description": "Schakel bij het beantwoorden automatisch het Van-adres om naar de identiteit die het oorspronkelijke bericht ontving" "description": "Schakel bij het beantwoorden automatisch het Van-adres om naar de identiteit die het oorspronkelijke bericht ontving"
}, },
"sub_address_delimiter": {
"label": "Sub-adres scheidingsteken",
"description": "Teken dat je gebruikersnaam scheidt van het sub-adres-label. Gebruik het scheidingsteken dat je mailserver gebruikt (bv. gebruiker{delimiter}tag@domein.nl).",
"option": "{delimiter} (gebruiker{delimiter}tag@domein.nl)"
},
"show_preview": { "show_preview": {
"label": "Voorbeeldtekst tonen", "label": "Voorbeeldtekst tonen",
"description": "E-mailvoorbeeld weergeven in de lijst", "description": "E-mailvoorbeeld weergeven in de lijst",
@@ -1756,7 +1761,7 @@
"use_address": "Dit adres gebruiken", "use_address": "Dit adres gebruiken",
"invalid_tag": "Tag mag alleen letters, cijfers en streepjes bevatten", "invalid_tag": "Tag mag alleen letters, cijfers en streepjes bevatten",
"tag_too_long": "Tag mag maximaal 30 tekens bevatten", "tag_too_long": "Tag mag maximaal 30 tekens bevatten",
"help_text": "E-mails verzonden naar gebruiker+tag@domein.nl komen in je postvak IN aan", "help_text": "E-mails verzonden naar gebruiker{delimiter}tag@domein.nl komen in je postvak IN aan",
"validation": { "validation": {
"empty": "Tag mag niet leeg zijn", "empty": "Tag mag niet leeg zijn",
"too_long": "Tag mag maximaal {max} tekens bevatten", "too_long": "Tag mag maximaal {max} tekens bevatten",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "Automatycznie wybieraj adres odpowiedzi", "label": "Automatycznie wybieraj adres odpowiedzi",
"description": "Podczas odpowiadania automatycznie przełączaj adres nadawcy na tożsamość, która pierwotnie otrzymała wiadomość" "description": "Podczas odpowiadania automatycznie przełączaj adres nadawcy na tożsamość, która pierwotnie otrzymała wiadomość"
}, },
"sub_address_delimiter": {
"label": "Separator sub-adresu",
"description": "Znak oddzielający Twoją nazwę użytkownika od tagu sub-adresu. Użyj separatora zgodnego z Twoim serwerem pocztowym (np. user{delimiter}tag@domain.com).",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "Akcja po kliknięciu załącznika", "label": "Akcja po kliknięciu załącznika",
"description": "Wybierz, czy kliknięcie załącznika pliku ma pokazać podgląd, czy od razu go pobrać", "description": "Wybierz, czy kliknięcie załącznika pliku ma pokazać podgląd, czy od razu go pobrać",
@@ -1756,7 +1761,7 @@
"use_address": "Użyj tego adresu", "use_address": "Użyj tego adresu",
"invalid_tag": "Tag może zawierać tylko litery, cyfry i myślniki", "invalid_tag": "Tag może zawierać tylko litery, cyfry i myślniki",
"tag_too_long": "Tag może mieć maksymalnie 30 znaków", "tag_too_long": "Tag może mieć maksymalnie 30 znaków",
"help_text": "Wiadomości wysłane na adres user+tag@domain.com trafią do Twojej skrzynki odbiorczej", "help_text": "Wiadomości wysłane na adres user{delimiter}tag@domain.com trafią do Twojej skrzynki odbiorczej",
"validation": { "validation": {
"empty": "Tag nie może być pusty", "empty": "Tag nie może być pusty",
"too_long": "Tag może mieć maksymalnie {max} znaków", "too_long": "Tag może mieć maksymalnie {max} znaków",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Selecionar automaticamente o endereço de resposta", "label": "Selecionar automaticamente o endereço de resposta",
"description": "Ao responder, muda automaticamente o endereço do remetente para a identidade que recebeu a mensagem original" "description": "Ao responder, muda automaticamente o endereço do remetente para a identidade que recebeu a mensagem original"
}, },
"sub_address_delimiter": {
"label": "Delimitador de sub-endereço",
"description": "Caractere que separa seu nome de usuário da tag de sub-endereço. Use o delimitador configurado no seu servidor de e-mail (ex.: usuario{delimiter}tag@dominio.com).",
"option": "{delimiter} (usuario{delimiter}tag@dominio.com)"
},
"show_preview": { "show_preview": {
"label": "Mostrar Texto de Visualização", "label": "Mostrar Texto de Visualização",
"description": "Exibir visualização do e-mail na lista", "description": "Exibir visualização do e-mail na lista",
@@ -1756,7 +1761,7 @@
"use_address": "Usar Este Endereço", "use_address": "Usar Este Endereço",
"invalid_tag": "A tag deve conter apenas caracteres alfanuméricos e hífens", "invalid_tag": "A tag deve conter apenas caracteres alfanuméricos e hífens",
"tag_too_long": "A tag deve ter no máximo 30 caracteres", "tag_too_long": "A tag deve ter no máximo 30 caracteres",
"help_text": "E-mails enviados para usuario+tag@dominio.com chegarão na sua caixa de entrada", "help_text": "E-mails enviados para usuario{delimiter}tag@dominio.com chegarão na sua caixa de entrada",
"validation": { "validation": {
"empty": "A tag não pode estar vazia", "empty": "A tag não pode estar vazia",
"too_long": "A tag deve ter no máximo {max} caracteres", "too_long": "A tag deve ter no máximo {max} caracteres",
+6 -1
View File
@@ -906,6 +906,11 @@
"label": "Автоматически выбирать адрес для ответа", "label": "Автоматически выбирать адрес для ответа",
"description": "При ответе автоматически переключать адрес отправителя на ту учетную запись, которая получила исходное сообщение" "description": "При ответе автоматически переключать адрес отправителя на ту учетную запись, которая получила исходное сообщение"
}, },
"sub_address_delimiter": {
"label": "Разделитель суб-адресов",
"description": "Символ, отделяющий имя пользователя от тега суб-адреса. Используйте разделитель, настроенный на вашем почтовом сервере (например, user{delimiter}tag@domain.com).",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"show_preview": { "show_preview": {
"label": "Показывать текст предпросмотра", "label": "Показывать текст предпросмотра",
"description": "Отображать предпросмотр письма в списке", "description": "Отображать предпросмотр письма в списке",
@@ -1756,7 +1761,7 @@
"use_address": "Использовать этот адрес", "use_address": "Использовать этот адрес",
"invalid_tag": "Тег должен содержать только буквы, цифры и дефисы", "invalid_tag": "Тег должен содержать только буквы, цифры и дефисы",
"tag_too_long": "Тег не должен превышать 30 символов", "tag_too_long": "Тег не должен превышать 30 символов",
"help_text": "Письма на адрес user+tag@domain.com будут приходить в ваш ящик", "help_text": "Письма на адрес user{delimiter}tag@domain.com будут приходить в ваш ящик",
"validation": { "validation": {
"empty": "Тег не может быть пустым", "empty": "Тег не может быть пустым",
"too_long": "Тег не должен превышать {max} символов", "too_long": "Тег не должен превышать {max} символов",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "Yanıt Adresini Otomatik Seç", "label": "Yanıt Adresini Otomatik Seç",
"description": "Yanıtlarken, Kimden adresini iletiyi başlangıçta alan kimliğe otomatik olarak değiştir" "description": "Yanıtlarken, Kimden adresini iletiyi başlangıçta alan kimliğe otomatik olarak değiştir"
}, },
"sub_address_delimiter": {
"label": "Alt Adres Ayırıcı",
"description": "Kullanıcı adını alt adres etiketinden ayıran karakter. Posta sunucunuzun kullandığı ayırıcıyı seçin (ör. kullanici{delimiter}etiket@domain.com).",
"option": "{delimiter} (kullanici{delimiter}etiket@domain.com)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "Ek Tıklama İşlemi", "label": "Ek Tıklama İşlemi",
"description": "Bir dosya ekine tıklandığında önizleme mi yoksa anında indirme mi yapılacağını seçin", "description": "Bir dosya ekine tıklandığında önizleme mi yoksa anında indirme mi yapılacağını seçin",
@@ -1756,7 +1761,7 @@
"use_address": "Bu Adresi Kullan", "use_address": "Bu Adresi Kullan",
"invalid_tag": "Etiket yalnızca harf, rakam ve tire içermelidir", "invalid_tag": "Etiket yalnızca harf, rakam ve tire içermelidir",
"tag_too_long": "Etiket en fazla 30 karakter olmalıdır", "tag_too_long": "Etiket en fazla 30 karakter olmalıdır",
"help_text": "kullanici+etiket@domain.com adresine gönderilen e-postalar gelen kutunuza ulaşır", "help_text": "kullanici{delimiter}etiket@domain.com adresine gönderilen e-postalar gelen kutunuza ulaşır",
"validation": { "validation": {
"empty": "Etiket boş olamaz", "empty": "Etiket boş olamaz",
"too_long": "Etiket en fazla {max} karakter olmalıdır", "too_long": "Etiket en fazla {max} karakter olmalıdır",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "Автоматичний вибір адреси для відповіді", "label": "Автоматичний вибір адреси для відповіді",
"description": "Під час відповіді автоматично змінюйте адресу відправника на особу, яка спочатку отримала повідомлення" "description": "Під час відповіді автоматично змінюйте адресу відправника на особу, яка спочатку отримала повідомлення"
}, },
"sub_address_delimiter": {
"label": "Розділювач під-адреси",
"description": "Символ, який відокремлює ім'я користувача від мітки під-адреси. Використовуйте розділювач, налаштований на вашому поштовому сервері (напр. user{delimiter}tag@domain.com).",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "Вкладення Натисніть Дія", "label": "Вкладення Натисніть Дія",
"description": "Виберіть, чи клацання вкладеного файлу попередньо переглядає його чи негайно завантажує", "description": "Виберіть, чи клацання вкладеного файлу попередньо переглядає його чи негайно завантажує",
@@ -1756,7 +1761,7 @@
"use_address": "Використовуйте цю адресу", "use_address": "Використовуйте цю адресу",
"invalid_tag": "Тег має бути лише буквено-цифровим і тире", "invalid_tag": "Тег має бути лише буквено-цифровим і тире",
"tag_too_long": "Тег має містити 30 символів або менше", "tag_too_long": "Тег має містити 30 символів або менше",
"help_text": "Електронні листи, надіслані на user+tag@domain.com, надходитимуть до вашої скриньки", "help_text": "Електронні листи, надіслані на user{delimiter}tag@domain.com, надходитимуть до вашої скриньки",
"validation": { "validation": {
"empty": "Тег не може бути порожнім", "empty": "Тег не може бути порожнім",
"too_long": "Тег має містити не більше {max} символів", "too_long": "Тег має містити не більше {max} символів",
+6 -1
View File
@@ -911,6 +911,11 @@
"label": "自动选择回复地址", "label": "自动选择回复地址",
"description": "回复时自动将发件人地址切换为最初收到该邮件的身份" "description": "回复时自动将发件人地址切换为最初收到该邮件的身份"
}, },
"sub_address_delimiter": {
"label": "子地址分隔符",
"description": "用于分隔用户名和子地址标签的字符。请选择与您的邮件服务器一致的分隔符(例如 user{delimiter}tag@domain.com)。",
"option": "{delimiter} (user{delimiter}tag@domain.com)"
},
"attachment_click_action": { "attachment_click_action": {
"label": "附件单击操作", "label": "附件单击操作",
"description": "选择点击附件时是预览还是直接下载", "description": "选择点击附件时是预览还是直接下载",
@@ -1756,7 +1761,7 @@
"use_address": "使用此地址", "use_address": "使用此地址",
"invalid_tag": "标签只能是字母数字和破折号", "invalid_tag": "标签只能是字母数字和破折号",
"tag_too_long": "标签不得超过 30 个字符", "tag_too_long": "标签不得超过 30 个字符",
"help_text": "发送到 user+tag@domain.com 的邮件将送达您的收件箱", "help_text": "发送到 user{delimiter}tag@domain.com 的邮件将送达您的收件箱",
"validation": { "validation": {
"empty": "标签不能为空", "empty": "标签不能为空",
"too_long": "标签不得超过 {max} 个字符", "too_long": "标签不得超过 {max} 个字符",
+11
View File
@@ -4,6 +4,11 @@ import { useThemeStore } from './theme-store';
import { useLocaleStore } from './locale-store'; import { useLocaleStore } from './locale-store';
import type { NotificationSoundChoice } from '@/lib/notification-sound'; import type { NotificationSoundChoice } from '@/lib/notification-sound';
import { apiFetch } from '@/lib/browser-navigation'; import { apiFetch } from '@/lib/browser-navigation';
import {
DEFAULT_SUB_ADDRESS_DELIMITER,
isSupportedSubAddressDelimiter,
type SubAddressDelimiter,
} from '@/lib/sub-addressing';
// Use console directly to avoid circular dependency with lib/debug.ts // Use console directly to avoid circular dependency with lib/debug.ts
// (debug.ts imports useSettingsStore for debugMode check) // (debug.ts imports useSettingsStore for debugMode check)
@@ -138,6 +143,7 @@ interface SettingsState {
defaultReplyMode: ReplyMode; defaultReplyMode: ReplyMode;
autoSelectReplyIdentity: boolean; autoSelectReplyIdentity: boolean;
plainTextMode: boolean; // Send plain text only (no rich text editor) plainTextMode: boolean; // Send plain text only (no rich text editor)
subAddressDelimiter: SubAddressDelimiter; // Character separating user from tag (e.g. "user+tag@")
// Privacy & Security // Privacy & Security
sessionTimeout: number; // minutes (0 = never) sessionTimeout: number; // minutes (0 = never)
@@ -286,6 +292,7 @@ const DEFAULT_SETTINGS = {
defaultReplyMode: 'reply' as ReplyMode, defaultReplyMode: 'reply' as ReplyMode,
autoSelectReplyIdentity: false, autoSelectReplyIdentity: false,
plainTextMode: false, plainTextMode: false,
subAddressDelimiter: DEFAULT_SUB_ADDRESS_DELIMITER as SubAddressDelimiter,
// Privacy & Security // Privacy & Security
sessionTimeout: 0, // Never sessionTimeout: 0, // Never
@@ -456,6 +463,7 @@ export const useSettingsStore = create<SettingsState>()(
defaultReplyMode: state.defaultReplyMode, defaultReplyMode: state.defaultReplyMode,
autoSelectReplyIdentity: state.autoSelectReplyIdentity, autoSelectReplyIdentity: state.autoSelectReplyIdentity,
plainTextMode: state.plainTextMode, plainTextMode: state.plainTextMode,
subAddressDelimiter: state.subAddressDelimiter,
sessionTimeout: state.sessionTimeout, sessionTimeout: state.sessionTimeout,
emailNotificationsEnabled: state.emailNotificationsEnabled, emailNotificationsEnabled: state.emailNotificationsEnabled,
emailNotificationSound: state.emailNotificationSound, emailNotificationSound: state.emailNotificationSound,
@@ -508,6 +516,9 @@ export const useSettingsStore = create<SettingsState>()(
// Apply settings // Apply settings
Object.keys(settings).forEach((key) => { Object.keys(settings).forEach((key) => {
if (key in DEFAULT_SETTINGS) { if (key in DEFAULT_SETTINGS) {
if (key === 'subAddressDelimiter' && !isSupportedSubAddressDelimiter(settings[key])) {
return;
}
set({ [key]: settings[key] }); set({ [key]: settings[key] });
} }
}); });