fix: preserve emoji colors in dark mode email viewer
This commit is contained in:
@@ -2884,6 +2884,75 @@ export function EmailViewer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-invert emoji glyphs so they keep their original colors. The
|
||||||
|
// body's invert filter flips colored emoji (yellow smiley → blue,
|
||||||
|
// red heart → cyan, etc.). Wrap each emoji run in a span that
|
||||||
|
// re-inverts. Only act when the ancestor invert depth is odd —
|
||||||
|
// emojis inside a double-inverted bgcolor container already render
|
||||||
|
// at their original colors.
|
||||||
|
let emojiRe: RegExp;
|
||||||
|
try {
|
||||||
|
emojiRe = new RegExp('\\p{RGI_Emoji}', 'gv');
|
||||||
|
} catch {
|
||||||
|
emojiRe = /\p{Extended_Pictographic}(?:\uFE0F)?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F)?)*/gu;
|
||||||
|
}
|
||||||
|
const emojiTestRe = /\p{Extended_Pictographic}/u;
|
||||||
|
const SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'IFRAME']);
|
||||||
|
|
||||||
|
const isOddInvertDepth = (start: Element | null) => {
|
||||||
|
let count = 0;
|
||||||
|
let n: Element | null = start;
|
||||||
|
while (n) {
|
||||||
|
if (n === doc.body) { count++; break; }
|
||||||
|
const cs = win.getComputedStyle(n);
|
||||||
|
if (cs.filter && cs.filter.includes('invert')) count++;
|
||||||
|
n = n.parentElement;
|
||||||
|
}
|
||||||
|
return count % 2 === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, {
|
||||||
|
acceptNode(node) {
|
||||||
|
let p = node.parentElement;
|
||||||
|
while (p) {
|
||||||
|
if (SKIP_TAGS.has(p.tagName)) return NodeFilter.FILTER_REJECT;
|
||||||
|
p = p.parentElement;
|
||||||
|
}
|
||||||
|
return emojiTestRe.test(node.nodeValue || '')
|
||||||
|
? NodeFilter.FILTER_ACCEPT
|
||||||
|
: NodeFilter.FILTER_REJECT;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emojiTextNodes: Text[] = [];
|
||||||
|
let cur: Node | null;
|
||||||
|
while ((cur = walker.nextNode())) emojiTextNodes.push(cur as Text);
|
||||||
|
|
||||||
|
emojiTextNodes.forEach((textNode) => {
|
||||||
|
const parent = textNode.parentElement;
|
||||||
|
if (!parent || !isOddInvertDepth(parent)) return;
|
||||||
|
const text = textNode.nodeValue || '';
|
||||||
|
emojiRe.lastIndex = 0;
|
||||||
|
const frag = doc.createDocumentFragment();
|
||||||
|
let lastIndex = 0;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
while ((m = emojiRe.exec(text)) !== null) {
|
||||||
|
if (m.index > lastIndex) {
|
||||||
|
frag.appendChild(doc.createTextNode(text.slice(lastIndex, m.index)));
|
||||||
|
}
|
||||||
|
const span = doc.createElement('span');
|
||||||
|
span.style.cssText = 'filter:invert(1) hue-rotate(180deg)';
|
||||||
|
span.textContent = m[0];
|
||||||
|
frag.appendChild(span);
|
||||||
|
lastIndex = m.index + m[0].length;
|
||||||
|
}
|
||||||
|
if (lastIndex === 0) return;
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
frag.appendChild(doc.createTextNode(text.slice(lastIndex)));
|
||||||
|
}
|
||||||
|
parent.replaceChild(frag, textNode);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user