mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 08:15:38 +00:00
fix: leave an empty line above the signature delimiter in replies #1503
This commit is contained in:
@@ -304,6 +304,33 @@ describe('reply signature above the quote (#842)', () => {
|
||||
expect(html).toContain('<i>Sales team</i>');
|
||||
expect(html.indexOf('<i>Sales team</i>')).toBeLessThan(html.indexOf('reply_line'));
|
||||
});
|
||||
|
||||
// #1503: with the delimiter on, a reply started with the "-- " line, so
|
||||
// there was no line above the signature to type the reply into.
|
||||
it('leaves an empty line above the delimiter', () => {
|
||||
useSettingsStore.setState({ signaturePosition: 'above_quote' });
|
||||
setIdentities([ME]);
|
||||
render(<EmailComposer mode="reply" replyTo={TO_ME} />);
|
||||
|
||||
const html = screen.getByTestId('rich-text-editor').getAttribute('data-html')!;
|
||||
expect(html.startsWith('<p></p><p data-signature-block="separator">-- </p>')).toBe(true);
|
||||
});
|
||||
|
||||
it('leaves an empty line above a delimiter that arrives with late identities', async () => {
|
||||
useSettingsStore.setState({ signaturePosition: 'above_quote' });
|
||||
setIdentities([]);
|
||||
const props = { mode: 'reply' as const, replyTo: TO_ME };
|
||||
const { rerender } = render(<EmailComposer {...props} />);
|
||||
setIdentities([ME]);
|
||||
rerender(<EmailComposer {...props} />);
|
||||
|
||||
await waitFor(() => expect(editorState.html).toContain('<b>Alice</b>'));
|
||||
const lines = new DOMParser().parseFromString(editorState.html, 'text/html').body.children;
|
||||
expect(lines[0].tagName).toBe('P');
|
||||
expect(lines[0].textContent).toBe('');
|
||||
expect(lines[0].hasAttribute('data-signature-block')).toBe(false);
|
||||
expect(lines[1].getAttribute('data-signature-block')).toBe('separator');
|
||||
});
|
||||
});
|
||||
|
||||
it('still signs a reply below the quote at send time by default', async () => {
|
||||
|
||||
@@ -139,7 +139,19 @@ export function replaceEmbeddedSignature(
|
||||
? children.find((node) => node.textContent?.includes(options.quoteHeader!))
|
||||
: undefined;
|
||||
const quote = header ?? children.find(isQuoteBoundary) ?? null;
|
||||
removed = { parent: doc.body, insertBefore: quote };
|
||||
// With no line above it, the signature would open the body and leave no
|
||||
// place to type (#1503). Put an empty one first, as getInitialBody does,
|
||||
// and keep the bare line breaks for the gap down to the quote.
|
||||
const above = quote ? children.slice(0, children.indexOf(quote)) : children;
|
||||
const hasLineAbove = above.some((node) => node.nodeType === 1
|
||||
? (node as Element).tagName !== 'BR'
|
||||
: !!node.textContent?.trim());
|
||||
if (!hasLineAbove) {
|
||||
removed = { parent: doc.body, insertBefore: above[0] ?? quote };
|
||||
signatureHtml = `<p></p>${signatureHtml}`;
|
||||
} else {
|
||||
removed = { parent: doc.body, insertBefore: quote };
|
||||
}
|
||||
}
|
||||
const host = doc.createElement('div');
|
||||
host.innerHTML = signatureHtml;
|
||||
|
||||
@@ -771,10 +771,13 @@ export function EmailComposer({
|
||||
: (from.email || from.name || tCommon('unknown')))
|
||||
: tCommon('unknown');
|
||||
|
||||
// Like a new mail, an empty line above the signature gives the cursor a
|
||||
// place for the reply. Without it the reply started on the "-- " line (#1503).
|
||||
const signatureBlock = buildEmbeddedSignatureHtml(initialSignatureIdentity, {
|
||||
embed: shouldEmbedSignatureAboveQuote,
|
||||
separator: signatureSeparatorEnabled,
|
||||
});
|
||||
const head = `${prefix || (signatureBlock ? '<p></p>' : '')}${signatureBlock}`;
|
||||
|
||||
// Whether the quoted original may show its remote images in the editor:
|
||||
// the viewer's rule, minus the per-message "load" click it doesn't know.
|
||||
@@ -798,7 +801,7 @@ export function EmailComposer({
|
||||
rewriteCidImagesForEditor(sanitizeEmailHtml(replyTo.htmlBody)),
|
||||
{ remoteAllowed: quoteRemoteAllowed, showQuoteBorder: replyTo.quoteWrapInBlockquote !== false },
|
||||
);
|
||||
return `${prefix}${signatureBlock}<br>${replyHeader(replyTo.quoteHeaderHtml)}${island}`;
|
||||
return `${head}<br>${replyHeader(replyTo.quoteHeaderHtml)}${island}`;
|
||||
}
|
||||
const escaped = replyTo.body
|
||||
? replyTo.body.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>')
|
||||
@@ -807,7 +810,7 @@ export function EmailComposer({
|
||||
const bodyHtml = wrap
|
||||
? `<blockquote style="margin:0 0 0 0.8ex;border-left:2px solid #ccc;padding-left:1ex">${escaped}</blockquote>`
|
||||
: escaped;
|
||||
return `${prefix}${signatureBlock}<br>${replyHeader(replyTo.quoteHeaderHtml)}${bodyHtml}`;
|
||||
return `${head}<br>${replyHeader(replyTo.quoteHeaderHtml)}${bodyHtml}`;
|
||||
}
|
||||
|
||||
// Build quoted content as HTML
|
||||
@@ -825,15 +828,15 @@ export function EmailComposer({
|
||||
rewriteCidImagesForEditor(sanitizeEmailHtml(replyTo.htmlBody)),
|
||||
{ remoteAllowed: quoteRemoteAllowed },
|
||||
);
|
||||
return `${prefix}${signatureBlock}<br>${replyHeader(`<div>${quoteHeader}</div>`)}${island}`;
|
||||
return `${head}<br>${replyHeader(`<div>${quoteHeader}</div>`)}${island}`;
|
||||
}
|
||||
|
||||
if (replyTo.body) {
|
||||
const escapedOriginal = replyTo.body.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
||||
if (mode === 'forward') {
|
||||
return `${prefix}${signatureBlock}<br><br>${tQuote('forwarded_separator')}<br>${tQuote('from_label')}: ${escapeHtml(fromStrFull)}<br>${tQuote('date_label')}: ${escapeHtml(date)}<br>${tQuote('subject_label')}: ${escapeHtml(replyTo.subject || '')}<br><br>${escapedOriginal}`;
|
||||
return `${head}<br><br>${tQuote('forwarded_separator')}<br>${tQuote('from_label')}: ${escapeHtml(fromStrFull)}<br>${tQuote('date_label')}: ${escapeHtml(date)}<br>${tQuote('subject_label')}: ${escapeHtml(replyTo.subject || '')}<br><br>${escapedOriginal}`;
|
||||
} else if (mode === 'reply' || mode === 'replyAll') {
|
||||
return `${prefix}${signatureBlock}<br><br>${replyHeader(`<p>${tQuote('reply_line', { date: escapeHtml(date), from: escapeHtml(fromStrFull) })}<br></p>`)}<blockquote style="margin:0 0 0 0.8ex;border-left:2px solid #ccc;padding-left:1ex">${escapedOriginal}</blockquote>`;
|
||||
return `${head}<br><br>${replyHeader(`<p>${tQuote('reply_line', { date: escapeHtml(date), from: escapeHtml(fromStrFull) })}<br></p>`)}<blockquote style="margin:0 0 0 0.8ex;border-left:2px solid #ccc;padding-left:1ex">${escapedOriginal}</blockquote>`;
|
||||
}
|
||||
}
|
||||
return prefix;
|
||||
|
||||
Reference in New Issue
Block a user