Skip to content

Commit 9ae6dd9

Browse files
committed
Fix epub use theme and saved location
1 parent 737cf5c commit 9ae6dd9

File tree

4 files changed

+150
-149
lines changed

4 files changed

+150
-149
lines changed

src/app/api/nlp/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import nlp from 'compromise';
33

4-
const MAX_BLOCK_LENGTH = 450;
4+
const MAX_BLOCK_LENGTH = 350;
55

66
const preprocessSentenceForAudio = (text: string): string => {
77
return text

src/components/EPUBViewer.tsx

Lines changed: 30 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -5,84 +5,18 @@ import { useParams } from 'next/navigation';
55
import dynamic from 'next/dynamic';
66
import { useEPUB } from '@/contexts/EPUBContext';
77
import { useTTS } from '@/contexts/TTSContext';
8+
import { useConfig } from '@/contexts/ConfigContext';
89
import { DocumentSkeleton } from '@/components/DocumentSkeleton';
910
import TTSPlayer from '@/components/player/TTSPlayer';
1011
import { setLastDocumentLocation } from '@/utils/indexedDB';
1112
import type { Rendition, Book, NavItem } from 'epubjs';
12-
import { ReactReaderStyle, type IReactReaderStyle } from 'react-reader';
13-
import { useConfig } from '@/contexts/ConfigContext';
13+
import { useEPUBTheme, getThemeStyles } from '@/hooks/useEPUBTheme';
1414

1515
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
1616
ssr: false,
1717
loading: () => <DocumentSkeleton />
1818
});
1919

20-
const getThemeStyles = (): IReactReaderStyle => {
21-
const baseStyle = {
22-
...ReactReaderStyle,
23-
readerArea: {
24-
...ReactReaderStyle.readerArea,
25-
transition: undefined,
26-
}
27-
};
28-
29-
const colors = {
30-
background: getComputedStyle(document.documentElement).getPropertyValue('--background'),
31-
foreground: getComputedStyle(document.documentElement).getPropertyValue('--foreground'),
32-
base: getComputedStyle(document.documentElement).getPropertyValue('--base'),
33-
offbase: getComputedStyle(document.documentElement).getPropertyValue('--offbase'),
34-
muted: getComputedStyle(document.documentElement).getPropertyValue('--muted'),
35-
};
36-
37-
return {
38-
...baseStyle,
39-
arrow: {
40-
...baseStyle.arrow,
41-
color: colors.foreground,
42-
},
43-
arrowHover: {
44-
...baseStyle.arrowHover,
45-
color: colors.muted,
46-
},
47-
readerArea: {
48-
...baseStyle.readerArea,
49-
backgroundColor: colors.base,
50-
},
51-
titleArea: {
52-
...baseStyle.titleArea,
53-
color: colors.foreground,
54-
display: 'none',
55-
},
56-
tocArea: {
57-
...baseStyle.tocArea,
58-
background: colors.base,
59-
},
60-
tocButtonExpanded: {
61-
...baseStyle.tocButtonExpanded,
62-
background: colors.offbase,
63-
},
64-
tocButtonBar: {
65-
...baseStyle.tocButtonBar,
66-
background: colors.muted,
67-
},
68-
tocButton: {
69-
...baseStyle.tocButton,
70-
color: colors.muted,
71-
},
72-
tocAreaButton: {
73-
...baseStyle.tocAreaButton,
74-
color: colors.muted,
75-
backgroundColor: colors.offbase,
76-
padding: '0.25rem',
77-
paddingLeft: '0.5rem',
78-
paddingRight: '0.5rem',
79-
marginBottom: '0.25rem',
80-
borderRadius: '0.25rem',
81-
borderColor: 'transparent',
82-
},
83-
};
84-
};
85-
8620
interface EPUBViewerProps {
8721
className?: string;
8822
}
@@ -96,11 +30,11 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
9630
const rendition = useRef<Rendition | undefined>(undefined);
9731
const toc = useRef<NavItem[]>([]);
9832
const locationRef = useRef<string | number>(currDocPage);
99-
const [reloadKey, setReloadKey] = useState(0);
10033
const [initialPrevLocLoad, setInitialPrevLocLoad] = useState(false);
34+
const { updateTheme } = useEPUBTheme(epubTheme, rendition.current);
10135

