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
|
/* ShadowRenderer.js - A tiny library to render safely encapsulated HTML */
const ShadowRenderer = {
render: function (hostElement, htmlContent, options = {}) {
// 1. Attach Shadow Root (if not exists)
let shadow = hostElement.shadowRoot;
if (!shadow) {
shadow = hostElement.attachShadow({ mode: 'open' });
}
// 2. Parse HTML
const parser = new DOMParser();
const doc = parser.parseFromString(htmlContent, 'text/html');
// 3. Rewrite Styles (Body -> Wrapper)
const styles = doc.querySelectorAll('style');
styles.forEach(style => {
// Replace 'body' selector with '.mail-body-content'
style.textContent = style.textContent.replace(/(^|[\}\s,;])body(?=[\s,\.\{])/gi, '$1.mail-body-content');
});
// 4. Create Wrapper
const wrapper = document.createElement('div');
wrapper.className = 'mail-body-content';
// Copy attributes & move children
if (doc.body) {
Array.from(doc.body.attributes).forEach(attr => {
if (attr.name === 'class') {
if (attr.value) wrapper.classList.add(...attr.value.split(' '));
} else {
wrapper.setAttribute(attr.name, attr.value);
}
});
// Handle legacy bgcolor if present
if (doc.body.bgColor) wrapper.style.backgroundColor = doc.body.bgColor;
while (doc.body.firstChild) wrapper.appendChild(doc.body.firstChild);
}
// 5. Build Shadow Content
shadow.innerHTML = ''; // Clear previous
// Append Head Content (Styles)
if (doc.head) {
while (doc.head.firstChild) shadow.appendChild(doc.head.firstChild);
}
// Append Body Wrapper
shadow.appendChild(wrapper);
// 6. Apply Default Styles
const defaultStyle = document.createElement('style');
defaultStyle.textContent = `
:host { display: block; overflow: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
.mail-body-content { margin: 0; padding: 16px; min-height: 100%; }
img { max-width: 100%; height: auto; }
a { color: #1a73e8; }
`;
shadow.prepend(defaultStyle);
return shadow;
}
};
|