fix(composer): keep HTML signature styling in the editor and on send
Rich, table-based identity signatures lost all their inline CSS (background/text colors, fonts, border-radius, bgcolor). The composer embeds the signature into the TipTap editor, and parsing it into the ProseMirror schema flattened it to a generic bordered table. That normalized version was then shown while composing AND delivered to the recipient, even though Identity settings stored and previewed it correctly. Hold the signature as a dedicated, non-editable atom node (SignatureBlock) that keeps the verbatim HTML in an attribute and renders it inside a Shadow Root, mirroring the existing QuotedHtml island. The markup is never parsed into the schema, so the styling survives 1:1 both in the in-editor preview and in the outgoing mail (serializeEditorContent inlines the verbatim HTML, as it already does for quoted originals). The signature stays a single unit: select it and Backspace/Delete to remove it; identity switching still swaps it via the existing data-signature-block markers. Adds unit coverage (parse + serialize round-trip preserves inline styles). Fixes #475 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
Linus Rath
co-authored by
Claude Opus 4.8
parent
1119d8ed73
commit
198407ebf5
@@ -15,6 +15,7 @@ import { sanitizeSignatureHtml, sanitizeEmailHtml } from "@/lib/email-sanitizati
|
||||
import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix";
|
||||
import { isFilePreviewable } from "@/lib/file-preview";
|
||||
import { buildQuotedHtmlBlock, serializeEditorContent } from "@/components/email/quoted-html";
|
||||
import { buildSignatureBlock } from "@/components/email/signature-block";
|
||||
import { emailHooks, contactHooks } from "@/lib/plugin-hooks";
|
||||
import type { OutgoingEmail, RecipientSuggestion } from "@/lib/plugin-types";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
@@ -188,11 +189,13 @@ type SignatureIdentityLike = {
|
||||
textSignature?: string;
|
||||
} | null | undefined;
|
||||
|
||||
// Render the embedded signature for "above quote" mode. Bracketed with
|
||||
// `data-signature-block` marker paragraphs so we can swap the inner content
|
||||
// when the user switches identity without losing the surrounding draft or
|
||||
// quoted message. The markers are preserved through TipTap by the
|
||||
// StyledParagraph extension.
|
||||
// Render the embedded signature. Bracketed with `data-signature-block` marker
|
||||
// paragraphs so we can swap the inner content when the user switches identity
|
||||
// without losing the surrounding draft or quoted message. The markers are
|
||||
// preserved through TipTap by the StyledParagraph extension. The HTML
|
||||
// signature itself is wrapped in a SignatureBlock atom node so its inline
|
||||
// styling survives the editor (see signature-block.ts) instead of being
|
||||
// flattened by the schema.
|
||||
function buildEmbeddedSignatureHtml(
|
||||
identity: SignatureIdentityLike,
|
||||
options: { embed: boolean; separator: boolean }
|
||||
@@ -203,7 +206,7 @@ function buildEmbeddedSignatureHtml(
|
||||
: `<p data-signature-block="start"></p>`;
|
||||
const endMarker = `<p data-signature-block="end"></p>`;
|
||||
if (identity?.htmlSignature) {
|
||||
return `${startMarker}${sanitizeSignatureHtml(identity.htmlSignature)}${endMarker}`;
|
||||
return `${startMarker}${buildSignatureBlock(sanitizeSignatureHtml(identity.htmlSignature))}${endMarker}`;
|
||||
}
|
||||
if (identity?.textSignature) {
|
||||
const escaped = identity.textSignature
|
||||
|
||||
Reference in New Issue
Block a user