mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-30 16:16:19 +00:00
fix: show one signature separator and keep its trailing space
This commit is contained in:
@@ -83,4 +83,10 @@ describe('compose-format (#1022): HTML <-> plain text switch', () => {
|
||||
const back = plainComposeBodyToHtml(text, identity, { separator: true });
|
||||
expect(back).toBe(start);
|
||||
});
|
||||
|
||||
it('adds no separator paragraph in front of a signature that has its own delimiter', () => {
|
||||
const html = buildEmbeddedSignatureHtml({ textSignature: '-- \nUser A' }, { embed: true, separator: true });
|
||||
expect(html).not.toContain(`${SIGNATURE_RANGE_MARKER}="separator"`);
|
||||
expect(count(html, '-- ')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
plainTextBodyHasSignature,
|
||||
plainTextBodyWithoutSignature,
|
||||
} from "@/lib/signature-utils";
|
||||
import { wantsSignatureSeparator } from "@/lib/signature-delimiter";
|
||||
import { buildSignatureBlock, SIGNATURE_RANGE_MARKER } from "@/components/email/signature-block";
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,7 @@ export function buildEmbeddedSignatureHtml(
|
||||
options: { embed: boolean; separator: boolean }
|
||||
): string {
|
||||
if (!options.embed) return '';
|
||||
const startMarker = options.separator
|
||||
const startMarker = wantsSignatureSeparator(options.separator, identity)
|
||||
? `<p ${SIGNATURE_RANGE_MARKER}="separator">-- </p>`
|
||||
: `<p ${SIGNATURE_RANGE_MARKER}="start"></p>`;
|
||||
const endMarker = `<p ${SIGNATURE_RANGE_MARKER}="end"></p>`;
|
||||
|
||||
@@ -47,6 +47,7 @@ import { TemplatePicker } from "@/components/templates/template-picker";
|
||||
import { TemplateForm } from "@/components/templates/template-form";
|
||||
import type { EmailTemplate } from "@/lib/template-types";
|
||||
import { appendPlainTextSignature, getPlainTextSignature, plainTextBodyHasSignature, plainTextBodyWithoutSignature } from "@/lib/signature-utils";
|
||||
import { wantsSignatureSeparator } from "@/lib/signature-delimiter";
|
||||
import { findComposeIdentityId, findDraftIdentityId, findReplyIdentityId, resolveReplyFrom } from "@/lib/reply-identity";
|
||||
import { buildReplyRecipients, isSelfSent } from "@/lib/reply-recipients";
|
||||
import { computeReplyThreadingHeaders, type ReplyThreadingHeaders } from "@/lib/email-threading";
|
||||
@@ -438,7 +439,7 @@ export function EmailComposer({
|
||||
// editable. Fixes #329 (A,B).
|
||||
if (mode === 'compose') {
|
||||
if (!shouldEmbedSignatureInNewMail) return prefix;
|
||||
const sep = signatureSeparatorEnabled ? '\n\n-- \n' : '\n\n';
|
||||
const sep = wantsSignatureSeparator(signatureSeparatorEnabled, initialSignatureIdentity) ? '\n\n-- \n' : '\n\n';
|
||||
return `${prefix}${sep}${getPlainTextSignature(initialSignatureIdentity)}`;
|
||||
}
|
||||
if (!replyTo?.body && !replyTo?.htmlBody) return prefix;
|
||||
@@ -460,7 +461,7 @@ export function EmailComposer({
|
||||
// drafting area and the quoted content so it reads naturally as a
|
||||
// closing for the reply body. Send-time append is skipped - see
|
||||
// shouldEmbedSignatureAboveQuote.
|
||||
const plainSep = signatureSeparatorEnabled ? '\n\n-- \n' : '\n\n';
|
||||
const plainSep = wantsSignatureSeparator(signatureSeparatorEnabled, initialSignatureIdentity) ? '\n\n-- \n' : '\n\n';
|
||||
const signatureBlock = shouldEmbedSignatureAboveQuote
|
||||
? `${plainSep}${getPlainTextSignature(initialSignatureIdentity)}`
|
||||
: '';
|
||||
@@ -2481,7 +2482,7 @@ export function EmailComposer({
|
||||
// Build HTML signature block (used only in rich text mode)
|
||||
const buildSignatureHtml = (): string => {
|
||||
if (signatureAlreadyInBody) return '';
|
||||
const sep = signatureSeparatorEnabled ? `<br><br>-- <br>` : `<br><br>`;
|
||||
const sep = wantsSignatureSeparator(signatureSeparatorEnabled, signatureIdentity) ? `<br><br>-- <br>` : `<br><br>`;
|
||||
if (signatureIdentity?.htmlSignature) {
|
||||
return `${sep}${sanitizeSignatureHtml(signatureIdentity.htmlSignature)}`;
|
||||
}
|
||||
@@ -3342,13 +3343,13 @@ export function EmailComposer({
|
||||
: plainTextMode ? (
|
||||
getPlainTextSignature(signatureIdentity) ? (
|
||||
<div className="px-4 pb-3 text-sm leading-6 text-muted-foreground break-words whitespace-pre-wrap font-mono">
|
||||
{signatureSeparatorEnabled ? '-- \n' : ''}{getPlainTextSignature(signatureIdentity)}
|
||||
{wantsSignatureSeparator(signatureSeparatorEnabled, signatureIdentity) ? '-- \n' : ''}{getPlainTextSignature(signatureIdentity)}
|
||||
</div>
|
||||
) : null
|
||||
) : composerSignatureHtml ? (
|
||||
<div
|
||||
className="px-4 pb-3 text-sm leading-6 text-foreground break-words [&_a]:text-primary [&_a]:underline-offset-2 [&_a:hover]:underline"
|
||||
dangerouslySetInnerHTML={{ __html: `${signatureSeparatorEnabled ? '<div>-- </div>' : ''}${composerSignatureHtml}` }}
|
||||
dangerouslySetInnerHTML={{ __html: `${wantsSignatureSeparator(signatureSeparatorEnabled, signatureIdentity) ? '<div>-- </div>' : ''}${composerSignatureHtml}` }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -54,4 +54,10 @@ describe('htmlToPlainText', () => {
|
||||
it('handles nested lists as separate lines', () => {
|
||||
expect(htmlToPlainText('<ul><li>One</li><li>Two</li></ul>')).toBe('One\nTwo');
|
||||
});
|
||||
|
||||
it('keeps the trailing space of a "-- " signature delimiter', () => {
|
||||
// Without it the text part's delimiter was "--", which clients do not
|
||||
// recognise as the start of a signature.
|
||||
expect(htmlToPlainText('<p>Hi</p><p>-- </p><p>User A</p>')).toBe('Hi\n-- \nUser A');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,4 +116,23 @@ describe('signature-utils', () => {
|
||||
expect(hasMeaningfulHtmlBody('<div>Hello world</div>')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('signature delimiter', () => {
|
||||
// Identities often carry their own "-- " line; the composer's separator on
|
||||
// top of it showed two delimiter lines.
|
||||
const own = { textSignature: '-- \nUser A\nstw-test' };
|
||||
|
||||
it('keeps the space of the "-- " delimiter', () => {
|
||||
expect(getPlainTextSignature(own)).toBe('-- \nUser A\nstw-test');
|
||||
});
|
||||
|
||||
it('adds no second delimiter when the signature has its own', () => {
|
||||
expect(appendPlainTextSignature('Hello', own, { separator: true })).toBe('Hello\n\n-- \nUser A\nstw-test');
|
||||
expect(appendHtmlSignature('<p>Hello</p>', own, { separator: true })).toBe('<p>Hello</p><br><br>-- <br>User A<br>stw-test');
|
||||
});
|
||||
|
||||
it('still adds the delimiter for a signature without one', () => {
|
||||
expect(appendPlainTextSignature('Hello', { textSignature: 'User A' }, { separator: true })).toBe('Hello\n\n-- \nUser A');
|
||||
});
|
||||
});
|
||||
});
|
||||
+5
-1
@@ -61,7 +61,11 @@ function normalizeLineBreaks(value: string): string {
|
||||
return value
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.replace(new RegExp(String.fromCharCode(160), 'g'), ' ')
|
||||
.replace(/[ \t]+\n/g, '\n')
|
||||
// Trims trailing spaces, but keeps the one that makes "-- " the RFC 3676
|
||||
// signature delimiter, so the text part still marks the signature.
|
||||
.split('\n')
|
||||
.map((line) => (/^--[ \t]*$/.test(line) ? '-- ' : line.replace(/[ \t]+$/, '')))
|
||||
.join('\n')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { sanitizeSignatureHtml } from '@/lib/email-sanitization';
|
||||
import { htmlToPlainText } from '@/lib/html-to-text';
|
||||
|
||||
type SignatureSource = {
|
||||
textSignature?: string;
|
||||
htmlSignature?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the signature starts with its own "-- " delimiter line. Many do
|
||||
* (imported from other clients, or set on the server), and the composer's own
|
||||
* separator on top of it showed two delimiter lines.
|
||||
*/
|
||||
export function signatureHasOwnDelimiter(signature?: SignatureSource | null): boolean {
|
||||
const text = signature?.textSignature?.trim()
|
||||
? signature.textSignature
|
||||
: signature?.htmlSignature?.trim()
|
||||
? htmlToPlainText(sanitizeSignatureHtml(signature.htmlSignature))
|
||||
: '';
|
||||
const firstLine = text.replace(/\r\n?/g, '\n').replace(/^\s*\n/, '').split('\n')[0];
|
||||
return /^--[ \t\u00a0]*$/.test(firstLine);
|
||||
}
|
||||
|
||||
/** Whether to add the "-- " separator line in front of this signature. */
|
||||
export function wantsSignatureSeparator(enabled: boolean | undefined, signature?: SignatureSource | null): boolean {
|
||||
return enabled !== false && !signatureHasOwnDelimiter(signature);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { parseHtmlSafely, sanitizeSignatureHtml } from '@/lib/email-sanitization';
|
||||
import { htmlToPlainText } from '@/lib/html-to-text';
|
||||
import { wantsSignatureSeparator } from '@/lib/signature-delimiter';
|
||||
|
||||
type SignatureSource = {
|
||||
textSignature?: string;
|
||||
@@ -10,7 +11,11 @@ function normalizeSignatureLineBreaks(value: string): string {
|
||||
return value
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.replace(/\u00a0/g, ' ')
|
||||
.replace(/[ \t]+\n/g, '\n')
|
||||
// Trims trailing spaces, but keeps the one that makes "-- " the RFC 3676
|
||||
// signature delimiter.
|
||||
.split('\n')
|
||||
.map((line) => (/^--[ \t]*$/.test(line) ? '-- ' : line.replace(/[ \t]+$/, '')))
|
||||
.join('\n')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
}
|
||||
@@ -37,7 +42,7 @@ export function appendPlainTextSignature(
|
||||
return body;
|
||||
}
|
||||
|
||||
const sep = options.separator === false ? '\n\n' : '\n\n-- \n';
|
||||
const sep = wantsSignatureSeparator(options.separator, signature) ? '\n\n-- \n' : '\n\n';
|
||||
return `${body}${sep}${plainTextSignature}`;
|
||||
}
|
||||
|
||||
@@ -95,7 +100,7 @@ export function appendHtmlSignature(
|
||||
signature?: SignatureSource | null,
|
||||
options: { separator?: boolean } = {},
|
||||
): string {
|
||||
const sep = options.separator === false ? '<br><br>' : '<br><br>-- <br>';
|
||||
const sep = wantsSignatureSeparator(options.separator, signature) ? '<br><br>-- <br>' : '<br><br>';
|
||||
|
||||
if (signature?.htmlSignature?.trim()) {
|
||||
return `${htmlBody}${sep}${sanitizeSignatureHtml(signature.htmlSignature)}`;
|
||||
|
||||
Reference in New Issue
Block a user