10236
const handleLocationChanged = useCallback((location: string | number, initial = false) => {
103-
if (!bookRef.current?.isOpen) return;
37+
if (!bookRef.current?.isOpen || !rendition.current) return;
10438
// Handle special 'next' and 'prev' cases, which
10539
if (location === 'next' && rendition.current) {
10640
rendition.current.next();
@@ -111,32 +45,32 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
11145
return;
11246
}
11347

114-
if (bookRef.current && rendition.current) {
115-
const { displayed, href } = rendition.current.location.start;
116-
const chapter = toc.current.find((item) => item.href === href);
117-
118-
console.log('Displayed:', displayed, 'Chapter:', chapter);
119-
120-
if (locationRef.current !== 1) {
121-
// Save the location to IndexedDB
122-
if (id) {
123-
console.log('Saving location:', location);
124-
setLastDocumentLocation(id as string, location.toString());
125-
}
126-
}
127-
128-
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
129-
130-
// Add a small delay for initial load to ensure rendition is ready
131-
if (initial) {
132-
rendition.current.display(location.toString()).then(() => {
133-
setInitialPrevLocLoad(true);
134-
});
135-
} else {
136-
locationRef.current = location;
137-
extractPageText(bookRef.current, rendition.current);
48+
49+
const { displayed, href } = rendition.current.location.start;
50+
const chapter = toc.current.find((item) => item.href === href);
51+
52+
console.log('Displayed:', displayed, 'Chapter:', chapter);
53+
54+
if (locationRef.current !== 1) {
55+
// Save the location to IndexedDB
56+
if (id) {
57+
console.log('Saving location:', location);
58+
setLastDocumentLocation(id as string, location.toString());
13859
}
13960
}
61+
62+
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
63+
64+
// Add a small delay for initial load to ensure rendition is ready
65+
if (initial) {
66+
rendition.current.display(location.toString()).then(() => {
67+
setInitialPrevLocLoad(true);
68+
});
69+
} else {
70+
locationRef.current = location;
71+
extractPageText(bookRef.current, rendition.current);
72+
}
73+
14074
}, [id, setEPUBPageInChapter, extractPageText]);
14175

14276
// Replace the debounced text extraction with a proper implementation using useMemo
@@ -174,46 +108,6 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
174108
}
175109
}, [extractPageText, debouncedExtractText, initialPrevLocLoad]);
176110

