fix: show one signature separator when the signature brings its own

This commit is contained in:
Linus Rath
2026-10-15 02:36:11 +02:00
parent cbc41c963b
commit 90170ea0e7
4 changed files with 53 additions and 9 deletions
+21
View File
@@ -12,6 +12,7 @@ import {
insertSignatureAboveQuote, insertSignatureAboveQuote,
signatureIdentityFor, signatureIdentityFor,
signPlainTextReply, signPlainTextReply,
signatureHasOwnDelimiter,
} from '../signature-utils'; } from '../signature-utils';
describe('sanitizeSignatureHtml', () => { describe('sanitizeSignatureHtml', () => {
@@ -115,3 +116,23 @@ describe('signPlainTextReply', () => {
expect(signPlainTextReply('Thanks', 'q', null, { position: 'above_quote', separator: true })).toBe('Thanks\n\nq'); expect(signPlainTextReply('Thanks', 'q', null, { position: 'above_quote', separator: true })).toBe('Thanks\n\nq');
}); });
}); });
describe('a signature with its own "-- " line', () => {
const own = { textSignature: '-- \nUser A\nstw-test' };
const plain = { textSignature: 'User A\nstw-test' };
it('is detected, also with a bare "--" or a leading blank line', () => {
expect(signatureHasOwnDelimiter(own)).toBe(true);
expect(signatureHasOwnDelimiter({ textSignature: '\n--\nUser A' })).toBe(true);
expect(signatureHasOwnDelimiter(plain)).toBe(false);
expect(signatureHasOwnDelimiter({ htmlSignature: '<p>-- </p><p>User A</p>' })).toBe(true);
});
it('gets no second separator in plain text or HTML', () => {
const delimiters = (text: string) => text.split('\n').filter((line) => /^--\s*$/.test(line)).length;
expect(delimiters(appendPlainTextSignature('Hi', own, { separator: true }))).toBe(1);
expect(appendPlainTextSignature('Hi', plain, { separator: true })).toBe('Hi\n\n-- \nUser A\nstw-test');
expect(buildEmbeddedSignatureHtml(own, { separator: true })).not.toContain('="separator"');
expect(buildEmbeddedSignatureHtml(plain, { separator: true })).toContain('="separator"');
});
});
+5 -3
View File
@@ -13,7 +13,7 @@ import { htmlToPlainText } from './compose-html';
import { import {
buildEmbeddedSignatureHtml, containsEmbeddedSignature, getPlainTextSignature, buildEmbeddedSignatureHtml, containsEmbeddedSignature, getPlainTextSignature,
plainTextBodyHasSignature, plainTextBodyWithoutSignature, spliceSignature, plainTextBodyHasSignature, plainTextBodyWithoutSignature, spliceSignature,
type SignatureSource, wantsSignatureSeparator, type SignatureSource,
} from './signature-utils'; } from './signature-utils';
/** /**
@@ -67,7 +67,9 @@ export function htmlComposeBodyToPlainText(
const head = text.slice(0, at).replace(/\s+$/, ''); const head = text.slice(0, at).replace(/\s+$/, '');
const tail = text.slice(at + SIGNATURE_TOKEN.length).replace(/^\s+/, ''); const tail = text.slice(at + SIGNATURE_TOKEN.length).replace(/^\s+/, '');
const signature = getPlainTextSignature(identity); const signature = getPlainTextSignature(identity);
const withSignature = signature ? `${head}${separatorText(options.separator)}${signature}` : head; const withSignature = signature
? `${head}${separatorText(wantsSignatureSeparator(options.separator, identity))}${signature}`
: head;
return tail ? `${withSignature}\n\n${tail}` : withSignature; return tail ? `${withSignature}\n\n${tail}` : withSignature;
} }
@@ -86,7 +88,7 @@ export function plainComposeBodyToHtml(
const signature = getPlainTextSignature(identity); const signature = getPlainTextSignature(identity);
const embedded = signature ? buildEmbeddedSignatureHtml(identity, options) : ''; const embedded = signature ? buildEmbeddedSignatureHtml(identity, options) : '';
if (embedded) { if (embedded) {
const marker = `${separatorText(options.separator)}${signature}`; const marker = `${separatorText(wantsSignatureSeparator(options.separator, identity))}${signature}`;
const at = text.indexOf(marker); const at = text.indexOf(marker);
if (at !== -1) { if (at !== -1) {
const before = plainTextToComposerHtml(text.slice(0, at).replace(/^\n+/, '')); const before = plainTextToComposerHtml(text.slice(0, at).replace(/^\n+/, ''));
+24 -3
View File
@@ -90,6 +90,27 @@ export function signatureIdentityFor<T extends { textSignature?: string; htmlSig
return primary && hasSignature(primary) ? primary : null; return primary && hasSignature(primary) ? primary : null;
} }
/**
* 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 (webmail
* lib/signature-delimiter.ts).
*/
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 ]*$/.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);
}
export function getPlainTextSignature(signature?: SignatureSource): string { export function getPlainTextSignature(signature?: SignatureSource): string {
if (signature?.textSignature?.trim()) { if (signature?.textSignature?.trim()) {
return normalizeSignatureLineBreaks(signature.textSignature); return normalizeSignatureLineBreaks(signature.textSignature);
@@ -112,7 +133,7 @@ export function appendPlainTextSignature(
return body; 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}`; return `${body}${sep}${plainTextSignature}`;
} }
@@ -186,7 +207,7 @@ export function appendHtmlSignature(
signature?: SignatureSource, signature?: SignatureSource,
options: { separator?: boolean } = {}, options: { separator?: boolean } = {},
): string { ): 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()) { if (signature?.htmlSignature?.trim()) {
return `${htmlBody}${sep}${sanitizeSignatureHtml(signature.htmlSignature)}`; return `${htmlBody}${sep}${sanitizeSignatureHtml(signature.htmlSignature)}`;
@@ -208,7 +229,7 @@ export function buildEmbeddedSignatureHtml(
identity: SignatureSource, identity: SignatureSource,
options: { separator: boolean }, options: { separator: boolean },
): string { ): string {
const startMarker = options.separator const startMarker = wantsSignatureSeparator(options.separator, identity)
? `<p ${SIGNATURE_RANGE_MARKER}="separator">-- </p>` ? `<p ${SIGNATURE_RANGE_MARKER}="separator">-- </p>`
: `<p ${SIGNATURE_RANGE_MARKER}="start"></p>`; : `<p ${SIGNATURE_RANGE_MARKER}="start"></p>`;
const endMarker = `<p ${SIGNATURE_RANGE_MARKER}="end"><br></p>`; const endMarker = `<p ${SIGNATURE_RANGE_MARKER}="end"><br></p>`;
+3 -3
View File
@@ -67,7 +67,7 @@ import { shouldBlockEditorRemoteImages } from '../lib/editor-html';
import { import {
signatureIdentityFor, buildEmbeddedSignatureHtml, containsEmbeddedSignature, spliceSignature, signatureIdentityFor, buildEmbeddedSignatureHtml, containsEmbeddedSignature, spliceSignature,
insertSignatureAboveQuote, getPlainTextSignature, appendPlainTextSignature, insertSignatureAboveQuote, getPlainTextSignature, appendPlainTextSignature,
plainTextBodyHasSignature, plainTextBodyWithoutSignature, SIGNATURE_RANGE_MARKER, plainTextBodyHasSignature, plainTextBodyWithoutSignature, SIGNATURE_RANGE_MARKER, wantsSignatureSeparator,
} from '../lib/signature-utils'; } from '../lib/signature-utils';
import { import {
getAutoFilledPlaceholders, getPlaceholdersFromTemplate, substitutePlaceholders, templateBodyToHtml, getAutoFilledPlaceholders, getPlaceholdersFromTemplate, substitutePlaceholders, templateBodyToHtml,
@@ -1083,7 +1083,7 @@ export default function ComposeScreen({ route, navigation }: Props) {
const placeSignaturePlain = React.useCallback((body: string, identity: Identity): string => { const placeSignaturePlain = React.useCallback((body: string, identity: Identity): string => {
const sig = getPlainTextSignature(identity); const sig = getPlainTextSignature(identity);
if (!sig) return body; if (!sig) return body;
const sep = signatureSeparatorEnabled ? '\n\n-- \n' : '\n\n'; const sep = wantsSignatureSeparator(signatureSeparatorEnabled, identity) ? '\n\n-- \n' : '\n\n';
if (isReplyLike && signaturePosition === 'above_quote') { if (isReplyLike && signaturePosition === 'above_quote') {
const idx = body.indexOf('\n\n'); const idx = body.indexOf('\n\n');
const head = idx === -1 ? body : body.slice(0, idx); const head = idx === -1 ? body : body.slice(0, idx);
@@ -1139,7 +1139,7 @@ export default function ComposeScreen({ route, navigation }: Props) {
stripped = plainTextBodyWithoutSignature(body, prev); stripped = plainTextBodyWithoutSignature(body, prev);
} else if (prev) { } else if (prev) {
const old = getPlainTextSignature(prev); const old = getPlainTextSignature(prev);
const marker = `${signatureSeparatorEnabled ? '\n\n-- \n' : '\n\n'}${old}`; const marker = `${wantsSignatureSeparator(signatureSeparatorEnabled, prev) ? '\n\n-- \n' : '\n\n'}${old}`;
if (old && body.includes(marker)) stripped = body.replace(marker, ''); if (old && body.includes(marker)) stripped = body.replace(marker, '');
} }
return signatureIdentity ? placeSignaturePlain(stripped, signatureIdentity) : stripped; return signatureIdentity ? placeSignaturePlain(stripped, signatureIdentity) : stripped;