diff --git a/src/app/(app)/pdf/[id]/layout.tsx b/src/app/(app)/pdf/[id]/layout.tsx
index f5ac472..01be8bf 100644
--- a/src/app/(app)/pdf/[id]/layout.tsx
+++ b/src/app/(app)/pdf/[id]/layout.tsx
@@ -4,14 +4,11 @@ import type { ReactNode } from 'react';
import { ConfigProvider } from '@/contexts/ConfigContext';
import { TTSProvider } from '@/contexts/TTSContext';
-import { PDFProvider } from '@/contexts/PDFContext';
export default function PdfReaderLayout({ children }: { children: ReactNode }) {
return (
-
- {children}
-
+ {children}
);
}
diff --git a/src/app/(app)/pdf/[id]/page.tsx b/src/app/(app)/pdf/[id]/page.tsx
index 8b4d154..1ecd6aa 100644
--- a/src/app/(app)/pdf/[id]/page.tsx
+++ b/src/app/(app)/pdf/[id]/page.tsx
@@ -1,7 +1,6 @@
'use client';
import dynamic from 'next/dynamic';
-import { usePDF } from '@/contexts/PDFContext';
import { useParams, useRouter } from 'next/navigation';
import Link from 'next/link';
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -20,6 +19,7 @@ import { resolveDocumentId } from '@/lib/client/dexie';
import { RateLimitBanner } from '@/components/auth/RateLimitBanner';
import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext';
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
+import { usePdfDocument } from './usePdfDocument';
// Dynamic import for client-side rendering only
const PDFViewer = dynamic(
@@ -34,7 +34,16 @@ export default function PDFViewerPage() {
const canExportAudiobook = useFeatureFlag('enableAudiobookExport');
const { id } = useParams();
const router = useRouter();
- const { setCurrentDocument, currDocName, clearCurrDoc, currDocPage, currDocPages, createFullAudioBook: createPDFAudioBook, regenerateChapter: regeneratePDFChapter } = usePDF();
+ const pdfState = usePdfDocument();
+ const {
+ setCurrentDocument,
+ currDocName,
+ clearCurrDoc,
+ currDocPage,
+ currDocPages,
+ createFullAudioBook: createPDFAudioBook,
+ regenerateChapter: regeneratePDFChapter,
+ } = pdfState;
const { stop } = useTTS();
const { isAtLimit } = useAuthRateLimit();
const [error, setError] = useState(null);
@@ -197,7 +206,7 @@ export default function PDFViewerPage() {
) : (
-
+
)}
{canExportAudiobook && (
diff --git a/src/contexts/PDFContext.tsx b/src/app/(app)/pdf/[id]/usePdfDocument.ts
similarity index 91%
rename from src/contexts/PDFContext.tsx
rename to src/app/(app)/pdf/[id]/usePdfDocument.ts
index 7496ebe..63418d1 100644
--- a/src/contexts/PDFContext.tsx
+++ b/src/app/(app)/pdf/[id]/usePdfDocument.ts
@@ -1,23 +1,14 @@
/**
- * PDF Context Provider
- *
- * This module provides a React context for managing PDF document functionality.
- * It handles document loading, text extraction, highlighting, and integration with TTS.
- *
- * Key features:
- * - PDF document management (add/remove/load)
- * - Text extraction and processing
- * - Text highlighting and navigation
- * - Document state management
+ * Route-local PDF document hook.
+ *
+ * This module owns PDF document loading, text extraction, highlighting, and
+ * audiobook integration for the `/pdf/[id]` route.
*/
'use client';
import {
- createContext,
- useContext,
useState,
- ReactNode,
useEffect,
useCallback,
useMemo,
@@ -50,9 +41,9 @@ import type { AudiobookGenerationSettings } from '@/types/client';
import { clampSegmentPreloadDepth } from '@/types/config';
/**
- * Interface defining all available methods and properties in the PDF context
+ * Interface defining all available methods and properties for the PDF route.
*/
-interface PDFContextType {
+export interface PdfDocumentState {
// Current document state
currDocId: string | undefined;
currDocData: ArrayBuffer | undefined;
@@ -93,22 +84,13 @@ interface PDFContextType {
isAudioCombining: boolean;
}
-// Create the context
-const PDFContext = createContext(undefined);
-
const EMPTY_TEXT_RETRY_DELAY_MS = 120;
const EMPTY_TEXT_MAX_RETRIES = 6;
/**
- * PDFProvider Component
- *
- * Main provider component that manages PDF state and functionality.
- * Handles document loading, text processing, and integration with TTS.
- *
- * @param {Object} props - Component props
- * @param {ReactNode} props.children - Child components to be wrapped by the provider
+ * Main PDF route hook.
*/
-export function PDFProvider({ children }: { children: ReactNode }) {
+export function usePdfDocument(): PdfDocumentState {
const {
setText: setTTSText,
stop,
@@ -507,8 +489,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
};
}, [registerVisualPageChangeHandler, currDocPages, pdfDocument]);
- // Context value memoization
- const contextValue = useMemo(
+ return useMemo(
() => ({
onDocumentLoadSuccess,
setCurrentDocument,
@@ -544,25 +525,4 @@ export function PDFProvider({ children }: { children: ReactNode }) {
isAudioCombining,
]
);
-
- return (
-
- {children}
-
- );
-}
-
-/**
- * Custom hook to consume the PDF context
- * Ensures the context is used within a provider
- *
- * @throws {Error} If used outside of PDFProvider
- * @returns {PDFContextType} The PDF context value containing all PDF-related functionality
- */
-export function usePDF() {
- const context = useContext(PDFContext);
- if (context === undefined) {
- throw new Error('usePDF must be used within a PDFProvider');
- }
- return context;
}
diff --git a/src/components/views/PDFViewer.tsx b/src/components/views/PDFViewer.tsx
index 805bb1c..01c980c 100644
--- a/src/components/views/PDFViewer.tsx
+++ b/src/components/views/PDFViewer.tsx
@@ -7,12 +7,25 @@ import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton';
import { useTTS } from '@/contexts/TTSContext';
-import { usePDF } from '@/contexts/PDFContext';
import { useConfig } from '@/contexts/ConfigContext';
import { usePDFResize } from '@/hooks/pdf/usePDFResize';
+import type { PdfDocumentState } from '@/app/(app)/pdf/[id]/usePdfDocument';
interface PDFViewerProps {
zoomLevel: number;
+ pdfState: Pick<
+ PdfDocumentState,
+ | 'highlightPattern'
+ | 'clearHighlights'
+ | 'clearWordHighlights'
+ | 'highlightWordIndex'
+ | 'onDocumentLoadSuccess'
+ | 'currDocId'
+ | 'currDocData'
+ | 'currDocPages'
+ | 'currDocText'
+ | 'currDocPage'
+ >;
}
interface PDFOnLinkClickArgs {
@@ -20,7 +33,7 @@ interface PDFOnLinkClickArgs {
dest?: Dest;
}
-export function PDFViewer({ zoomLevel }: PDFViewerProps) {
+export function PDFViewer({ zoomLevel, pdfState }: PDFViewerProps) {
const containerRef = useRef(null);
const [isPageRendering, setIsPageRendering] = useState(false);
const scaleRef = useRef(1);
@@ -43,7 +56,6 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
skipToLocation,
} = useTTS();
- // PDF context
const {
highlightPattern,
clearHighlights,
@@ -55,7 +67,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
currDocPages,
currDocText,
currDocPage,
- } = usePDF();
+ } = pdfState;
// IMPORTANT:
// - pdf.js may transfer/detach ArrayBuffers when sending them to its worker, so we must clone.