-
-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathemail-composer-utils.ts
More file actions
79 lines (73 loc) · 2.77 KB
/
email-composer-utils.ts
File metadata and controls
79 lines (73 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const HTML_ESCAPE_MAP = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
} as const;
function escapeHtml(value: string): string {
return value.replace(/[&<>"']/g, (char) =>
HTML_ESCAPE_MAP[char as keyof typeof HTML_ESCAPE_MAP]
);
}
export function plainTextToComposerBody(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("");
}
// Transparent 1x1 GIF used as a stand-in src while the real inline image is
// being fetched from JMAP. Browsers cannot render `cid:` URLs directly, so
// without this swap the editor would show a broken-image icon (issue #163).
export const INLINE_IMAGE_PLACEHOLDER =
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
/**
* Rewrites `<img src="cid:xxx">` references into `<img src="<placeholder>" data-cid="xxx">`
* so TipTap can render the editor (the original cid: URL would 404) while still
* carrying the cid through edits. The placeholder is swapped to the actual
* image data once the corresponding inline blob has been fetched.
*/
export function rewriteCidImagesForEditor(html: string): string {
if (!html || html.indexOf("cid:") === -1) return html;
const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
let touched = false;
doc.querySelectorAll("img").forEach((img) => {
const src = img.getAttribute("src") || "";
if (!/^cid:/i.test(src)) return;
const cid = src.slice(4);
if (!cid) return;
if (!img.getAttribute("data-cid")) {
img.setAttribute("data-cid", cid);
}
img.setAttribute("src", INLINE_IMAGE_PLACEHOLDER);
touched = true;
});
return touched ? doc.body.innerHTML : html;
}
/**
* Replaces the placeholder src on `<img data-cid="...">` elements with the
* resolved data URL once the inline blob has been fetched. Leaves images
* whose src has been edited away from the placeholder/cid alone.
*/
export function replaceInlineImagePlaceholders(
html: string,
cidToDataUrl: Map<string, string>
): string {
if (!html || cidToDataUrl.size === 0) return html;
if (html.indexOf("data-cid") === -1) return html;
const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
let changed = false;
doc.querySelectorAll("img[data-cid]").forEach((img) => {
const cid = img.getAttribute("data-cid");
if (!cid) return;
const dataUrl = cidToDataUrl.get(cid);
if (!dataUrl) return;
const currentSrc = img.getAttribute("src") || "";
if (currentSrc !== INLINE_IMAGE_PLACEHOLDER && !/^cid:/i.test(currentSrc)) return;
img.setAttribute("src", dataUrl);
changed = true;
});
return changed ? doc.body.innerHTML : html;
}