diff --git a/src/app/(app)/epub/[id]/layout.tsx b/src/app/(app)/epub/[id]/layout.tsx
index 701e1e7..2f41494 100644
--- a/src/app/(app)/epub/[id]/layout.tsx
+++ b/src/app/(app)/epub/[id]/layout.tsx
@@ -4,13 +4,12 @@ import type { ReactNode } from 'react';
import { ConfigProvider } from '@/contexts/ConfigContext';
import { TTSProvider } from '@/contexts/TTSContext';
-import { EPUBProvider } from '@/contexts/EPUBContext';
export default function EpubReaderLayout({ children }: { children: ReactNode }) {
return (
- {children}
+ {children}
);
diff --git a/src/app/(app)/epub/[id]/page.tsx b/src/app/(app)/epub/[id]/page.tsx
index b3ee043..5e6f784 100644
--- a/src/app/(app)/epub/[id]/page.tsx
+++ b/src/app/(app)/epub/[id]/page.tsx
@@ -3,7 +3,6 @@
import { useParams, useRouter } from "next/navigation";
import Link from 'next/link';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { useEPUB } from '@/contexts/EPUBContext';
import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton';
import { EPUBViewer } from '@/components/views/EPUBViewer';
import { DocumentSettings } from '@/components/documents/DocumentSettings';
@@ -20,12 +19,21 @@ import { resolveDocumentId } from '@/lib/client/dexie';
import { RateLimitBanner } from '@/components/auth/RateLimitBanner';
import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext';
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
+import { useEPUB } from './useEpubDocument';
export default function EPUBPage() {
const canExportAudiobook = useFeatureFlag('enableAudiobookExport');
const { id } = useParams();
const router = useRouter();
- const { setCurrentDocument, currDocName, clearCurrDoc, createFullAudioBook: createEPUBAudioBook, regenerateChapter: regenerateEPUBChapter, bookRef } = useEPUB();
+ const epubState = useEPUB();
+ const {
+ setCurrentDocument,
+ currDocName,
+ clearCurrDoc,
+ createFullAudioBook: createEPUBAudioBook,
+ regenerateChapter: regenerateEPUBChapter,
+ bookRef,
+ } = epubState;
const { stop } = useTTS();
const { isAtLimit } = useAuthRateLimit();
const [error, setError] = useState(null);
@@ -209,7 +217,7 @@ export default function EPUBPage() {
) : (
-
+
)}
diff --git a/src/contexts/EPUBContext.tsx b/src/app/(app)/epub/[id]/useEpubDocument.ts
similarity index 97%
rename from src/contexts/EPUBContext.tsx
rename to src/app/(app)/epub/[id]/useEpubDocument.ts
index 660f644..e3e7546 100644
--- a/src/contexts/EPUBContext.tsx
+++ b/src/app/(app)/epub/[id]/useEpubDocument.ts
@@ -1,10 +1,7 @@
'use client';
import {
- createContext,
- useContext,
useState,
- ReactNode,
useCallback,
useMemo,
useRef,
@@ -36,7 +33,7 @@ import {
type EpubCanonicalWordToken,
} from '@/lib/client/epub/epub-word-highlight';
import { useParams } from 'next/navigation';
-import { useConfig } from './ConfigContext';
+import { useConfig } from '@/contexts/ConfigContext';
import type {
EpubRenderedLocationWalker,
TTSSentenceAlignment,
@@ -47,7 +44,7 @@ import type { AudiobookGenerationSettings, TTSSegmentLocator } from '@/types/cli
import { isStableEpubLocator } from '@/types/client';
import type { CanonicalTtsSegment } from '@/lib/shared/tts-segment-plan';
-interface EPUBContextType {
+export interface EPUBState {
currDocData: ArrayBuffer | undefined;
currDocName: string | undefined;
currDocPages: number | undefined;
@@ -90,8 +87,6 @@ interface EPUBContextType {
clearWordHighlights: () => void;
}
-const EPUBContext = createContext(undefined);
-
const EPUB_CONTINUATION_CHARS = 5000;
const stepToNextNode = (node: Node | null, root: Node): Node | null => {
@@ -526,12 +521,9 @@ const resolveVisibleSegmentRange = (
/**
- * Provider component for EPUB functionality
- * Manages the state and operations for EPUB document handling
- * @param {Object} props - Component props
- * @param {ReactNode} props.children - Child components to be wrapped by the provider
+ * Route-local EPUB reader hook.
*/
-export function EPUBProvider({ children }: { children: ReactNode }) {
+export function useEPUB(): EPUBState {
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages, stop, skipToLocation, setIsEPUB } = useTTS();
const { authEnabled } = useAuthConfig();
const { id } = useParams();
@@ -1141,8 +1133,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
- // Context value memoization
- const contextValue = useMemo(
+ return useMemo(
() => ({
setCurrentDocument,
currDocData,
@@ -1190,23 +1181,4 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
clearWordHighlights,
]
);
-
- return (
-
- {children}
-
- );
-}
-
-/**
- * Custom hook to consume the EPUB context
- * @returns {EPUBContextType} The EPUB context value
- * @throws {Error} When used outside of EPUBProvider
- */
-export function useEPUB() {
- const context = useContext(EPUBContext);
- if (context === undefined) {
- throw new Error('useEPUB must be used within an EPUBProvider');
- }
- return context;
}
diff --git a/src/components/views/EPUBViewer.tsx b/src/components/views/EPUBViewer.tsx
index f269eab..37c57c3 100644
--- a/src/components/views/EPUBViewer.tsx
+++ b/src/components/views/EPUBViewer.tsx
@@ -2,13 +2,13 @@
import { useEffect, useRef, useCallback, useState } from 'react';
import dynamic from 'next/dynamic';
-import { useEPUB } from '@/contexts/EPUBContext';
import { useTTS } from '@/contexts/TTSContext';
import { useConfig } from '@/contexts/ConfigContext';
import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton';
import { useEPUBTheme, getThemeStyles } from '@/hooks/epub/useEPUBTheme';
import { useEPUBResize } from '@/hooks/epub/useEPUBResize';
import { DotsVerticalIcon, ChevronLeftIcon, ChevronRightIcon } from '@/components/icons/Icons';
+import type { EPUBState } from '@/app/(app)/epub/[id]/useEpubDocument';
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
ssr: false,
@@ -17,9 +17,29 @@ const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactRe
interface EPUBViewerProps {
className?: string;
+ epubState: Pick<
+ EPUBState,
+ | 'currDocData'
+ | 'currDocName'
+ | 'currDocPage'
+ | 'currDocPages'
+ | 'locationRef'
+ | 'handleLocationChanged'
+ | 'bookRef'
+ | 'renditionRef'
+ | 'tocRef'
+ | 'setRendition'
+ | 'extractPageText'
+ | 'highlightSegment'
+ | 'clearHighlights'
+ | 'highlightWordIndex'
+ | 'clearWordHighlights'
+ | 'walkUpcomingRenderedLocations'
+ | 'resolveEpubLocator'
+ >;
}
-export function EPUBViewer({ className = '' }: EPUBViewerProps) {
+export function EPUBViewer({ className = '', epubState }: EPUBViewerProps) {
const [isTocOpen, setIsTocOpen] = useState(false);
const {
currDocData,
@@ -39,7 +59,7 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
clearWordHighlights,
walkUpcomingRenderedLocations,
resolveEpubLocator,
- } = useEPUB();
+ } = epubState;
const {
registerLocationChangeHandler,
registerEpubLocationWalker,