177-
const updateTheme = useCallback((rendition: Rendition) => {
178-
if (!epubTheme) return; // Only apply theme if enabled
179-
180-
const colors = {
181-
foreground: getComputedStyle(document.documentElement).getPropertyValue('--foreground'),
182-
base: getComputedStyle(document.documentElement).getPropertyValue('--base'),
183-
};
184-
185-
rendition.themes.override('color', colors.foreground);
186-
rendition.themes.override('background', colors.base);
187-
}, [epubTheme]);
188-
189-
// Watch for theme changes
190-
useEffect(() => {
191-
if (!epubTheme || !bookRef.current?.isOpen || !rendition.current) return;
192-
193-
const observer = new MutationObserver((mutations) => {
194-
mutations.forEach((mutation) => {
195-
if (mutation.attributeName === 'class') {
196-
if (epubTheme) {
197-
setReloadKey(prev => prev + 1);
198-
}
199-
}
200-
});
201-
});
202-
203-
observer.observe(document.documentElement, {
204-
attributes: true,
205-
attributeFilter: ['class']
206-
});
207-
208-
return () => observer.disconnect();
209-
}, [epubTheme]);
210-
211-
// Watch for epubTheme changes
212-
useEffect(() => {
213-
if (!epubTheme || !bookRef.current?.isOpen || !rendition.current) return;
214-
setReloadKey(prev => prev + 1);
215-
}, [epubTheme]);
216-
217111
// Register the location change handler
218112
useEffect(() => {
219113
registerLocationChangeHandler(handleLocationChanged);
@@ -230,7 +124,7 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
230124
</div>
231125
<div className="flex-1 -mt-16 pt-16">
232126
<ReactReader
233-
key={reloadKey} // Add this line to force remount
127+
key={'epub-reader'}
234128
location={locationRef.current}
235129
locationChanged={handleLocationChanged}
236130
url={currDocData}
@@ -239,10 +133,9 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
239133
showToc={true}
240134
readerStyles={epubTheme && getThemeStyles() || undefined}
241135
getRendition={(_rendition: Rendition) => {
242-
updateTheme(_rendition);
243-
244136
bookRef.current = _rendition.book;
245137
rendition.current = _rendition;
138+
updateTheme();
246139
}}
247140
/>
248141
</div>

src/contexts/EPUBContext.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ interface EPUBContextType {
2121
currDocText: string | undefined;
2222
setCurrentDocument: (id: string) => Promise<void>;
2323
clearCurrDoc: () => void;
24-
onDocumentLoadSuccess: ({ numPages }: { numPages: number }) => void;
2524
extractPageText: (book: Book, rendition: Rendition) => Promise<string>;
2625
}
2726

@@ -35,14 +34,6 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
3534
const [currDocName, setCurrDocName] = useState<string>();
3635
const [currDocText, setCurrDocText] = useState<string>();
3736

38-
/**
39-
* Handles successful document load
40-
*/
41-
const onDocumentLoadSuccess = useCallback(({ numPages }: { numPages: number }) => {
42-
console.log('EPUB loaded:', numPages);
43-
setCurrDocPages(numPages);
44-
}, [setCurrDocPages]);
45-
4637
/**
4738
* Clears the current document state
4839
*/
@@ -106,7 +97,6 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
10697
// Context value memoization
10798
const contextValue = useMemo(
10899
() => ({
109-
onDocumentLoadSuccess,
110100
setCurrentDocument,
111101
currDocData,
112102
currDocName,
@@ -117,7 +107,6 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
117107
extractPageText,
118108
}),
119109
[
120-
onDocumentLoadSuccess,
121110
setCurrentDocument,
122111
currDocData,
123112
currDocName,

src/hooks/useEPUBTheme.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { useCallback, useEffect } from 'react';
2+
import { Rendition } from 'epubjs';
3+
import { ReactReaderStyle, IReactReaderStyle } from 'react-reader';
4+
5+
export const getThemeStyles = (): IReactReaderStyle => {
6+
const baseStyle = {
7+
...ReactReaderStyle,
8+
readerArea: {
9+
...ReactReaderStyle.readerArea,
10+
transition: undefined,
11+
}
12+
};
13+
14+
const colors = {
15+
background: getComputedStyle(document.documentElement).getPropertyValue('--background'),
16+
foreground: getComputedStyle(document.documentElement).getPropertyValue('--foreground'),
17+
base: getComputedStyle(document.documentElement).getPropertyValue('--base'),
18+
offbase: getComputedStyle(document.documentElement).getPropertyValue('--offbase'),
19+
muted: getComputedStyle(document.documentElement).getPropertyValue('--muted'),
20+
};
21+
22+
return {
23+
...baseStyle,
24+
arrow: {
25+
...baseStyle.arrow,
26+
color: colors.foreground,
27+
},
28+
arrowHover: {
29+
...baseStyle.arrowHover,
30+
color: colors.muted,
31+
},
32+
readerArea: {
33+
...baseStyle.readerArea,
34+
backgroundColor: colors.base,
35+
},
36+
titleArea: {
37+
...baseStyle.titleArea,
38+
color: colors.foreground,
39+
display: 'none',
40+
},
41+
tocArea: {
42+
...baseStyle.tocArea,
43+
background: colors.base,
44+
},
45+
tocButtonExpanded: {
46+
...baseStyle.tocButtonExpanded,
47+
background: colors.offbase,
48+
},
49+
tocButtonBar: {
50+
...baseStyle.tocButtonBar,
51+
background: colors.muted,
52+
},
53+
tocButton: {
54+
...baseStyle.tocButton,
55+
color: colors.muted,
56+
},
57+
tocAreaButton: {
58+
...baseStyle.tocAreaButton,
59+
color: colors.muted,
60+
backgroundColor: colors.offbase,
61+
padding: '0.25rem',
62+
paddingLeft: '0.5rem',
63+
paddingRight: '0.5rem',
64+
marginBottom: '0.25rem',
65+
borderRadius: '0.25rem',
66+
borderColor: 'transparent',
67+
},
68+
};
69+
};
70+
71+
export const useEPUBTheme = (epubTheme: boolean, rendition: Rendition | undefined) => {
72+
const updateTheme = useCallback(() => {
73+
if (!epubTheme || !rendition) return;
74+
75+
const colors = {
76+
foreground: getComputedStyle(document.documentElement).getPropertyValue('--foreground'),
77+
base: getComputedStyle(document.documentElement).getPropertyValue('--base'),
78+
};
79+
80+
// Register theme rules instead of using override
81+
rendition.themes.registerRules('theme-light', {
82+
'body': {
83+
'color': colors.foreground,
84+
'background-color': colors.base
85+
}
86+
});
87+
88+
// Select the theme to apply it
89+
rendition.themes.select('theme-light');
90+
}, [epubTheme, rendition]);
91+
92+
// Watch for theme changes
93+
useEffect(() => {
94+
if (!epubTheme || !rendition) return;
95+
96+
const observer = new MutationObserver((mutations) => {
97+
mutations.forEach((mutation) => {
98+
if (mutation.attributeName === 'class') {
99+
updateTheme();
100+
}
101+
});
102+
});
103+
104+
observer.observe(document.documentElement, {
105+
attributes: true,
106+
attributeFilter: ['class']
107+
});
108+
109+
return () => observer.disconnect();
110+
}, [epubTheme, rendition, updateTheme]);
111+
112+
// Watch for epubTheme changes
113+
useEffect(() => {
114+
if (!epubTheme || !rendition) return;
115+
updateTheme();
116+
}, [epubTheme, rendition, updateTheme]);
117+
118+
return { updateTheme };
119+
};

0 commit comments

Comments
 (0)