feat: add auto-select reply identity feature with settings and localization
This commit is contained in:
@@ -1539,6 +1539,7 @@ export default function Home() {
|
|||||||
from: selectedEmail.from,
|
from: selectedEmail.from,
|
||||||
to: selectedEmail.to,
|
to: selectedEmail.to,
|
||||||
cc: selectedEmail.cc,
|
cc: selectedEmail.cc,
|
||||||
|
bcc: selectedEmail.bcc,
|
||||||
subject: selectedEmail.subject,
|
subject: selectedEmail.subject,
|
||||||
body: selectedEmail.bodyValues?.[selectedEmail.textBody?.[0]?.partId || '']?.value || selectedEmail.preview || '',
|
body: selectedEmail.bodyValues?.[selectedEmail.textBody?.[0]?.partId || '']?.value || selectedEmail.preview || '',
|
||||||
htmlBody: selectedEmail.bodyValues?.[selectedEmail.htmlBody?.[0]?.partId || '']?.value || undefined,
|
htmlBody: selectedEmail.bodyValues?.[selectedEmail.htmlBody?.[0]?.partId || '']?.value || undefined,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const RESTRICTABLE_SETTINGS = [
|
|||||||
{ key: 'externalContentPolicy', label: 'External Content Policy', category: 'Email', type: 'enum', allowedValues: ['allow', 'block', 'ask'] },
|
{ key: 'externalContentPolicy', label: 'External Content Policy', category: 'Email', type: 'enum', allowedValues: ['allow', 'block', 'ask'] },
|
||||||
{ key: 'sendConfirmation', label: 'Send Confirmation', category: 'Composer', type: 'boolean' },
|
{ key: 'sendConfirmation', label: 'Send Confirmation', category: 'Composer', type: 'boolean' },
|
||||||
{ key: 'defaultReplyMode', label: 'Default Reply Mode', category: 'Composer', type: 'enum', allowedValues: ['reply', 'reply-all'] },
|
{ key: 'defaultReplyMode', label: 'Default Reply Mode', category: 'Composer', type: 'enum', allowedValues: ['reply', 'reply-all'] },
|
||||||
|
{ key: 'autoSelectReplyIdentity', label: 'Auto-select Reply Identity', category: 'Composer', type: 'boolean' },
|
||||||
{ key: 'plainTextMode', label: 'Plain Text Only', category: 'Composer', type: 'boolean' },
|
{ key: 'plainTextMode', label: 'Plain Text Only', category: 'Composer', type: 'boolean' },
|
||||||
{ key: 'sessionTimeout', label: 'Session Timeout', category: 'Privacy', type: 'number' },
|
{ key: 'sessionTimeout', label: 'Session Timeout', category: 'Privacy', type: 'number' },
|
||||||
{ key: 'emailNotificationsEnabled', label: 'Email Notifications', category: 'Notifications', type: 'boolean' },
|
{ key: 'emailNotificationsEnabled', label: 'Email Notifications', category: 'Notifications', type: 'boolean' },
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { TemplatePicker } from "@/components/templates/template-picker";
|
|||||||
import { TemplateForm } from "@/components/templates/template-form";
|
import { TemplateForm } from "@/components/templates/template-form";
|
||||||
import type { EmailTemplate } from "@/lib/template-types";
|
import type { EmailTemplate } from "@/lib/template-types";
|
||||||
import { appendPlainTextSignature, getPlainTextSignature } from "@/lib/signature-utils";
|
import { appendPlainTextSignature, getPlainTextSignature } from "@/lib/signature-utils";
|
||||||
|
import { findReplyIdentityId } from "@/lib/reply-identity";
|
||||||
import { RichTextEditor } from "@/components/email/rich-text-editor";
|
import { RichTextEditor } from "@/components/email/rich-text-editor";
|
||||||
|
|
||||||
/** Strip HTML tags and decode entities to get a plain-text version */
|
/** Strip HTML tags and decode entities to get a plain-text version */
|
||||||
@@ -78,6 +79,7 @@ interface EmailComposerProps {
|
|||||||
from?: { email?: string; name?: string }[];
|
from?: { email?: string; name?: string }[];
|
||||||
to?: { email?: string; name?: string }[];
|
to?: { email?: string; name?: string }[];
|
||||||
cc?: { email?: string; name?: string }[];
|
cc?: { email?: string; name?: string }[];
|
||||||
|
bcc?: { email?: string; name?: string }[];
|
||||||
subject?: string;
|
subject?: string;
|
||||||
body?: string;
|
body?: string;
|
||||||
htmlBody?: string;
|
htmlBody?: string;
|
||||||
@@ -100,6 +102,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 autoSelectReplyIdentity = useSettingsStore((state) => state.autoSelectReplyIdentity);
|
||||||
|
|
||||||
// Initialize with reply/forward data if provided
|
// Initialize with reply/forward data if provided
|
||||||
const getInitialTo = () => {
|
const getInitialTo = () => {
|
||||||
@@ -226,6 +229,31 @@ export function EmailComposer({
|
|||||||
const currentIdentity = selectedIdentityId
|
const currentIdentity = selectedIdentityId
|
||||||
? identities.find((identity) => identity.id === selectedIdentityId) || primaryIdentity
|
? identities.find((identity) => identity.id === selectedIdentityId) || primaryIdentity
|
||||||
: primaryIdentity;
|
: primaryIdentity;
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoSelectReplyIdentity) return;
|
||||||
|
if (selectedIdentityId || initialData?.selectedIdentityId) return;
|
||||||
|
if (mode !== 'reply' && mode !== 'replyAll') return;
|
||||||
|
|
||||||
|
const matchedIdentityId = findReplyIdentityId(identities, {
|
||||||
|
to: replyTo?.to,
|
||||||
|
cc: replyTo?.cc,
|
||||||
|
bcc: replyTo?.bcc,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchedIdentityId) {
|
||||||
|
setSelectedIdentityId(matchedIdentityId);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
autoSelectReplyIdentity,
|
||||||
|
identities,
|
||||||
|
initialData?.selectedIdentityId,
|
||||||
|
mode,
|
||||||
|
replyTo?.bcc,
|
||||||
|
replyTo?.cc,
|
||||||
|
replyTo?.to,
|
||||||
|
selectedIdentityId,
|
||||||
|
]);
|
||||||
|
|
||||||
const composerSignatureHtml = currentIdentity?.htmlSignature
|
const composerSignatureHtml = currentIdentity?.htmlSignature
|
||||||
? `<div>${sanitizeEmailHtml(currentIdentity.htmlSignature)}</div>`
|
? `<div>${sanitizeEmailHtml(currentIdentity.htmlSignature)}</div>`
|
||||||
: currentIdentity?.textSignature
|
: currentIdentity?.textSignature
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export function EmailSettings() {
|
|||||||
permanentlyDeleteJunk,
|
permanentlyDeleteJunk,
|
||||||
showPreview,
|
showPreview,
|
||||||
disableThreading,
|
disableThreading,
|
||||||
|
autoSelectReplyIdentity,
|
||||||
plainTextMode,
|
plainTextMode,
|
||||||
emailsPerPage,
|
emailsPerPage,
|
||||||
externalContentPolicy,
|
externalContentPolicy,
|
||||||
@@ -230,6 +231,13 @@ export function EmailSettings() {
|
|||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem label={t('auto_select_reply_identity.label')} description={t('auto_select_reply_identity.description')}>
|
||||||
|
<ToggleSwitch
|
||||||
|
checked={autoSelectReplyIdentity}
|
||||||
|
onChange={(checked) => updateSetting('autoSelectReplyIdentity', checked)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
{/* Quick Hover Actions */}
|
{/* Quick Hover Actions */}
|
||||||
{isFeatureEnabled('hoverActionsConfigEnabled') && (
|
{isFeatureEnabled('hoverActionsConfigEnabled') && (
|
||||||
<div className="py-3 border-b border-border space-y-3">
|
<div className="py-3 border-b border-border space-y-3">
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { findReplyIdentityId } from '../reply-identity';
|
||||||
|
import type { Identity } from '../jmap/types';
|
||||||
|
|
||||||
|
const identities: Identity[] = [
|
||||||
|
{
|
||||||
|
id: 'primary',
|
||||||
|
name: 'Harry Primary',
|
||||||
|
email: 'harry@primary.com',
|
||||||
|
mayDelete: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'secondary',
|
||||||
|
name: 'Harry Secondary',
|
||||||
|
email: 'harry@secondary.com',
|
||||||
|
mayDelete: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('findReplyIdentityId', () => {
|
||||||
|
it('matches the identity that received the original message', () => {
|
||||||
|
const selected = findReplyIdentityId(identities, {
|
||||||
|
to: [{ email: 'harry@secondary.com' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(selected).toBe('secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches case-insensitively across recipients', () => {
|
||||||
|
const selected = findReplyIdentityId(identities, {
|
||||||
|
cc: [{ email: 'HARRY@PRIMARY.COM' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(selected).toBe('primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to sub-address matching when needed', () => {
|
||||||
|
const selected = findReplyIdentityId(identities, {
|
||||||
|
to: [{ email: 'harry+news@secondary.com' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(selected).toBe('secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no reply recipient matches an identity', () => {
|
||||||
|
const selected = findReplyIdentityId(identities, {
|
||||||
|
to: [{ email: 'other@example.com' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(selected).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { Identity } from '@/lib/jmap/types';
|
||||||
|
|
||||||
|
interface ReplyRecipient {
|
||||||
|
email?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplyRecipients {
|
||||||
|
to?: ReplyRecipient[];
|
||||||
|
cc?: ReplyRecipient[];
|
||||||
|
bcc?: ReplyRecipient[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEmailAddress(email: string): string {
|
||||||
|
return email.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBaseEmailAddress(email: string): string {
|
||||||
|
const normalized = normalizeEmailAddress(email);
|
||||||
|
const atIndex = normalized.indexOf('@');
|
||||||
|
|
||||||
|
if (atIndex <= 0) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPart = normalized.slice(0, atIndex);
|
||||||
|
const domain = normalized.slice(atIndex + 1);
|
||||||
|
const plusIndex = localPart.indexOf('+');
|
||||||
|
|
||||||
|
return `${plusIndex >= 0 ? localPart.slice(0, plusIndex) : localPart}@${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findReplyIdentityId(
|
||||||
|
identities: Identity[],
|
||||||
|
recipients?: ReplyRecipients,
|
||||||
|
): string | null {
|
||||||
|
if (identities.length === 0 || !recipients) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const receivedAddresses = [
|
||||||
|
...(recipients.to || []),
|
||||||
|
...(recipients.cc || []),
|
||||||
|
...(recipients.bcc || []),
|
||||||
|
]
|
||||||
|
.map((recipient) => recipient.email?.trim())
|
||||||
|
.filter((email): email is string => Boolean(email));
|
||||||
|
|
||||||
|
if (receivedAddresses.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exactMatches = new Set(receivedAddresses.map(normalizeEmailAddress));
|
||||||
|
const exactIdentity = identities.find((identity) => exactMatches.has(normalizeEmailAddress(identity.email)));
|
||||||
|
if (exactIdentity) {
|
||||||
|
return exactIdentity.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseMatches = new Set(receivedAddresses.map(normalizeBaseEmailAddress));
|
||||||
|
const baseIdentity = identities.find((identity) => baseMatches.has(normalizeBaseEmailAddress(identity.email)));
|
||||||
|
|
||||||
|
return baseIdentity?.id ?? null;
|
||||||
|
}
|
||||||
@@ -811,6 +811,10 @@
|
|||||||
"label": "Nur Klartext",
|
"label": "Nur Klartext",
|
||||||
"description": "Rich-Text-Editor deaktivieren und alle E-Mails nur als Klartext senden, einschließlich Antworten und Weiterleitungen"
|
"description": "Rich-Text-Editor deaktivieren und alle E-Mails nur als Klartext senden, einschließlich Antworten und Weiterleitungen"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"label": "Antwortadresse automatisch wählen",
|
||||||
|
"description": "Beim Antworten die Absenderadresse automatisch auf die Identität umstellen, die die ursprüngliche Nachricht erhalten hat"
|
||||||
|
},
|
||||||
"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",
|
||||||
|
|||||||
@@ -811,6 +811,10 @@
|
|||||||
"label": "Plain Text Only",
|
"label": "Plain Text Only",
|
||||||
"description": "Disable the rich text editor and send all emails as plain text only, including replies and forwards"
|
"description": "Disable the rich text editor and send all emails as plain text only, including replies and forwards"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"label": "Auto-select Reply Address",
|
||||||
|
"description": "When replying, automatically switch the From address to the identity that originally received the message"
|
||||||
|
},
|
||||||
"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",
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "Solo texto sin formato",
|
"label": "Solo texto sin formato",
|
||||||
"description": "Desactivar el editor de texto enriquecido y enviar todos los correos solo como texto sin formato, incluyendo respuestas y reenvíos"
|
"description": "Desactivar el editor de texto enriquecido y enviar todos los correos solo como texto sin formato, incluyendo respuestas y reenvíos"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
"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"
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "Texte brut uniquement",
|
"label": "Texte brut uniquement",
|
||||||
"description": "Désactiver l'éditeur de texte enrichi et envoyer tous les e-mails en texte brut uniquement, y compris les réponses et les transferts"
|
"description": "Désactiver l'éditeur de texte enrichi et envoyer tous les e-mails en texte brut uniquement, y compris les réponses et les transferts"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
"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"
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "Solo testo normale",
|
"label": "Solo testo normale",
|
||||||
"description": "Disabilita l'editor di testo formattato e invia tutte le email solo come testo normale, incluse risposte e inoltri"
|
"description": "Disabilita l'editor di testo formattato e invia tutte le email solo come testo normale, incluse risposte e inoltri"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"label": "Seleziona automaticamente l'indirizzo di risposta",
|
||||||
|
"description": "Quando rispondi, passa automaticamente l'indirizzo mittente all'identità che ha ricevuto il messaggio originale"
|
||||||
|
},
|
||||||
"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"
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "プレーンテキストのみ",
|
"label": "プレーンテキストのみ",
|
||||||
"description": "リッチテキストエディターを無効にし、返信や転送を含むすべてのメールをプレーンテキストのみで送信します"
|
"description": "リッチテキストエディターを無効にし、返信や転送を含むすべてのメールをプレーンテキストのみで送信します"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"label": "返信元アドレスを自動選択",
|
||||||
|
"description": "返信時に、元のメッセージを受信したIDへ差出人アドレスを自動的に切り替えます"
|
||||||
|
},
|
||||||
"show_preview": {
|
"show_preview": {
|
||||||
"label": "プレビューテキストを表示",
|
"label": "プレビューテキストを表示",
|
||||||
"description": "リストにメールのプレビューを表示"
|
"description": "リストにメールのプレビューを表示"
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "Alleen platte tekst",
|
"label": "Alleen platte tekst",
|
||||||
"description": "Schakel de rich text-editor uit en verzend alle e-mails alleen als platte tekst, inclusief antwoorden en doorgestuurde berichten"
|
"description": "Schakel de rich text-editor uit en verzend alle e-mails alleen als platte tekst, inclusief antwoorden en doorgestuurde berichten"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"label": "Antwoordadres automatisch selecteren",
|
||||||
|
"description": "Schakel bij het beantwoorden automatisch het Van-adres om naar de identiteit die het oorspronkelijke bericht ontving"
|
||||||
|
},
|
||||||
"show_preview": {
|
"show_preview": {
|
||||||
"label": "Voorbeeldtekst tonen",
|
"label": "Voorbeeldtekst tonen",
|
||||||
"description": "E-mailvoorbeeld weergeven in de lijst"
|
"description": "E-mailvoorbeeld weergeven in de lijst"
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "Apenas texto simples",
|
"label": "Apenas texto simples",
|
||||||
"description": "Desativar o editor de texto formatado e enviar todos os e-mails apenas como texto simples, incluindo respostas e encaminhamentos"
|
"description": "Desativar o editor de texto formatado e enviar todos os e-mails apenas como texto simples, incluindo respostas e encaminhamentos"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
"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"
|
||||||
|
|||||||
@@ -807,6 +807,10 @@
|
|||||||
"label": "Только простой текст",
|
"label": "Только простой текст",
|
||||||
"description": "Отключить редактор форматированного текста и отправлять все письма только в виде простого текста, включая ответы и пересылки"
|
"description": "Отключить редактор форматированного текста и отправлять все письма только в виде простого текста, включая ответы и пересылки"
|
||||||
},
|
},
|
||||||
|
"auto_select_reply_identity": {
|
||||||
|
"label": "Автоматически выбирать адрес для ответа",
|
||||||
|
"description": "При ответе автоматически переключать адрес отправителя на ту учетную запись, которая получила исходное сообщение"
|
||||||
|
},
|
||||||
"show_preview": {
|
"show_preview": {
|
||||||
"label": "Показывать текст предпросмотра",
|
"label": "Показывать текст предпросмотра",
|
||||||
"description": "Отображать предпросмотр письма в списке"
|
"description": "Отображать предпросмотр письма в списке"
|
||||||
|
|||||||
@@ -29,4 +29,14 @@ describe('settings-store attachment action', () => {
|
|||||||
|
|
||||||
expect(exported.calendarInvitationParsingEnabled).toBe(false);
|
expect(exported.calendarInvitationParsingEnabled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('includes reply identity auto-selection in exported settings', () => {
|
||||||
|
useSettingsStore.getState().updateSetting('autoSelectReplyIdentity', true);
|
||||||
|
|
||||||
|
const exported = JSON.parse(useSettingsStore.getState().exportSettings()) as {
|
||||||
|
autoSelectReplyIdentity?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(exported.autoSelectReplyIdentity).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -119,6 +119,7 @@ interface SettingsState {
|
|||||||
autoSaveDraftInterval: number; // milliseconds
|
autoSaveDraftInterval: number; // milliseconds
|
||||||
sendConfirmation: boolean;
|
sendConfirmation: boolean;
|
||||||
defaultReplyMode: ReplyMode;
|
defaultReplyMode: ReplyMode;
|
||||||
|
autoSelectReplyIdentity: boolean;
|
||||||
plainTextMode: boolean; // Send plain text only (no rich text editor)
|
plainTextMode: boolean; // Send plain text only (no rich text editor)
|
||||||
|
|
||||||
// Privacy & Security
|
// Privacy & Security
|
||||||
@@ -238,6 +239,7 @@ const DEFAULT_SETTINGS = {
|
|||||||
autoSaveDraftInterval: 60000, // 1 minute
|
autoSaveDraftInterval: 60000, // 1 minute
|
||||||
sendConfirmation: false,
|
sendConfirmation: false,
|
||||||
defaultReplyMode: 'reply' as ReplyMode,
|
defaultReplyMode: 'reply' as ReplyMode,
|
||||||
|
autoSelectReplyIdentity: false,
|
||||||
plainTextMode: false,
|
plainTextMode: false,
|
||||||
|
|
||||||
// Privacy & Security
|
// Privacy & Security
|
||||||
@@ -346,6 +348,7 @@ export const useSettingsStore = create<SettingsState>()(
|
|||||||
autoSaveDraftInterval: state.autoSaveDraftInterval,
|
autoSaveDraftInterval: state.autoSaveDraftInterval,
|
||||||
sendConfirmation: state.sendConfirmation,
|
sendConfirmation: state.sendConfirmation,
|
||||||
defaultReplyMode: state.defaultReplyMode,
|
defaultReplyMode: state.defaultReplyMode,
|
||||||
|
autoSelectReplyIdentity: state.autoSelectReplyIdentity,
|
||||||
plainTextMode: state.plainTextMode,
|
plainTextMode: state.plainTextMode,
|
||||||
sessionTimeout: state.sessionTimeout,
|
sessionTimeout: state.sessionTimeout,
|
||||||
emailNotificationsEnabled: state.emailNotificationsEnabled,
|
emailNotificationsEnabled: state.emailNotificationsEnabled,
|
||||||
|
|||||||
Reference in New Issue
Block a user