mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: follow the typed subject in a Pro compose tab's title
This commit is contained in:
@@ -348,6 +348,8 @@ interface EmailComposerProps {
|
||||
*/
|
||||
onDiscardDraft?: (draftId: string, draft?: { accountId?: string; localAccountId?: string }) => void;
|
||||
onSaveState?: (data: ComposerDraftData) => void;
|
||||
/** The subject as typed, for a host that shows it (a Pro compose tab's title). */
|
||||
onSubjectChange?: (subject: string) => void;
|
||||
className?: string;
|
||||
initialDraftText?: string;
|
||||
initialData?: ComposerDraftData | null;
|
||||
@@ -494,6 +496,7 @@ export function EmailComposer({
|
||||
requestCloseRef,
|
||||
onDiscardDraft,
|
||||
onSaveState,
|
||||
onSubjectChange,
|
||||
className,
|
||||
initialDraftText: initialDraftTextProp,
|
||||
initialData: initialDataProp,
|
||||
@@ -1828,6 +1831,10 @@ export function EmailComposer({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [effectiveSessionKey]);
|
||||
|
||||
useEffect(() => {
|
||||
onSubjectChange?.(subject);
|
||||
}, [onSubjectChange, subject]);
|
||||
|
||||
// Auto-save state on unmount (when user navigates away without explicitly closing)
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@@ -5,7 +5,13 @@ import React from 'react';
|
||||
// The mail tab hands every compose tab the selected message as replyTo, a new
|
||||
// message too. A new message was titled after whatever message was open.
|
||||
|
||||
vi.mock('@/components/email/email-composer', () => ({ EmailComposer: () => React.createElement('div') }));
|
||||
const composer = vi.hoisted(() => ({ onSubjectChange: null as ((s: string) => void) | null }));
|
||||
vi.mock('@/components/email/email-composer', () => ({
|
||||
EmailComposer: (props: { onSubjectChange: (s: string) => void }) => {
|
||||
composer.onSubjectChange = props.onSubjectChange;
|
||||
return React.createElement('div');
|
||||
},
|
||||
}));
|
||||
vi.mock('@/components/error', () => ({
|
||||
ErrorBoundary: ({ children }: { children: React.ReactNode }) => React.createElement(React.Fragment, null, children),
|
||||
ComposerErrorFallback: () => null,
|
||||
@@ -35,20 +41,23 @@ import { ProComposeTabBody } from '../pro-compose-tab-body';
|
||||
const selected = { subject: 'Invoice August' };
|
||||
|
||||
describe('Pro compose tab title', () => {
|
||||
beforeEach(() => updateTabTitle.mockClear());
|
||||
|
||||
it('stays the new-message title with a message selected in the mail tab', () => {
|
||||
render(<ProComposeTabBody tabId="t" data={{ sessionId: 1, mode: 'compose', title: 'New message', replyTo: selected }} />);
|
||||
expect(updateTabTitle).toHaveBeenLastCalledWith('t', 'New message');
|
||||
beforeEach(() => {
|
||||
updateTabTitle.mockClear();
|
||||
composer.onSubjectChange = null;
|
||||
});
|
||||
|
||||
it('keeps the prefix the opener gave a reply', () => {
|
||||
render(<ProComposeTabBody tabId="t" data={{ sessionId: 1, mode: 'reply', title: 'Re: Invoice August', replyTo: selected }} />);
|
||||
expect(updateTabTitle).toHaveBeenLastCalledWith('t', 'Re: Invoice August');
|
||||
it('does not take the title of the message selected in the mail tab', () => {
|
||||
render(<ProComposeTabBody tabId="t" data={{ sessionId: 1, mode: 'compose', title: 'email_composer.new_message', replyTo: selected }} />);
|
||||
composer.onSubjectChange!('');
|
||||
expect(updateTabTitle).toHaveBeenLastCalledWith('t', 'email_composer.new_message');
|
||||
expect(updateTabTitle).not.toHaveBeenCalledWith('t', 'Invoice August');
|
||||
});
|
||||
|
||||
it('follows the subject of a reopened draft', () => {
|
||||
render(<ProComposeTabBody tabId="t" data={{ sessionId: 1, mode: 'compose', title: 'x', initialData: { subject: 'Lunch?' } as never }} />);
|
||||
it('follows the subject as it is typed, and falls back when it is emptied', () => {
|
||||
render(<ProComposeTabBody tabId="t" data={{ sessionId: 1, mode: 'compose', title: 'email_composer.new_message' }} />);
|
||||
composer.onSubjectChange!('Lunch?');
|
||||
expect(updateTabTitle).toHaveBeenLastCalledWith('t', 'Lunch?');
|
||||
composer.onSubjectChange!(' ');
|
||||
expect(updateTabTitle).toHaveBeenLastCalledWith('t', 'email_composer.new_message');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,21 +195,15 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) {
|
||||
|
||||
const handleSaveState = useCallback((state: ComposerDraftData) => {
|
||||
updateComposeDraft(tabIdRef.current, state);
|
||||
// Keep the tab title in sync with the working subject.
|
||||
const subject = state.subject?.trim() || t('email_composer.new_message');
|
||||
updateTabTitle(tabIdRef.current, subject);
|
||||
}, [updateComposeDraft, updateTabTitle, t]);
|
||||
}, [updateComposeDraft]);
|
||||
|
||||
// On first mount, ensure the tab title matches whatever subject we were
|
||||
// initialised with. The opener's title already says "Re: …" or "Fwd: …" for
|
||||
// a reply or forward. replyTo is no source for it: the mail tab fills that
|
||||
// in from the selected message for a new message too.
|
||||
useEffect(() => {
|
||||
const title = data.initialData?.subject?.trim() || data.title || t('email_composer.new_message');
|
||||
updateTabTitle(tabIdRef.current, title);
|
||||
// Run once on mount only.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
// The tab's title is the subject as it is typed, so several open compose
|
||||
// tabs can be told apart. replyTo is no source for it: the mail tab fills
|
||||
// that in from the selected message for a new message too.
|
||||
const newMessageTitle = t('email_composer.new_message');
|
||||
const handleSubjectChange = useCallback((subject: string) => {
|
||||
updateTabTitle(tabIdRef.current, subject.trim() || newMessageTitle);
|
||||
}, [updateTabTitle, newMessageTitle]);
|
||||
|
||||
// Mirrors the standard shell (see the same prop in `components/mail/mail-app.tsx`).
|
||||
// Without it `mode === 'compose'` resolves `findComposeIdentityId(identities,
|
||||
@@ -252,6 +246,7 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) {
|
||||
requestCloseRef={requestCloseRef}
|
||||
onDiscardDraft={handleDiscardDraft}
|
||||
onSaveState={handleSaveState}
|
||||
onSubjectChange={handleSubjectChange}
|
||||
className="flex-1"
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
|
||||
Reference in New Issue
Block a user