forked from akai/readest
This commit is contained in:
@@ -2,6 +2,8 @@ import { describe, it, expect, vi } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import SectionInfo from '@/app/reader/components/SectionInfo';
|
||||
|
||||
let currentBookData: { isFixedLayout: boolean } | undefined;
|
||||
|
||||
vi.mock('@/context/EnvContext', () => ({
|
||||
useEnv: () => ({ appService: { isAndroidApp: false, isMobile: true } }),
|
||||
}));
|
||||
@@ -19,6 +21,14 @@ vi.mock('@/store/readerStore', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/bookDataStore', () => {
|
||||
const state = { getBookData: () => currentBookData };
|
||||
return {
|
||||
useBookDataStore: <R,>(selector?: (s: typeof state) => R) =>
|
||||
selector ? selector(state) : state,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@/hooks/useTranslation', () => ({
|
||||
useTranslation: () => (key: string) => key,
|
||||
}));
|
||||
@@ -70,22 +80,32 @@ describe('SectionInfo notch mask', () => {
|
||||
});
|
||||
|
||||
describe('SectionInfo contrast against the page (#4901)', () => {
|
||||
// A light-mode PDF shown under a dark theme keeps its white page, but the
|
||||
// running header text was themed for the dark UI (neutral-content, light)
|
||||
// and became unreadable on the white page. mix-blend-mode: difference makes
|
||||
// the text invert against whatever is actually behind it (the page or the
|
||||
// themed margin), so it stays legible over any background.
|
||||
it('blends the section title against the background in non-eink mode', () => {
|
||||
// A light-mode PDF shown under a dark theme keeps its white page, so the
|
||||
// running header blends against the real backdrop (text-white/75 +
|
||||
// mix-blend-difference) to stay legible on any background. Reflowable books
|
||||
// theme their own page to the UI, so the header uses plain base-content text
|
||||
// instead of the blend.
|
||||
it('blends the section title over a fixed-layout page in non-eink mode', () => {
|
||||
currentBookData = { isFixedLayout: true };
|
||||
const { container } = render(<SectionInfo {...baseProps} isEink={false} />);
|
||||
const info = container.querySelector('.sectioninfo') as HTMLElement;
|
||||
|
||||
expect(info.classList.contains('mix-blend-difference')).toBe(true);
|
||||
expect(info.classList.contains('text-white/75')).toBe(true);
|
||||
// The theme-fixed neutral color is what made it unreadable on a white page.
|
||||
expect(info.classList.contains('text-neutral-content')).toBe(false);
|
||||
});
|
||||
|
||||
it('uses themed base-content text for reflowable books in non-eink mode', () => {
|
||||
currentBookData = { isFixedLayout: false };
|
||||
const { container } = render(<SectionInfo {...baseProps} isEink={false} />);
|
||||
const info = container.querySelector('.sectioninfo') as HTMLElement;
|
||||
|
||||
expect(info.classList.contains('mix-blend-difference')).toBe(false);
|
||||
expect(info.classList.contains('text-white/75')).toBe(false);
|
||||
expect(info.classList.contains('text-base-content')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not blend in eink mode (base-content text on the e-ink page)', () => {
|
||||
currentBookData = { isFixedLayout: true };
|
||||
const { container } = render(<SectionInfo {...baseProps} isEink={true} />);
|
||||
const info = container.querySelector('.sectioninfo') as HTMLElement;
|
||||
|
||||
|
||||
@@ -288,13 +288,35 @@ describe('ProgressBar — sticky progress bar', () => {
|
||||
});
|
||||
|
||||
describe('ProgressBar — contrast against the page (#4901)', () => {
|
||||
// A light-mode PDF under a dark theme keeps its white page while the footer
|
||||
// progress text stayed themed for the dark UI (neutral-content, light) and
|
||||
// became unreadable over the white page. Blending the text spans against the
|
||||
// real backdrop keeps the page number and remaining-pages text legible on
|
||||
// any background. StatusInfo (clock/battery) is intentionally left alone — it
|
||||
// manages its own blend against the battery glyph.
|
||||
it('blends the progress and remaining text against the background in non-eink mode', () => {
|
||||
// A light-mode PDF under a dark theme keeps its white page, so the footer
|
||||
// progress/remaining text blends against the real backdrop (text-white/75 +
|
||||
// mix-blend-difference) to stay legible over the white page. Reflowable books
|
||||
// theme their own page to the UI, so the footer uses plain base-content text
|
||||
// instead of the blend. StatusInfo (clock/battery) is intentionally left
|
||||
// alone -- it manages its own blend against the battery glyph.
|
||||
it('blends the progress and remaining text over a fixed-layout page in non-eink mode', () => {
|
||||
currentViewSettings = {
|
||||
...baseSettings,
|
||||
isEink: false,
|
||||
showRemainingPages: true,
|
||||
showRemainingTime: false,
|
||||
progressInfoMode: 'all',
|
||||
} as ViewSettings;
|
||||
currentProgress = makeProgress(2, 5);
|
||||
currentBookData = { isFixedLayout: true };
|
||||
currentRenderer = { page: 1, pages: 4 };
|
||||
|
||||
const { container } = renderProgressBar();
|
||||
|
||||
const progress = container.querySelector('.progress-info') as HTMLElement;
|
||||
const remaining = container.querySelector('.remaining-info') as HTMLElement;
|
||||
expect(progress.classList.contains('mix-blend-difference')).toBe(true);
|
||||
expect(progress.classList.contains('text-white/75')).toBe(true);
|
||||
expect(remaining.classList.contains('mix-blend-difference')).toBe(true);
|
||||
expect(remaining.classList.contains('text-white/75')).toBe(true);
|
||||
});
|
||||
|
||||
it('uses themed base-content text for reflowable books in non-eink mode', () => {
|
||||
currentViewSettings = {
|
||||
...baseSettings,
|
||||
isEink: false,
|
||||
@@ -310,10 +332,10 @@ describe('ProgressBar — contrast against the page (#4901)', () => {
|
||||
|
||||
const progress = container.querySelector('.progress-info') as HTMLElement;
|
||||
const remaining = container.querySelector('.remaining-info') as HTMLElement;
|
||||
expect(progress.classList.contains('mix-blend-difference')).toBe(true);
|
||||
expect(progress.classList.contains('text-white/75')).toBe(true);
|
||||
expect(remaining.classList.contains('mix-blend-difference')).toBe(true);
|
||||
expect(remaining.classList.contains('text-white/75')).toBe(true);
|
||||
expect(progress.classList.contains('mix-blend-difference')).toBe(false);
|
||||
expect(progress.classList.contains('text-base-content')).toBe(true);
|
||||
expect(remaining.classList.contains('mix-blend-difference')).toBe(false);
|
||||
expect(remaining.classList.contains('text-base-content')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not blend in eink mode', () => {
|
||||
|
||||
@@ -141,7 +141,7 @@ const NowPlayingBar = ({ isSelectMode }: NowPlayingBarProps) => {
|
||||
aria-label={`${_('Open Book')}: ${title}`}
|
||||
className={clsx(
|
||||
'not-eink:bg-base-300 eink-bordered flex items-center gap-2 rounded-full shadow-lg',
|
||||
'h-14 max-w-[calc(100vw-1rem)] cursor-pointer px-2',
|
||||
'h-14 max-w-[calc(100vw-2rem)] cursor-pointer px-2',
|
||||
'focus-visible:ring-primary focus-visible:ring-2 focus-visible:outline-none',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -781,6 +781,7 @@ const FoliateViewer: React.FC<{
|
||||
const applyMarginAndGap = () => {
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const viewState = getViewState(bookKey);
|
||||
const bookData = getBookData(bookKey);
|
||||
const viewInsets = getViewInsets(viewSettings);
|
||||
const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder;
|
||||
const showDoubleBorderHeader = showDoubleBorder && viewSettings.showHeader;
|
||||
@@ -814,7 +815,7 @@ const FoliateViewer: React.FC<{
|
||||
const scrollBottom = footerVisible
|
||||
? Math.max(footerBarHeight, miniPlayerClearance)
|
||||
: miniPlayerClearance;
|
||||
setScrollMargins({ top: scrollTop, bottom: scrollBottom });
|
||||
setScrollMargins({ top: bookData?.isFixedLayout ? 0 : scrollTop, bottom: scrollBottom });
|
||||
} else {
|
||||
setScrollMargins({ top: 0, bottom: 0 });
|
||||
}
|
||||
|
||||
@@ -60,8 +60,6 @@ const HintInfo: React.FC<SectionInfoProps> = ({
|
||||
};
|
||||
}, [hintMessage]);
|
||||
|
||||
if (!hintMessage) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -100,8 +98,8 @@ const HintInfo: React.FC<SectionInfoProps> = ({
|
||||
>
|
||||
<h2
|
||||
className={clsx(
|
||||
'text-center font-sans',
|
||||
isEink ? 'text-sm font-normal' : 'text-neutral-content text-xs font-light',
|
||||
'text-center font-sans line-clamp-1',
|
||||
isEink ? 'text-sm font-normal' : 'text-base-content text-xs font-light',
|
||||
)}
|
||||
>
|
||||
{hintMessage || ''}
|
||||
|
||||
@@ -287,9 +287,9 @@ const ProgressBar: React.FC<ProgressBarProps> = ({
|
||||
'remaining-info whitespace-nowrap text-start',
|
||||
!stickyBarActive && 'flex-1',
|
||||
showStatusInfo && 'overflow-hidden',
|
||||
// Keep the text legible on any backdrop (e.g. a light PDF page
|
||||
// under a dark theme, #4901); blend it against what's behind.
|
||||
!isEink && 'text-white/75 mix-blend-difference',
|
||||
bookData?.isFixedLayout && !isEink
|
||||
? 'text-white/75 mix-blend-difference'
|
||||
: 'text-base-content',
|
||||
)}
|
||||
>
|
||||
{viewSettings.showRemainingTime ? (
|
||||
@@ -349,9 +349,9 @@ const ProgressBar: React.FC<ProgressBarProps> = ({
|
||||
className={clsx(
|
||||
'progress-info items-center overflow-hidden whitespace-nowrap text-end tabular-nums',
|
||||
!stickyBarActive && 'flex-1',
|
||||
// Keep the page number legible on any backdrop (e.g. a light PDF
|
||||
// page under a dark theme, #4901); blend it against what's behind.
|
||||
!isEink && 'text-white/75 mix-blend-difference',
|
||||
bookData?.isFixedLayout && !isEink
|
||||
? 'text-white/75 mix-blend-difference'
|
||||
: 'text-base-content',
|
||||
)}
|
||||
>
|
||||
{(progressBarMode === 'all' || progressBarMode.includes('progress')) && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
|
||||
interface SectionInfoProps {
|
||||
bookKey: string;
|
||||
@@ -34,7 +35,9 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
const { appService } = useEnv();
|
||||
const { hoveredBookKey, getView, getViewSettings, setHoveredBookKey } = useReaderStore();
|
||||
const { systemUIVisible, statusBarHeight } = useThemeStore();
|
||||
const getBookData = useBookDataStore((s) => s.getBookData);
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const bookData = getBookData(bookKey);
|
||||
const topInset = Math.max(
|
||||
gridInsets.top,
|
||||
appService?.isAndroidApp && systemUIVisible ? statusBarHeight / 2 : 0,
|
||||
@@ -75,13 +78,11 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
<div
|
||||
className={clsx(
|
||||
'sectioninfo absolute flex items-center overflow-hidden font-sans',
|
||||
// A light-mode PDF stays white under a dark theme, so the themed
|
||||
// neutral-content title was unreadable on the page (#4901). Blend the
|
||||
// text against whatever is actually behind it (page or margin) so it
|
||||
// stays legible on any background. text-white/75 matches the previous
|
||||
// neutral-content brightness over the dark theme, so nothing changes
|
||||
// for reflowable books. E-ink keeps its plain base-content text.
|
||||
isEink ? 'text-sm font-normal' : 'text-white/75 mix-blend-difference text-xs font-light',
|
||||
isEink
|
||||
? 'text-sm font-normal'
|
||||
: bookData?.isFixedLayout
|
||||
? 'text-white/75 mix-blend-difference text-xs font-light'
|
||||
: 'text-base-content text-xs font-light',
|
||||
isVertical ? 'writing-vertical-rl max-h-[85%]' : 'top-0',
|
||||
)}
|
||||
role='none'
|
||||
|
||||
@@ -28,7 +28,7 @@ const StatusInfo: React.FC<StatusInfoProps> = ({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'status-bar flex shrink-0 items-center gap-2 whitespace-nowrap tabular-nums',
|
||||
'status-bar flex shrink-0 items-center gap-2 whitespace-nowrap tabular-nums text-base-content',
|
||||
isVertical ? 'my-auto' : 'flex-row',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -614,7 +614,9 @@ const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRese
|
||||
value={showHeader && !isVertical ? marginTopPx : compactMarginTopPx}
|
||||
onChange={showHeader && !isVertical ? setMarginTopPx : setCompactMarginTopPx}
|
||||
min={
|
||||
showHeader && !isVertical ? Math.max(0, Math.round((16 - gridInsets.top) / 4) * 4) : 0
|
||||
showHeader && !isVertical
|
||||
? Math.max(0, Math.round((16 - gridInsets.top) / 4) * 4) - gridInsets.top
|
||||
: 0
|
||||
}
|
||||
max={88}
|
||||
step={4}
|
||||
@@ -625,7 +627,7 @@ const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRese
|
||||
onChange={showFooter && !isVertical ? setMarginBottomPx : setCompactMarginBottomPx}
|
||||
min={
|
||||
showFooter && !isVertical
|
||||
? Math.max(0, Math.round((16 - gridInsets.bottom) / 4) * 4)
|
||||
? Math.max(0, Math.round((16 - gridInsets.bottom) / 4) * 4) - gridInsets.bottom
|
||||
: 0
|
||||
}
|
||||
max={88}
|
||||
|
||||
Reference in New Issue
Block a user