fix(reader): replace light callout backgrounds in dark mode (#4392)
Why: - Dark mode sets theme foreground on html/body and rewrites black text, but EPUB callout boxes often keep white/light backgrounds from inline styles or publisher CSS unless override book color is enabled. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
GitHub
parent
458ad7510c
commit
176b950c92
@@ -571,6 +571,22 @@ describe('getColorStyles branches (via getStyles)', () => {
|
||||
expect(css).not.toMatch(/body\.pbg\s*\{[^}]*background-color:[^}]*!important/);
|
||||
});
|
||||
|
||||
it('overrides inline white backgrounds in dark mode when overrideColor is false', () => {
|
||||
const vs = makeViewSettings({ overrideColor: false });
|
||||
const theme = makeThemeCode({ isDarkMode: true, bg: '#1a1a1a', fg: '#e0e0e0' });
|
||||
const css = getStyles(vs, theme);
|
||||
expect(css).toContain('background-color: #1a1a1a !important');
|
||||
expect(css).toContain('background-color: #fff"]');
|
||||
expect(css).toContain('body.theme-dark');
|
||||
});
|
||||
|
||||
it('does not add inline white background overrides in light mode', () => {
|
||||
const vs = makeViewSettings({ overrideColor: false });
|
||||
const theme = makeThemeCode({ isDarkMode: false });
|
||||
const css = getStyles(vs, theme);
|
||||
expect(css).not.toContain('background-color: #fff"]');
|
||||
});
|
||||
|
||||
it('applies dark mode link color lightblue when overrideColor is false', () => {
|
||||
const vs = makeViewSettings({ overrideColor: false });
|
||||
const theme = makeThemeCode({ isDarkMode: true, bg: '#1a1a1a', fg: '#e0e0e0' });
|
||||
|
||||
@@ -313,6 +313,27 @@ describe('transformStylesheet', () => {
|
||||
expect(result).toBe(css);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dark mode light backgrounds', () => {
|
||||
it('rewrites white backgrounds in EPUB CSS to theme bg', () => {
|
||||
localStorage.setItem('themeMode', 'dark');
|
||||
localStorage.setItem('themeColor', 'default');
|
||||
localStorage.setItem('systemIsDarkMode', 'false');
|
||||
const css = '.callout { background-color: #ffffff; padding: 1em; }';
|
||||
const result = transformStylesheet(css, VW, VH, VERTICAL);
|
||||
expect(result).toMatch(/background-color:\s*#[0-9a-f]{6}/i);
|
||||
expect(result).not.toContain('background-color: #ffffff');
|
||||
localStorage.removeItem('themeMode');
|
||||
});
|
||||
|
||||
it('leaves dark backgrounds unchanged in dark mode', () => {
|
||||
localStorage.setItem('themeMode', 'dark');
|
||||
const css = '.panel { background-color: #222; }';
|
||||
const result = transformStylesheet(css, VW, VH, VERTICAL);
|
||||
expect(result).toContain('background-color: #222');
|
||||
localStorage.removeItem('themeMode');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFootnoteStyles', () => {
|
||||
|
||||
@@ -108,6 +108,54 @@ const getFontStyles = (
|
||||
return fontStyles;
|
||||
};
|
||||
|
||||
/** True for #fff, #f5f5f5, rgb(255,…), etc. Used when rewriting EPUB CSS in dark mode. */
|
||||
const isLightCssColor = (value: string): boolean => {
|
||||
const v = value.trim().toLowerCase();
|
||||
if (v === 'white') return true;
|
||||
const hex = v.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
||||
if (hex) {
|
||||
const h = hex[1]!;
|
||||
const expand =
|
||||
h.length === 3
|
||||
? h
|
||||
.split('')
|
||||
.map((c) => c + c)
|
||||
.join('')
|
||||
: h;
|
||||
const r = parseInt(expand.slice(0, 2), 16);
|
||||
const g = parseInt(expand.slice(2, 4), 16);
|
||||
const b = parseInt(expand.slice(4, 6), 16);
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
return luminance > 0.85;
|
||||
}
|
||||
const rgb = v.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
|
||||
if (rgb?.[1] != null && rgb[2] != null && rgb[3] != null) {
|
||||
const r = parseInt(rgb[1], 10);
|
||||
const g = parseInt(rgb[2], 10);
|
||||
const b = parseInt(rgb[3], 10);
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
return luminance > 0.85;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const getDarkModeLightBackgroundOverrides = (bg: string) => `
|
||||
/* Callout boxes often use inline white/light backgrounds while html/body set dark fg. */
|
||||
*[style*="background-color: #fff"], *[style*="background-color:#fff"],
|
||||
*[style*="background-color: #ffffff"], *[style*="background-color:#ffffff"],
|
||||
*[style*="background-color: white"], *[style*="background-color:white"],
|
||||
*[style*="background: #fff"], *[style*="background:#fff"],
|
||||
*[style*="background: #ffffff"], *[style*="background:#ffffff"],
|
||||
*[style*="background: white"], *[style*="background:white"],
|
||||
*[style*="background-color: rgb(255"], *[style*="background-color:rgb(255"],
|
||||
*[style*="background: rgb(255"], *[style*="background:rgb(255"] {
|
||||
background-color: ${bg} !important;
|
||||
}
|
||||
body.theme-dark {
|
||||
background-color: ${bg} !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const getEinkSelectionStyles = () => {
|
||||
return `
|
||||
::selection {
|
||||
@@ -222,6 +270,7 @@ const getColorStyles = (
|
||||
*[style*="color:#000"], *[style*="color:#000000"], *[style*="color:black"] {
|
||||
color: ${fg} !important;
|
||||
}
|
||||
${isDarkMode && !overrideColor ? getDarkModeLightBackgroundOverrides(bg) : ''}
|
||||
/* for the Gutenberg eBooks */
|
||||
#pg-header * {
|
||||
color: inherit !important;
|
||||
@@ -975,6 +1024,22 @@ export const transformStylesheet = (css: string, vw: number, vh: number, vertica
|
||||
.replace(/([\s;])color\s*:\s*#000000/gi, '$1color: var(--theme-fg-color)')
|
||||
.replace(/([\s;])color\s*:\s*#000/gi, '$1color: var(--theme-fg-color)')
|
||||
.replace(/([\s;])color\s*:\s*rgb\(0,\s*0,\s*0\)/gi, '$1color: var(--theme-fg-color)');
|
||||
|
||||
const { isDarkMode, bg } = getThemeCode();
|
||||
if (isDarkMode) {
|
||||
css = css.replace(ruleRegex, (match, selector, block) => {
|
||||
const rewritten = block.replace(
|
||||
/background(-color)?\s*:\s*([^;!}]+)(\s*!important)?(?=\s*[;!}])/gi,
|
||||
(decl: string, _prop: string, value: string, important?: string) => {
|
||||
const raw = value.trim().split(/\s+/)[0] ?? '';
|
||||
if (!isLightCssColor(raw)) return decl;
|
||||
return `background-color: ${bg}${important ?? ''}`;
|
||||
},
|
||||
);
|
||||
return rewritten === block ? match : selector + rewritten;
|
||||
});
|
||||
}
|
||||
|
||||
return css;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user