diff --git a/components/settings/__tests__/vacation-html-body.test.tsx b/components/settings/__tests__/vacation-html-body.test.tsx index efda0de52..46035ede3 100644 --- a/components/settings/__tests__/vacation-html-body.test.tsx +++ b/components/settings/__tests__/vacation-html-body.test.tsx @@ -78,4 +78,31 @@ describe('HTML auto-reply and its plain-text part', () => { await settle(); expect(sends(server)).toBe(1); }, 15_000); + + it('derives the text again after a reload, and keeps it when HTML is switched off', async () => { + const server = mockStalwartAccount('b', [], ['vacation'], { + vacation: { htmlBody: '
Back on 5 May.
', textBody: 'Back on 5 May.' }, + }); + await openCard(server); + expect(body().value).toBe(''); + fireEvent.change(editor(), { target: { value: 'Back on 12 May.
' } }); + await saved(server, 1); + expect(lastSent(server)).toMatchObject({ htmlBody: 'Back on 12 May.
', textBody: 'Back on 12 May.' }); + + fireEvent.click(htmlSwitch()); + expect(body().value).toBe('Back on 12 May.'); + await saved(server, 2); + expect(lastSent(server)).toMatchObject({ htmlBody: null, textBody: 'Back on 12 May.' }); + }, 15_000); + + it('keeps a plain-text part the user wrote', async () => { + const server = mockStalwartAccount('b', [], ['vacation'], { + vacation: { htmlBody: 'Back on 5 May.
', textBody: 'Back soon.' }, + }); + await openCard(server); + expect(body().value).toBe('Back soon.'); + fireEvent.change(editor(), { target: { value: 'Back on 12 May.
' } }); + await saved(server, 1); + expect(lastSent(server)).toMatchObject({ htmlBody: 'Back on 12 May.
', textBody: 'Back soon.' }); + }, 15_000); }); diff --git a/components/settings/vacation-settings.tsx b/components/settings/vacation-settings.tsx index c2d7bd270..34b324aeb 100644 --- a/components/settings/vacation-settings.tsx +++ b/components/settings/vacation-settings.tsx @@ -33,6 +33,11 @@ const STALWART_VACATION_LIMITS = { subject: 511, body: 2047 }; const SAVE_AT_ONCE = 0; const SAVE_AFTER_TYPING = 800; +/** The plain-text part a save derives from the sanitized HTML. */ +function textFromHtml(sanitizedHtml: string): string { + return htmlToPlainText(sanitizedHtml, { paragraphSpacing: true }); +} + function byteLength(value: string): number { return new TextEncoder().encode(value).length; } @@ -210,7 +215,11 @@ export function VacationSettings() { setInputZone(timeZone); setHalfFilled({ from: false, to: false }); setLocalSubject(stored.subject); - setLocalTextBody(stored.textBody); + // Text a save derived from the HTML leaves the Body blank, so the next + // HTML edit derives it again instead of keeping the old wording. + setLocalTextBody(stored.htmlBody && stored.textBody.trim() === textFromHtml(sanitizeEmailHtml(stored.htmlBody)).trim() + ? '' + : stored.textBody); setHtmlEnabled(!!stored.htmlBody); setLocalHtmlBody(stored.htmlBody || ''); } @@ -296,10 +305,7 @@ export function VacationSettings() { htmlEnabled && htmlToPlainText(localHtmlBody).trim() ? sanitizeEmailHtml(localHtmlBody) : null; - const text = - localTextBody.trim() || !sanitizedHtml - ? localTextBody - : htmlToPlainText(sanitizedHtml, { paragraphSpacing: true }); + const text = localTextBody.trim() || !sanitizedHtml ? localTextBody : textFromHtml(sanitizedHtml); return { sanitizedHtml, textBody: text }; }, [htmlEnabled, localHtmlBody, localTextBody]); @@ -588,7 +594,14 @@ export function VacationSettings() { label={t('message.html_label')} description={t('message.html_description')} > -