diff --git a/components/email/__tests__/viewer-send-failures.test.tsx b/components/email/__tests__/viewer-send-failures.test.tsx index fa19d2702..27250067a 100644 --- a/components/email/__tests__/viewer-send-failures.test.tsx +++ b/components/email/__tests__/viewer-send-failures.test.tsx @@ -76,6 +76,7 @@ function makeEmail(overrides: Record = {}): Email { let toastError: ReturnType; beforeEach(() => { + useSettingsStore.setState({ quickReplyEnabled: true }); toastError = vi.spyOn(toast, 'error').mockImplementation(() => {}); vi.spyOn(console, 'error').mockImplementation(() => {}); }); @@ -85,6 +86,15 @@ afterEach(() => { }); describe('viewer send failures', () => { + it('hides quick reply when disabled and restores it when enabled', () => { + useSettingsStore.setState({ quickReplyEnabled: false }); + render(); + + expect(screen.queryByPlaceholderText('quick_reply_placeholder')).not.toBeInTheDocument(); + act(() => useSettingsStore.getState().updateSetting('quickReplyEnabled', true)); + expect(screen.getByPlaceholderText('quick_reply_placeholder')).toBeInTheDocument(); + }); + it('reports a failed quick reply and keeps the text on Stalwart 1.0', async () => { const onQuickReply = vi.fn().mockRejectedValue(new SubmissionQuotaError(QUOTA, true)); render( diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index e87cf8318..c5bb5c58b 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -1084,6 +1084,7 @@ export function EmailViewer({ const tContextMenu = useTranslations('context_menu'); const copyLink = useCopyLink(); const tWelcome = useTranslations('welcome'); + const quickReplyEnabled = useSettingsStore((state) => state.quickReplyEnabled); const externalContentPolicy = useSettingsStore((state) => state.externalContentPolicy); const messageSpacing = useSettingsStore((state) => state.messageSpacing); const plainTextFont = useSettingsStore((state) => state.plainTextFont); @@ -6380,7 +6381,7 @@ export function EmailViewer({ )} {/* Quick Reply Section - hidden for drafts and while loading a new email */} - {!mvReplyRow && !isDraft && !isScheduled && !isBodyLoading && (effectiveEmailContent.isHtml ? iframeReady : true) && (
+ {quickReplyEnabled && !mvReplyRow && !isDraft && !isScheduled && !isBodyLoading && (effectiveEmailContent.isHtml ? iframeReady : true) && (
+ + updateSetting('quickReplyEnabled', checked)} + /> + + { useSettingsStore.getState().resetToDefaults(); }); + it('enables quick reply by default and exports the disabled preference', () => { + expect(useSettingsStore.getState().quickReplyEnabled).toBe(true); + useSettingsStore.getState().updateSetting('quickReplyEnabled', false); + expect(JSON.parse(useSettingsStore.getState().exportSettings()).quickReplyEnabled).toBe(false); + useSettingsStore.getState().resetToDefaults(); + expect(useSettingsStore.getState().quickReplyEnabled).toBe(true); + }); + it('defaults to preview when settings are reset', () => { expect(useSettingsStore.getState().mailAttachmentAction).toBe('preview'); }); diff --git a/stores/settings-store.ts b/stores/settings-store.ts index 217931e2f..c1fafd300 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -528,6 +528,7 @@ interface SettingsState { // Composer autoSaveDraftInterval: number; // milliseconds sendConfirmation: boolean; + quickReplyEnabled: boolean; defaultReplyMode: ReplyMode; autoSelectReplyIdentity: boolean; replyIdentityMatch: ReplyIdentityMatch; // With autoSelectReplyIdentity on: 'exact' = configured identities only, 'domain' = also same-domain catch-all addresses (rewrites From) #1000 @@ -836,6 +837,7 @@ const DEFAULT_SETTINGS = { // Composer autoSaveDraftInterval: 60000, // 1 minute sendConfirmation: false, + quickReplyEnabled: true, defaultReplyMode: 'reply' as ReplyMode, autoSelectReplyIdentity: false, replyIdentityMatch: 'domain' as ReplyIdentityMatch, @@ -1202,6 +1204,7 @@ export const useSettingsStore = create()( trustedSenders: state.trustedSenders, autoSaveDraftInterval: state.autoSaveDraftInterval, sendConfirmation: state.sendConfirmation, + quickReplyEnabled: state.quickReplyEnabled, defaultReplyMode: state.defaultReplyMode, autoSelectReplyIdentity: state.autoSelectReplyIdentity, replyIdentityMatch: state.replyIdentityMatch,