Merge pull request #1493 from ibayue/feat/signature-insert-toggles

This commit is contained in:
Linus Rath
2026-11-02 20:23:58 +01:00
47 changed files with 409 additions and 11 deletions
@@ -49,6 +49,8 @@ const settings = vi.hoisted(() => ({
emptySubjectWarningEnabled: false,
sendDelaySeconds: 0,
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: false,
requestReadReceiptDefault: false,
sendConfirmation: false,
@@ -95,6 +95,8 @@ vi.mock('@/stores/settings-store', () => {
// The default position: replies/forwards get the signature appended at
// send time rather than embedded above the quote.
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
requestReadReceiptDefault: false,
addTrustedSender: () => {},
@@ -95,6 +95,8 @@ vi.mock('@/stores/settings-store', () => {
// The default position: replies/forwards get the signature appended at
// send time rather than embedded above the quote.
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
requestReadReceiptDefault: false,
addTrustedSender: () => {},
@@ -95,6 +95,8 @@ vi.mock('@/stores/settings-store', () => {
// The default position: replies/forwards get the signature appended at
// send time rather than embedded above the quote.
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
requestReadReceiptDefault: false,
addTrustedSender: () => {},
@@ -257,3 +259,40 @@ describe('composer signature follows the picked identity', () => {
expect(sent.body).toBe('Plain draft');
});
});
describe('signature auto-insert toggles', () => {
afterEach(() => {
useSettingsStore.setState({
plainTextMode: false,
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
});
vi.clearAllMocks();
});
it('keeps a new message free of the signature when the new-mail toggle is off', async () => {
useSettingsStore.setState({ plainTextMode: true, signatureNewMailEnabled: false });
const onSend = vi.fn();
render(<EmailComposer initialData={{ ...DRAFT, body: 'Draft text' }} onSend={onSend} />);
// Not embedded into the editable body…
await waitFor(() => expect(textarea().value).toBe('Draft text'));
// …and not appended at send time either.
const sent = await send(onSend);
expect(sent.body).toBe('Draft text');
});
it('sends a reply without appending the signature when the reply toggle is off', async () => {
useSettingsStore.setState({ signatureReplyForwardEnabled: false });
const onSend = vi.fn();
render(<EmailComposer mode="reply" initialData={{ ...DRAFT, mode: 'reply' }} onSend={onSend} />);
// No read-only signature preview under the editor: it would promise a
// signature that never goes out.
expect(document.body.textContent).not.toContain('Alice');
const sent = await send(onSend);
expect(sent.htmlBody).not.toContain('Alice');
expect(sent.body).not.toContain('Alice');
});
});
@@ -136,6 +136,8 @@ vi.mock('@/stores/settings-store', () => {
// The default position: replies/forwards get the signature appended at
// send time rather than embedded above the quote.
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
requestReadReceiptDefault: false,
addTrustedSender: () => {},
@@ -96,6 +96,8 @@ vi.mock('@/stores/settings-store', () => {
// The default position: replies/forwards get the signature appended at
// send time rather than embedded above the quote.
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
requestReadReceiptDefault: false,
addTrustedSender: () => {},
@@ -123,6 +123,8 @@ vi.mock('@/stores/settings-store', () => {
// The default position: replies/forwards get the signature appended at
// send time rather than embedded above the quote.
signaturePosition: 'below_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
requestReadReceiptDefault: false,
addTrustedSender: () => {},
@@ -107,6 +107,8 @@ vi.mock('@/stores/settings-store', () => {
recipientMentionsEnabled: false,
sendDelaySeconds: 0,
signaturePosition: 'above_quote',
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: false,
requestReadReceiptDefault: false,
externalContentPolicy: 'block',
+27 -9
View File
@@ -547,6 +547,13 @@ export function EmailComposer({
const updateSetting = useSettingsStore((state) => state.updateSetting);
const sendDelaySeconds = useSettingsStore((state) => state.sendDelaySeconds);
const signaturePosition = useSettingsStore((state) => state.signaturePosition);
const signatureNewMailEnabled = useSettingsStore((state) => state.signatureNewMailEnabled);
const signatureReplyForwardEnabled = useSettingsStore((state) => state.signatureReplyForwardEnabled);
// Whether automatic signature insertion is on for this composer mode: new
// mail (and re-opened drafts, which open in compose mode) follow the
// new-mail toggle, replies and forwards the reply/forward one. What the
// body already carries is never stripped - only the insert/append is gated.
const autoSignatureEnabled = mode === 'compose' ? signatureNewMailEnabled : signatureReplyForwardEnabled;
const signatureSeparatorEnabled = useSettingsStore((state) => state.signatureSeparatorEnabled);
const requestReadReceiptDefault = useSettingsStore((state) => state.requestReadReceiptDefault);
// A draft belongs to the account the composer was opened in. Switching
@@ -622,12 +629,13 @@ export function EmailComposer({
const shouldEmbedSignatureAboveQuote =
(mode === 'reply' || mode === 'replyAll' || mode === 'forward') &&
signaturePosition === 'above_quote' &&
signatureReplyForwardEnabled &&
hasInitialSignature;
// New-mail composes always embed the signature into the editor body so it's
// editable/removable (the previous read-only preview below the editor was
// never spec-correct - see #329). Compose mode also ignores any leftover
// `replyTo` from a still-selected email; getInitialBody short-circuits below.
const shouldEmbedSignatureInNewMail = mode === 'compose' && hasInitialSignature;
const shouldEmbedSignatureInNewMail = mode === 'compose' && signatureNewMailEnabled && hasInitialSignature;
// Format a single EmailAddress for display in the composer input
const toRecipient = (r: { name?: string; email?: string }): Recipient =>
@@ -1320,6 +1328,9 @@ export function EmailComposer({
const isReplyLike = mode === 'reply' || mode === 'replyAll' || mode === 'forward';
if (!isReplyLike && mode !== 'compose') return;
if (isReplyLike && signaturePosition !== 'above_quote') return;
// With automatic insertion off the body never got a signature spliced
// in, so there is nothing to swap.
if (!autoSignatureEnabled) return;
if (plainTextMode) {
// A new plain-text message carries the signature at the end of the
@@ -1361,7 +1372,7 @@ export function EmailComposer({
// would re-run on unrelated identity-field changes and re-splice the
// signature into the live editor.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signatureIdentity?.id, signatureContent, signatureSeparatorEnabled, signaturePosition, mode, plainTextMode, pluginSignatureRevision]);
}, [signatureIdentity?.id, signatureContent, signatureSeparatorEnabled, signaturePosition, mode, plainTextMode, pluginSignatureRevision, autoSignatureEnabled]);
const openScheduleDialog = useCallback(() => {
setShowScheduleDialog(true);
@@ -2663,7 +2674,7 @@ export function EmailComposer({
// below the editor - was saved without it (#823). Embed the *marked-up*
// form so re-opening the draft round-trips the signature as one block and
// signatureAlreadyInBody sees it instead of appending a second copy.
const draftSignatureHtml = signatureAlreadyInBody
const draftSignatureHtml = signatureAlreadyInBody || !autoSignatureEnabled
? ''
: buildEmbeddedSignatureHtml(signatureIdentity, {
embed: true,
@@ -2671,7 +2682,7 @@ export function EmailComposer({
});
const draftHtmlBody = plainTextMode ? undefined : `${body}${draftSignatureHtml}`;
const draftTextBody = plainTextMode
? (signatureAlreadyInBody
? (signatureAlreadyInBody || !autoSignatureEnabled
? body
: appendPlainTextSignature(body, signatureIdentity, { separator: signatureSeparatorEnabled }))
: htmlToPlainText(draftHtmlBody!, imageAltText);
@@ -3608,9 +3619,14 @@ export function EmailComposer({
// above-quote replies, a re-opened draft) `signatureAlreadyInBody` is set
// and the trailing append below is skipped so we don't duplicate it.
// Whether automatic signature insertion is on for this composer mode
// (component-level `autoSignatureEnabled` - see signatureAlreadyInBody).
// What the body already carries is never stripped - only the append is
// gated.
// Build HTML signature block (used only in rich text mode)
const buildSignatureHtml = (): string => {
if (signatureAlreadyInBody) return '';
if (signatureAlreadyInBody || !autoSignatureEnabled) return '';
const sep = wantsSignatureSeparator(signatureSeparatorEnabled, signatureIdentity) ? `<br><br>-- <br>` : `<br><br>`;
if (signatureIdentity?.htmlSignature) {
return `${sep}${sanitizeSignatureHtml(signatureIdentity.htmlSignature)}`;
@@ -3627,8 +3643,8 @@ export function EmailComposer({
// In plain text mode, send text/plain only (no HTML body)
const signatureOpts = { separator: signatureSeparatorEnabled };
const builtTextBody = plainTextMode
? (signatureAlreadyInBody ? body : appendPlainTextSignature(body, signatureIdentity, signatureOpts))
: (signatureAlreadyInBody ? htmlToPlainText(body, imageAltText) : appendPlainTextSignature(htmlToPlainText(body, imageAltText), signatureIdentity, signatureOpts));
? (signatureAlreadyInBody || !autoSignatureEnabled ? body : appendPlainTextSignature(body, signatureIdentity, signatureOpts))
: (signatureAlreadyInBody || !autoSignatureEnabled ? htmlToPlainText(body, imageAltText) : appendPlainTextSignature(htmlToPlainText(body, imageAltText), signatureIdentity, signatureOpts));
// Before any blobId is read: forwarded parts may still sit on the account
// that holds the original message.
@@ -4780,8 +4796,10 @@ export function EmailComposer({
{/* Hide the visual signature preview when the signature has already been
embedded into the body (compose, above-quote replies, re-opened
drafts) - it would otherwise read as a second signature. */}
{signatureAlreadyInBody ? null
drafts) - it would otherwise read as a second signature. Also when
automatic insertion is off: nothing will be appended at send time,
so a preview would promise a signature that never goes out. */}
{signatureAlreadyInBody || !autoSignatureEnabled ? null
: plainTextMode ? (
getPlainTextSignature(signatureIdentity) ? (
<div className="px-4 pb-3 text-sm leading-6 text-muted-foreground break-words whitespace-pre-wrap font-mono">
+2
View File
@@ -4140,6 +4140,8 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
signatureIdentity,
sendingIdentity,
separator: settings.signatureSeparatorEnabled,
// The user's reply/forward signature toggle: off sends the bare text.
insertSignature: settings.signatureReplyForwardEnabled,
to: recipients,
fromEmail: headerFromEmail,
identityId: sendingIdentity?.id,
@@ -36,6 +36,8 @@ export function ComposingSettings() {
mentionAddsRecipientTo,
sendDelaySeconds,
signaturePosition,
signatureNewMailEnabled,
signatureReplyForwardEnabled,
signatureSeparatorEnabled,
requestReadReceiptDefault,
readReceiptResponse,
@@ -124,6 +126,20 @@ export function ComposingSettings() {
/>
</SettingItem>
<SettingItem label={t('signature_new_mail.label')} description={t('signature_new_mail.description')}>
<ToggleSwitch
checked={signatureNewMailEnabled}
onChange={(checked) => updateSetting('signatureNewMailEnabled', checked)}
/>
</SettingItem>
<SettingItem label={t('signature_reply_forward.label')} description={t('signature_reply_forward.description')}>
<ToggleSwitch
checked={signatureReplyForwardEnabled}
onChange={(checked) => updateSetting('signatureReplyForwardEnabled', checked)}
/>
</SettingItem>
<SettingItem advanced label={t('signature_separator.label')} description={t('signature_separator.description')}>
<ToggleSwitch
checked={signatureSeparatorEnabled}
+6
View File
@@ -77,6 +77,12 @@ describe('settings search index (English messages)', () => {
expect(resultsFor('composing', 'reply signature').map((r) => r.label)).toContain('Signature position');
});
it('finds the newer signature settings on their tabs', () => {
expect(labelsFor('composing')).toContain('Signature on new mail');
expect(labelsFor('composing')).toContain('Signature on replies & forwards');
expect(labelsFor('composing')).toContain('Undo send / send delay');
});
it('marks the rows the Advanced switch hides', () => {
const advanced = settingsSubResults(en, 'message_list', 'Bulwark').filter((r) => r.advanced).map((r) => r.label);
expect(advanced).toContain('Emails per page');
@@ -33,6 +33,14 @@ describe('prepareQuickReply', () => {
expect(plain?.text).toMatch(/Thanks <3[\s\S]*-- \nMe$/);
});
it('sends the bare text when the reply signature toggle is off', async () => {
const withHtml = await prepareQuickReply({ ...input(html), insertSignature: false });
expect(withHtml?.html).toBeUndefined();
expect(withHtml?.text).toBe('Thanks <3');
const plain = await prepareQuickReply({ ...input(text), insertSignature: false });
expect(plain?.text).toBe('Thanks <3');
});
it('runs the finalisers with a reply context', async () => {
const contexts: unknown[] = [];
registerOutgoingFinalizer('f', 10, (bodies, context) => {
+12 -2
View File
@@ -35,12 +35,19 @@ export interface QuickReplyQuestions {
* (onBeforeEmailSend) run on it too, after the organisation signature and
* before the disclaimer, as in the composer; `onSendCheckStopped` hears
* why one stopped it.
*
* `insertSignature` is the user's reply/forward signature toggle: false
* sends the bare typed text, with no signature of the user's own. The
* organisation signature and disclaimer still apply - they are policy,
* not the user's choice.
*/
export async function prepareQuickReply(input: {
body: string;
signatureIdentity: Identity | null | undefined;
sendingIdentity?: Identity | null;
separator: boolean;
/** The user's signature auto-insert toggle for replies/forwards. */
insertSignature?: boolean;
to: string[];
fromEmail?: string;
identityId?: string;
@@ -66,7 +73,10 @@ export async function prepareQuickReply(input: {
}
const signatureIdentity = currentSignatureIdentity(input.signatureIdentity, input.sendingIdentity ?? undefined);
const text = appendPlainTextSignature(body, signatureIdentity, { separator });
const insertSignature = input.insertSignature !== false;
const text = insertSignature
? appendPlainTextSignature(body, signatureIdentity, { separator })
: body;
// When the identity has an HTML signature, send a matching HTML body so the
// signature keeps its formatting; appendPlainTextSignature would otherwise
@@ -77,7 +87,7 @@ export async function prepareQuickReply(input: {
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
const html = signatureIdentity?.htmlSignature?.trim()
const html = insertSignature && signatureIdentity?.htmlSignature?.trim()
? appendHtmlSignature(`<div>${escapedBody}</div>`, signatureIdentity, { separator })
: undefined;
+2
View File
@@ -247,6 +247,8 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
{ label: 'settings.email_behavior.quick_reply.label' },
{ label: 'settings.email_behavior.auto_select_reply_identity.label' },
{ label: 'settings.email_behavior.signature_position.label' },
{ label: 'settings.email_behavior.signature_new_mail.label' },
{ label: 'settings.email_behavior.signature_reply_forward.label' },
{ label: 'settings.email_behavior.signature_separator.label', advanced: true },
{ label: 'settings.composing_page.sections.sending' },
{ label: 'settings.email_behavior.send_delay.label' },
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "قبل النص المقتبس",
"below_quote": "بعد النص المقتبس"
},
"signature_new_mail": {
"label": "التوقيع في الرسائل الجديدة",
"description": "يُدرج توقيعك تلقائيًا عند كتابة رسالة جديدة. عطّله لبدء كل رسالة جديدة دون توقيع."
},
"signature_reply_forward": {
"label": "التوقيع في الردود وإعادة التوجيه",
"description": "يُرفق توقيعك تلقائيًا عند الرد على رسالة أو إعادة توجيهها. عطّله لإرسالها دون توقيع."
},
"signature_separator": {
"label": "فاصل التوقيع",
"description": "إضافة السطر الفاصل القياسي \"-- \" قبل التوقيع (RFC 3676). عطّله إذا كنت تفضّل الانتقال مباشرة من رسالتك إلى التوقيع."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Abans del text citat",
"below_quote": "Després del text citat"
},
"signature_new_mail": {
"label": "Signatura als correus nous",
"description": "Insereix automàticament la teva signatura en redactar un missatge nou. Desactiva-ho per començar cada missatge nou sense ella."
},
"signature_reply_forward": {
"label": "Signatura en respostes i reenviaments",
"description": "Afegeix automàticament la teva signatura en respondre o reenviar un missatge. Desactiva-ho per enviar-los sense ella."
},
"signature_separator": {
"label": "Delimitador de la signatura",
"description": "Prefixa la signatura amb la línia delimitadora estàndard «-- » (RFC 3676). Desactiveu-ho si preferiu passar directament del missatge a la signatura."
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "Před citovaným textem",
"below_quote": "Za citovaným textem"
},
"signature_new_mail": {
"label": "Podpis u nových zpráv",
"description": "Při psaní nové zprávy automaticky vloží váš podpis. Vypněte, pokud chcete každou novou zprávu začínat bez podpisu."
},
"signature_reply_forward": {
"label": "Podpis u odpovědí a přeposílání",
"description": "Při odpovídání nebo přeposílání automaticky připojí váš podpis. Vypněte, pokud je chcete odesílat bez podpisu."
},
"signature_separator": {
"label": "Oddělovač podpisu",
"description": "Před podpis přidat standardní oddělovací řádek \"-- \" (RFC 3676). Vypněte, pokud chcete plynule přejít z textu zprávy do podpisu."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Før citeret tekst",
"below_quote": "Efter citeret tekst"
},
"signature_new_mail": {
"label": "Signatur på nye e-mails",
"description": "Indsætter automatisk din signatur, når du skriver en ny besked. Slå fra for at starte hver ny besked uden."
},
"signature_reply_forward": {
"label": "Signatur på svar og videresendelser",
"description": "Tilføjer automatisk din signatur, når du svarer eller videresender. Slå fra for at sende dem uden."
},
"signature_separator": {
"label": "Signatur-afgrænser",
"description": "Præfiksér signaturen med standard \"-- \"-afgrænserlinjen (RFC 3676). Slå fra, hvis du hellere vil gå direkte fra din besked til signaturen."
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "Vor zitiertem Text",
"below_quote": "Nach zitiertem Text"
},
"signature_new_mail": {
"label": "Signatur bei neuen E-Mails",
"description": "Fügt die Signatur beim Verfassen einer neuen Nachricht automatisch ein. Ausschalten, um jede neue Nachricht ohne Signatur zu beginnen."
},
"signature_reply_forward": {
"label": "Signatur bei Antworten und Weiterleitungen",
"description": "Hängt die Signatur beim Antworten oder Weiterleiten automatisch an. Ausschalten, um sie ohne Signatur zu senden."
},
"signature_separator": {
"label": "Signatur-Trenner",
"description": "Der Signatur die Standard-Trennerzeile \"-- \" voranstellen (RFC 3676). Deaktivieren, wenn der Nachrichtentext direkt in die Signatur übergehen soll."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Before quoted text",
"below_quote": "After quoted text"
},
"signature_new_mail": {
"label": "Signature on new mail",
"description": "Automatically insert your signature when composing a new message. Turn off to start every new message without one."
},
"signature_reply_forward": {
"label": "Signature on replies & forwards",
"description": "Automatically append your signature when replying to or forwarding a message. Turn off to send them without one."
},
"signature_separator": {
"label": "Signature delimiter",
"description": "Prefix the signature with the standard \"-- \" delimiter line (RFC 3676). Turn off if you'd rather flow straight from your message into the signature."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Antes del texto citado",
"below_quote": "Después del texto citado"
},
"signature_new_mail": {
"label": "Firma en correos nuevos",
"description": "Inserta automáticamente tu firma al redactar un mensaje nuevo. Desactívalo para empezar cada mensaje nuevo sin ella."
},
"signature_reply_forward": {
"label": "Firma en respuestas y reenvíos",
"description": "Añade automáticamente tu firma al responder o reenviar un mensaje. Desactívalo para enviarlos sin ella."
},
"signature_separator": {
"label": "Delimitador de firma",
"description": "Anteponer a la firma la línea delimitadora estándar \"-- \" (RFC 3676). Desactiva si prefieres pasar directamente del mensaje a la firma."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Enne tsiteeritud teksti",
"below_quote": "Pärast tsiteeritud teksti"
},
"signature_new_mail": {
"label": "Allkirja uutes kirjades",
"description": "Sisestab allkirja automaatselt uue kirja koostamisel. Lülita välja, et iga uus kiri algaks ilma allkirjata."
},
"signature_reply_forward": {
"label": "Allkirja vastamisel ja edastamisel",
"description": "Lisab allkirja automaatselt vastamisel või kirja edastamisel. Lülita välja, et saata need ilma allkirjata."
},
"signature_separator": {
"label": "Allkirja eraldaja",
"description": "Lisa allkirja ette standardne „-- “ eraldusrida (RFC 3676). Lülita välja, kui eelistad, et kiri sulandub otse allkirjaga."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "قبل از نقل قول",
"below_quote": "بعد از نقل قول"
},
"signature_new_mail": {
"label": "امضا در نامه‌های جدید",
"description": "هنگام نوشتن نامه جدید، امضای شما به‌طور خودکار درج می‌شود. برای شروع هر نامه جدید بدون امضا، خاموش کنید."
},
"signature_reply_forward": {
"label": "امضا در پاسخ و ارجاع",
"description": "هنگام پاسخ دادن یا ارجاع دادن نامه، امضای شما به‌طور خودکار افزوده می‌شود. برای ارسال بدون امضا، خاموش کنید."
},
"signature_separator": {
"label": "جداکننده امضا",
"description": "پیشوند \"-- \""
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Avant le texte cité",
"below_quote": "Après le texte cité"
},
"signature_new_mail": {
"label": "Signature sur les nouveaux messages",
"description": "Insère automatiquement votre signature lors de la rédaction d'un nouveau message. Désactivez pour rédiger sans signature."
},
"signature_reply_forward": {
"label": "Signature sur les réponses et transferts",
"description": "Ajoute automatiquement votre signature lors d'une réponse ou d'un transfert. Désactivez pour les envoyer sans signature."
},
"signature_separator": {
"label": "Délimiteur de signature",
"description": "Préfixer la signature par la ligne de délimitation standard \"-- \" (RFC 3676). Désactivez si vous préférez enchaîner directement du message à la signature."
+8
View File
@@ -1996,6 +1996,14 @@
"above_quote": "לפני טקסט מצוטט",
"below_quote": "אחרי טקסט מצוטט"
},
"signature_new_mail": {
"label": "חתימה בהודעות חדשות",
"description": "מוסיף אוטומטית את החתימה שלך בעת כתיבת הודעה חדשה. כבה כדי להתחיל כל הודעה חדשה בלי חתימה."
},
"signature_reply_forward": {
"label": "חתימה בתשובות והעברות",
"description": "מצרף אוטומטית את החתימה שלך בעת מענה או העברה של הודעה. כבה כדי לשלוח אותן בלי חתימה."
},
"signature_separator": {
"label": "מפריד חתימה",
"description": "הוסף קידומת לחתימה בעדכון סטנדרטי ״-- ״ (RFC 3676). כבה אם אתה מעדיף לזרום ישירות מההודעה שלך לחתימה."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Idézett szöveg előtt",
"below_quote": "Idézett szöveg után"
},
"signature_new_mail": {
"label": "Aláírás új leveleknél",
"description": "Új levél írása esetén automatikusan beszúrja az aláírásodat. Kapcsold ki, ha minden új levél aláírás nélkül induljon."
},
"signature_reply_forward": {
"label": "Aláírás válaszoknál és továbbításnál",
"description": "Válasz vagy továbbítás esetén automatikusan hozzáfűzi az aláírásodat. Kapcsold ki, ha ezek aláírás nélkül mennek ki."
},
"signature_separator": {
"label": "Aláírás elválasztó",
"description": "Aláírás előtagjaként a szabványos \"-- \" elválasztó sor (RFC 3676). Kapcsold ki, ha közvetlenül az üzenetből szeretnél az aláírásba folyni."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Prima del testo citato",
"below_quote": "Dopo il testo citato"
},
"signature_new_mail": {
"label": "Firma sui nuovi messaggi",
"description": "Inserisce automaticamente la firma quando componi un nuovo messaggio. Disattiva per iniziare ogni nuovo messaggio senza."
},
"signature_reply_forward": {
"label": "Firma su risposte e inoltri",
"description": "Aggiunge automaticamente la firma quando rispondi o inoltri un messaggio. Disattiva per inviarli senza."
},
"signature_separator": {
"label": "Delimitatore firma",
"description": "Anteporre alla firma la riga di delimitazione standard \"-- \" (RFC 3676). Disattiva se preferisci passare direttamente dal messaggio alla firma."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "引用テキストの前",
"below_quote": "引用テキストの後"
},
"signature_new_mail": {
"label": "新規メールの署名",
"description": "新しいメールを作成するときに署名を自動的に挿入します。オフにすると署名なしで作成を始められます。"
},
"signature_reply_forward": {
"label": "返信・転送の署名",
"description": "返信や転送のときに署名を自動的に追加します。オフにすると署名なしで送信されます。"
},
"signature_separator": {
"label": "署名区切り",
"description": "署名の前に標準の区切り行「-- 」(RFC 3676)を付けます。本文から署名へ直接続けたい場合はオフにしてください。"
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "인용 텍스트 앞",
"below_quote": "인용 텍스트 뒤"
},
"signature_new_mail": {
"label": "새 메일 서명",
"description": "새 메일을 작성할 때 서명을 자동으로 삽입합니다. 끄면 서명 없이 새 메일을 시작합니다."
},
"signature_reply_forward": {
"label": "답장·전달 서명",
"description": "답장이나 전달할 때 서명을 자동으로 추가합니다. 끄면 서명 없이 보냅니다."
},
"signature_separator": {
"label": "서명 구분선",
"description": "서명 앞에 표준 구분선 \"-- \" (RFC 3676)을 추가합니다. 본문에서 바로 서명으로 이어지길 원하면 해제하세요."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Pirms citētā teksta",
"below_quote": "Pēc citētā teksta"
},
"signature_new_mail": {
"label": "Paraksts jaunajos e-pastos",
"description": "Rakstot jaunu vēstuli, automātiski ievieto jūsu parakstu. Izslēdziet, lai katra jauna vēstule sāktos bez tā."
},
"signature_reply_forward": {
"label": "Paraksts atbildēs un pārsūtījumos",
"description": "Atbildot vai pārsūtot vēstuli, automātiski pievieno jūsu parakstu. Izslēdziet, lai tās tiktu nosūtītas bez tā."
},
"signature_separator": {
"label": "Paraksta atdalītājs",
"description": "Pirms paraksta pievienot standarta atdalītāju \"-- \" (RFC 3676). Izslēdziet, ja vēlaties pāriet no ziņojuma tieši uz parakstu."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Ишлэлээс өмнө",
"below_quote": "Иш иш татсаны дараа"
},
"signature_new_mail": {
"label": "Шинэ захидалд гарын үсэг",
"description": "Шинэ захидал бичих үед гарын үсгээ автоматаар оруулна. Шинэ захидал бүрийг гарын үсэггүй эхлүүлэх бол унтраана уу."
},
"signature_reply_forward": {
"label": "Хариу, дамжуулахад гарын үсэг",
"description": "Захидалд хариу өгөх эсвэл дамжуулах үед гарын үсгээ автоматаар хавсаргана. Гарын үсэггүй илгээхийн тулд унтраана уу."
},
"signature_separator": {
"label": "Гарын үсэг хязгаарлагч",
"description": "Гарын үсгийн угтварыг стандарт \"--\" хязгаарлагч шугамаар (RFC 3676) оруулна. Хэрэв та мессежээсээ шууд гарын үсэг рүү шилжихийг хүсвэл унтраа."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Før sitert tekst",
"below_quote": "Etter sitert tekst"
},
"signature_new_mail": {
"label": "Signatur på nye e-poster",
"description": "Setter automatisk inn signaturen din når du skriver en ny melding. Slå av for å starte hver ny melding uten."
},
"signature_reply_forward": {
"label": "Signatur på svar og videresendinger",
"description": "Legger automatisk til signaturen din når du svarer eller videresender. Slå av for å sende dem uten."
},
"signature_separator": {
"label": "Signaturavgrensning",
"description": "Sett standardavgrensningen «-- » på en egen linje foran signaturen (RFC 3676). Slå av hvis meldingen skal gå rett over i signaturen."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Voor geciteerde tekst",
"below_quote": "Na geciteerde tekst"
},
"signature_new_mail": {
"label": "Handtekening bij nieuwe berichten",
"description": "Voegt je handtekening automatisch toe bij het opstellen van een nieuw bericht. Zet uit om elk nieuw bericht zonder te beginnen."
},
"signature_reply_forward": {
"label": "Handtekening bij antwoorden en doorsturen",
"description": "Voegt je handtekening automatisch toe bij antwoorden of doorsturen. Zet uit om ze zonder te versturen."
},
"signature_separator": {
"label": "Handtekening-scheider",
"description": "De handtekening voorafgaan met de standaard scheidingsregel \"-- \" (RFC 3676). Zet uit als je liever direct van het bericht in de handtekening overgaat."
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "Przed cytowanym tekstem",
"below_quote": "Po cytowanym tekście"
},
"signature_new_mail": {
"label": "Podpis w nowych wiadomościach",
"description": "Automatycznie wstawia podpis podczas pisania nowej wiadomości. Wyłącz, aby zaczynać każdą nową wiadomość bez niego."
},
"signature_reply_forward": {
"label": "Podpis w odpowiedziach i przesyłanych",
"description": "Automatycznie dołącza podpis przy odpowiadaniu lub przesyłaniu dalej. Wyłącz, aby wysyłać je bez niego."
},
"signature_separator": {
"label": "Separator podpisu",
"description": "Poprzedź podpis standardową linią separatora \"-- \" (RFC 3676). Wyłącz, jeśli chcesz przejść bezpośrednio z treści wiadomości do podpisu."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Antes do texto citado",
"below_quote": "Depois do texto citado"
},
"signature_new_mail": {
"label": "Assinatura em novas mensagens",
"description": "Insere automaticamente a sua assinatura ao escrever uma nova mensagem. Desative para começar cada nova mensagem sem ela."
},
"signature_reply_forward": {
"label": "Assinatura em respostas e encaminhamentos",
"description": "Acrescenta automaticamente a sua assinatura ao responder ou encaminhar uma mensagem. Desative para as enviar sem ela."
},
"signature_separator": {
"label": "Delimitador de assinatura",
"description": "Anteceder a assinatura com a linha delimitadora padrão \"-- \" (RFC 3676). Desative se preferir passar direto da mensagem para a assinatura."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Înaintea textului citat",
"below_quote": "După textul între ghilimele"
},
"signature_new_mail": {
"label": "Semnătură la mesajele noi",
"description": "Inserează automat semnătura când compui un mesaj nou. Dezactivează pentru a începe fiecare mesaj nou fără ea."
},
"signature_reply_forward": {
"label": "Semnătură la răspunsuri și redirecționări",
"description": "Adaugă automat semnătura când răspunzi sau redirecționezi un mesaj. Dezactivează pentru a le trimite fără ea."
},
"signature_separator": {
"label": "Delimitator de semnătură",
"description": "Prefațați semnătura cu linia standard de delimitare „-- ” (RFC 3676). Dezactivați această opțiune dacă preferați ca mesajul să treacă direct în semnătură."
+8
View File
@@ -1940,6 +1940,14 @@
"above_quote": "Перед цитируемым текстом",
"below_quote": "После цитируемого текста"
},
"signature_new_mail": {
"label": "Подпись в новых письмах",
"description": "Автоматически вставляет подпись при написании нового письма. Отключите, чтобы начинать каждое новое письмо без неё."
},
"signature_reply_forward": {
"label": "Подпись в ответах и пересылках",
"description": "Автоматически добавляет подпись при ответе или пересылке письма. Отключите, чтобы отправлять их без неё."
},
"signature_separator": {
"label": "Разделитель подписи",
"description": "Добавлять перед подписью стандартную строку-разделитель \"-- \" (RFC 3676). Отключите, если хотите переходить от текста сразу к подписи."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "Pred citovaným textom",
"below_quote": "Za citovaným textom"
},
"signature_new_mail": {
"label": "Podpis pri nových správach",
"description": "Pri písaní novej správy automaticky vloží váš podpis. Vypnite, ak chcete každú novú správu začať bez podpisu."
},
"signature_reply_forward": {
"label": "Podpis pri odpovediach a preposielaní",
"description": "Pri odpovedaní alebo preposielaní automaticky priloží váš podpis. Vypnite, ak ich chcete odosielať bez podpisu."
},
"signature_separator": {
"label": "Oddeľovač podpisu",
"description": "Pred podpis pridať štandardný oddeľovací riadok \"-- \" (RFC 3676)."
+8
View File
@@ -4621,6 +4621,14 @@
"above_quote": "ก่อนข้อความที่อ้างอิง",
"below_quote": "หลังข้อความที่อ้างอิง"
},
"signature_new_mail": {
"label": "ลายเซ็นในอีเมลใหม่",
"description": "แทรกลายเซ็นของคุณโดยอัตโนมัติเมื่อเขียนอีเมลใหม่ ปิดใช้งานหากต้องการเริ่มอีเมลใหม่ทุกฉบับโดยไม่มีลายเซ็น"
},
"signature_reply_forward": {
"label": "ลายเซ็นในการตอบกลับและส่งต่อ",
"description": "แนบลายเซ็นของคุณโดยอัตโนมัติเมื่อตอบกลับหรือส่งต่ออีเมล ปิดใช้งานหากต้องการส่งโดยไม่มีลายเซ็น"
},
"signature_separator": {
"label": "เส้นคั่นลายเซ็น",
"description": "ใส่เส้นคั่นมาตรฐาน \"-- \" นำหน้าลายเซ็น (RFC 3676) ปิดใช้งานหากคุณต้องการให้ข้อความของคุณไหลต่อเนื่องเข้าสู่ลายเซ็นทันที"
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "Alıntılanan metinden önce",
"below_quote": "Alıntılanan metinden sonra"
},
"signature_new_mail": {
"label": "Yeni e-postalarda imza",
"description": "Yeni bir ileti yazarken imzanızı otomatik olarak ekler. Her yeni iletiye imzasız başlamak için kapatın."
},
"signature_reply_forward": {
"label": "Yanıtlarda ve iletmelerde imza",
"description": "Bir iletiyi yanıtlarken veya iletirken imzanızı otomatik olarak ekler. İmzasız göndermek için kapatın."
},
"signature_separator": {
"label": "İmza ayırıcı",
"description": "İmzayı standart \"-- \" ayırıcı satırıyla başlat (RFC 3676). Mesajdan doğrudan imzaya geçmek istiyorsanız kapatın."
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "Перед цитованим текстом",
"below_quote": "Після цитованого тексту"
},
"signature_new_mail": {
"label": "Підпис у нових листах",
"description": "Автоматично вставляє підпис під час написання нового листа. Вимкніть, щоб кожен новий лист починався без нього."
},
"signature_reply_forward": {
"label": "Підпис у відповідях і пересиланнях",
"description": "Автоматично додає підпис під час відповіді або пересилання листа. Вимкніть, щоб надсилати їх без нього."
},
"signature_separator": {
"label": "Розділювач підпису",
"description": "Додавати перед підписом стандартний рядок-розділювач \"-- \" (RFC 3676). Вимкніть, якщо хочете переходити з повідомлення відразу до підпису."
+8
View File
@@ -1964,6 +1964,14 @@
"above_quote": "Trước phần trích dẫn",
"below_quote": "Sau phần trích dẫn"
},
"signature_new_mail": {
"label": "Chữ ký trong thư mới",
"description": "Tự động chèn chữ ký của bạn khi soạn thư mới. Tắt nếu bạn muốn mỗi thư mới bắt đầu không có chữ ký."
},
"signature_reply_forward": {
"label": "Chữ ký khi trả lời và chuyển tiếp",
"description": "Tự động thêm chữ ký của bạn khi trả lời hoặc chuyển tiếp thư. Tắt nếu muốn gửi chúng không kèm chữ ký."
},
"signature_separator": {
"label": "Dấu phân cách chữ ký",
"description": "Thêm dòng phân cách chuẩn \"-- \" trước chữ ký (RFC 3676). Tắt nếu bạn muốn nối thẳng nội dung thư với chữ ký."
+8
View File
@@ -1952,6 +1952,14 @@
"above_quote": "引用文字之前",
"below_quote": "引用文字之後"
},
"signature_new_mail": {
"label": "新郵件簽名",
"description": "撰寫新郵件時自動插入簽名。關閉後每封新郵件都不帶簽名。"
},
"signature_reply_forward": {
"label": "回覆轉寄簽名",
"description": "回覆或轉寄時自動附加簽名。關閉後回覆和轉寄不帶簽名。"
},
"signature_separator": {
"label": "簽名檔分隔線",
"description": "在簽名檔前加入標準的「-- 」分隔線(RFC 3676)。若希望內文直接接續簽名檔,請停用此選項。"
+8
View File
@@ -1949,6 +1949,14 @@
"above_quote": "引用文本之前",
"below_quote": "引用文本之后"
},
"signature_new_mail": {
"label": "新邮件签名",
"description": "写新邮件时自动插入签名。关闭后每封新邮件都不带签名。"
},
"signature_reply_forward": {
"label": "回复转发签名",
"description": "回复或转发时自动附加签名。关闭后回复和转发不带签名。"
},
"signature_separator": {
"label": "签名分隔符",
"description": "在签名前加入标准的 “-- ” 分隔行(RFC 3676)。如果希望正文直接连到签名,请关闭。"
@@ -52,6 +52,41 @@ describe('settings sync debounce', () => {
});
});
it('defaults the signature auto-insert toggles and the separator to on and exports them', () => {
const settings = useSettingsStore.getState();
expect(settings.signatureNewMailEnabled).toBe(true);
expect(settings.signatureReplyForwardEnabled).toBe(true);
expect(settings.signatureSeparatorEnabled).toBe(true);
const exported = JSON.parse(settings.exportSettings()) as Record<string, unknown>;
expect(exported).toMatchObject({
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
});
});
it('keeps inserting the signature for users whose saved settings predate the toggles', async () => {
useSettingsStore.getState().resetToDefaults();
// Saved by a build without the toggles: the keys are missing.
localStorage.setItem('settings-storage', JSON.stringify({
state: { signaturePosition: 'below_quote', signatureSeparatorEnabled: true },
version: 12,
}));
await useSettingsStore.persist.rehydrate();
expect(useSettingsStore.getState().signatureNewMailEnabled).toBe(true);
expect(useSettingsStore.getState().signatureReplyForwardEnabled).toBe(true);
// A server copy written by such a build has no toggles either.
useSettingsStore.getState().importSettings(
JSON.stringify({ signaturePosition: 'above_quote' }),
{ serverAccountId: 'a@example.com' },
);
expect(useSettingsStore.getState().signatureNewMailEnabled).toBe(true);
expect(useSettingsStore.getState().signatureReplyForwardEnabled).toBe(true);
localStorage.removeItem('settings-storage');
});
it('does not rewrite a pending account snapshot after another account becomes active', async () => {
const settings = useSettingsStore.getState();
settings.enableSync('a@example.com', 'https://mail-a.example.com');
+8
View File
@@ -591,6 +591,8 @@ interface SettingsState extends AccessibilitySettings {
subAddressDelimiter: string; // Character separating user from tag (e.g. "user+tag@")
sendDelaySeconds: SendDelaySeconds;
signaturePosition: SignaturePosition; // Position of the signature relative to quoted text in replies/forwards
signatureNewMailEnabled: boolean; // Insert the signature automatically in new messages
signatureReplyForwardEnabled: boolean; // Insert the signature automatically in replies and forwards
signatureSeparatorEnabled: boolean; // Prefix the signature with the RFC 3676 "-- " delimiter
signatureDetails: SignatureDetails; // Title, department, phone, mobile for the organisation signature (lib/org-signature.ts)
requestReadReceiptDefault: boolean; // Pre-check "request read receipt" in the composer
@@ -931,6 +933,10 @@ const DEFAULT_SETTINGS = {
subAddressDelimiter: DEFAULT_SUB_ADDRESS_DELIMITER,
sendDelaySeconds: 0 as SendDelaySeconds,
signaturePosition: 'below_quote' as SignaturePosition,
// On, as before the toggles existed: saved settings and server copies
// from older builds lack these keys and fall back to this default.
signatureNewMailEnabled: true,
signatureReplyForwardEnabled: true,
signatureSeparatorEnabled: true,
signatureDetails: EMPTY_SIGNATURE_DETAILS,
requestReadReceiptDefault: false,
@@ -1365,6 +1371,8 @@ export const useSettingsStore = create<SettingsState>()(
subAddressDelimiter: state.subAddressDelimiter,
sendDelaySeconds: state.sendDelaySeconds,
signaturePosition: state.signaturePosition,
signatureNewMailEnabled: state.signatureNewMailEnabled,
signatureReplyForwardEnabled: state.signatureReplyForwardEnabled,
signatureSeparatorEnabled: state.signatureSeparatorEnabled,
signatureDetails: state.signatureDetails,
requestReadReceiptDefault: state.requestReadReceiptDefault,