files: readable text previews in dark themes (#697)

* files: readable text previews in dark themes

A same-origin text document inherits the page's color-scheme, so dark
themes rendered the browser's default light text on the preview's
forced white background. Force a light color-scheme on the preview
iframe so text is always black on white.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* files: text previews follow the theme

Instead of pinning the preview iframe to a light scheme, follow the
theme: dark, night, and amber render the document light-on-dark against
the theme's panel color; light stays black-on-white.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
lrnselfreliance 2026-09-03 15:47:44 -06:00 • committed by GitHub
parent f1270b4e6c
commit 9b8ebdc9b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 4 deletions

View file

@ -100,8 +100,8 @@ function getEpubViewerURL(previewFile) {
* A component rather than part of `getIframePreviewModal` because it reads the theme: the
* modal builders are plain functions called from an event handler, where hooks cannot run.
*/
function IframePreview({url, gatePdf}) {
const {mediaFilterEnabled} = React.useContext(ThemeContext);
export function IframePreview({url, gatePdf}) {
const {isDark, mediaFilterEnabled} = React.useContext(ThemeContext);
/*
* `gatePdf` only for PDFs. Text and the EPUB viewer are documents of our own, which the
@ -119,8 +119,12 @@ function IframePreview({url, gatePdf}) {
<iframe title='textModal' src={url}
style={{
height: '100%', width: '100%', border: 'none',
// Use white to avoid iframe displaying with dark-theme.
backgroundColor: '#ffffff',
// A same-origin document has no styles of its own; it renders with the
// browser's default stylesheet for whatever color-scheme this element
// declares. Pin the scheme to the theme so text is light-on-dark under
// the dark themes and black-on-white under light, never white-on-white.
backgroundColor: isDark ? 'var(--panel)' : '#ffffff',
colorScheme: isDark ? 'dark' : 'light',
}}/>
</div>
</MediaGate>

View file

@ -100,3 +100,45 @@ describe('preview gate remounting (source contract)', () => {
expect(rebuild.length).toBeGreaterThan(0);
});
});
describe('IframePreview color scheme', () => {
/*
* The previewed file is a bare document with no styles of its own, so it renders with the
* browser's default stylesheet for whatever color-scheme the iframe element declares. If
* the scheme does not follow the theme, one combination is always unreadable: a dark page
* scheme with a white iframe renders the browser's light text on white.
*/
const {render} = require('@testing-library/react');
const {ThemeContext} = require('../contexts/contexts');
const {IframePreview} = require('./FilePreview');
const renderWithTheme = (isDark) => render(
<ThemeContext.Provider value={{isDark, mediaFilterEnabled: false}}>
<IframePreview url='/media/config/wrolpi.yaml'/>
</ThemeContext.Provider>
);
test('dark themes render the document dark with a dark background', () => {
const {container} = renderWithTheme(true);
const iframe = container.querySelector('iframe');
expect(iframe.style.colorScheme).toEqual('dark');
});
test('the dark background is the theme panel color', () => {
/*
* Read off the source because jsdom's CSSOM drops var() declarations entirely, so a
* rendered iframe in jsdom cannot show the background it gets in a real browser.
*/
const fs = require('fs');
const path = require('path');
const source = fs.readFileSync(path.join(__dirname, 'FilePreview.js'), 'utf8');
expect(source).toContain(`backgroundColor: isDark ? 'var(--panel)' : '#ffffff'`);
});
test('the light theme renders the document black-on-white', () => {
const {container} = renderWithTheme(false);
const iframe = container.querySelector('iframe');
expect(iframe.style.colorScheme).toEqual('light');
expect(iframe.style.backgroundColor).toEqual('rgb(255, 255, 255)');
});
});