mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
The composer's format came only from the "plain text only" setting, and a reopened draft was forced into it: with plain text on, an HTML draft was flattened on its first autosave, and with it off a text draft opened in the rich-text editor. Ported from the webmail (f6ad8a59, `compose-format.ts`): - a reopened draft opens in the format it was written in; an empty draft and new messages still follow the setting - a toolbar button switches just this message between rich and plain text, converting the body in place; the signature keeps its place (end or above the quote) in the target format, so it is never doubled or lost - images inserted into the rich-text body stay on as ordinary attachments after a switch to plain text instead of being dropped
103 lines
4.1 KiB
TypeScript
103 lines
4.1 KiB
TypeScript
// The composer's per-message HTML <-> plain-text switch (#1022). Port of the
|
|
// webmail's components/email/compose-format.ts.
|
|
//
|
|
// Both directions keep what the send, draft-save and identity-switch paths
|
|
// rely on: whether (and where) the body carries the signature. An HTML body
|
|
// carries it as a marker-bracketed range (`containsEmbeddedSignature`), a
|
|
// plain-text body as the signature text after its separator. The converters
|
|
// take the signature out in the source format and put it back, in the same
|
|
// place, in the target format, so a switch never doubles or loses it.
|
|
|
|
import { escapeHtml } from './email-html';
|
|
import { htmlToPlainText } from './compose-html';
|
|
import {
|
|
buildEmbeddedSignatureHtml, containsEmbeddedSignature, getPlainTextSignature,
|
|
plainTextBodyHasSignature, plainTextBodyWithoutSignature, spliceSignature,
|
|
type SignatureSource,
|
|
} from './signature-utils';
|
|
|
|
/**
|
|
* The format a composer opens in. A reopened draft keeps the one it was
|
|
* written in - an HTML draft used to be flattened by the "plain text only"
|
|
* setting on its first autosave. An empty draft and every new message follow
|
|
* the setting.
|
|
*/
|
|
export function initialPlainTextMode(
|
|
draft: { htmlBody?: string; textBody?: string } | null | undefined,
|
|
plainTextSetting: boolean,
|
|
): boolean {
|
|
if (draft?.htmlBody) return false;
|
|
if (draft?.textBody) return true;
|
|
return plainTextSetting;
|
|
}
|
|
|
|
/** Plain text as editor HTML: a paragraph per blank line, `<br>` per newline. */
|
|
export function plainTextToComposerHtml(text: string): string {
|
|
if (!text) return '';
|
|
return text
|
|
.replace(/\r\n?/g, '\n')
|
|
.split(/\n{2,}/)
|
|
.map((paragraph) => `<p>${escapeHtml(paragraph).replace(/\n/g, '<br>')}</p>`)
|
|
.join('');
|
|
}
|
|
|
|
function separatorText(separator: boolean): string {
|
|
return separator ? '\n\n-- \n' : '\n\n';
|
|
}
|
|
|
|
// Stands in for the signature while the rest of the body is converted. Control
|
|
// characters survive `htmlToPlainText`, which only strips tags and entities.
|
|
const SIGNATURE_TOKEN = '\u0003signature\u0003';
|
|
|
|
/**
|
|
* The rich-text body as a plain-text body. Formatting and inline images are
|
|
* dropped (that is the point of plain text); an embedded signature comes back
|
|
* as the identity's plain-text signature, where it was: at the end, or above
|
|
* the quote.
|
|
*/
|
|
export function htmlComposeBodyToPlainText(
|
|
html: string,
|
|
identity: SignatureSource,
|
|
options: { separator: boolean },
|
|
): string {
|
|
if (!containsEmbeddedSignature(html)) return htmlToPlainText(html);
|
|
const text = htmlToPlainText(spliceSignature(html, `<p>${SIGNATURE_TOKEN}</p>`));
|
|
const at = text.indexOf(SIGNATURE_TOKEN);
|
|
if (at === -1) return text;
|
|
const head = text.slice(0, at).replace(/\s+$/, '');
|
|
const tail = text.slice(at + SIGNATURE_TOKEN.length).replace(/^\s+/, '');
|
|
const signature = getPlainTextSignature(identity);
|
|
const withSignature = signature ? `${head}${separatorText(options.separator)}${signature}` : head;
|
|
return tail ? `${withSignature}\n\n${tail}` : withSignature;
|
|
}
|
|
|
|
/**
|
|
* A plain-text body as editor HTML. The identity's signature - after its
|
|
* separator above the quote, or at the end - becomes the embedded,
|
|
* marker-bracketed signature, so identity switches and sending keep working
|
|
* as in a body that started out as HTML.
|
|
*/
|
|
export function plainComposeBodyToHtml(
|
|
text: string,
|
|
identity: SignatureSource,
|
|
options: { separator: boolean },
|
|
): string {
|
|
const empty = '<p><br></p>';
|
|
const signature = getPlainTextSignature(identity);
|
|
const embedded = signature ? buildEmbeddedSignatureHtml(identity, options) : '';
|
|
if (embedded) {
|
|
const marker = `${separatorText(options.separator)}${signature}`;
|
|
const at = text.indexOf(marker);
|
|
if (at !== -1) {
|
|
const before = plainTextToComposerHtml(text.slice(0, at).replace(/^\n+/, ''));
|
|
const after = plainTextToComposerHtml(text.slice(at + marker.length).replace(/^\n+/, ''));
|
|
return `${before || empty}${embedded}${after}`;
|
|
}
|
|
if (plainTextBodyHasSignature(text, identity)) {
|
|
const before = plainTextToComposerHtml(plainTextBodyWithoutSignature(text, identity).replace(/^\n+/, ''));
|
|
return `${before || empty}${embedded}`;
|
|
}
|
|
}
|
|
return plainTextToComposerHtml(text) || empty;
|
|
}
|