diff --git a/ui/homepage/app/docs/[collection]/[version]/DocReader.tsx b/ui/homepage/app/docs/[collection]/[version]/DocReader.tsx index e7c3bfa..83c2e7e 100644 --- a/ui/homepage/app/docs/[collection]/[version]/DocReader.tsx +++ b/ui/homepage/app/docs/[collection]/[version]/DocReader.tsx @@ -1,6 +1,8 @@ 'use client' import { + ComponentProps, + ReactElement, forwardRef, useCallback, useEffect, @@ -12,7 +14,6 @@ import { import DOMPurify from 'dompurify' import type { DocViewOption } from './DocViewSection' -import type { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api' export interface DocTocItem { id: string @@ -42,6 +43,10 @@ interface PdfModule { Page: typeof import('react-pdf').Page } +type DocumentComponent = PdfModule['Document'] +type OnLoadSuccess = NonNullable['onLoadSuccess']> +type PdfDocument = Parameters[0] + const HTML_ENCODINGS = ['utf-8', 'utf-16le', 'utf-16be', 'gb18030'] const slugify = (value: string) => @@ -88,7 +93,17 @@ type PdfOutline = { items?: PdfOutline[] } -const buildPdfToc = async (pdf: PDFDocumentProxy): Promise => { +type PdfRef = { num: number; gen: number } + +const isPdfRef = (value: unknown): value is PdfRef => + typeof value === 'object' && + value !== null && + 'num' in value && + 'gen' in value && + typeof (value as { num: unknown }).num === 'number' && + typeof (value as { gen: unknown }).gen === 'number' + +const buildPdfToc = async (pdf: PdfDocument): Promise => { const outline = ((await pdf.getOutline()) as PdfOutline[]) ?? [] const flattenOutline = async (items: PdfOutline[] | undefined, level = 1): Promise => { @@ -101,9 +116,17 @@ const buildPdfToc = async (pdf: PDFDocumentProxy): Promise => { let pageNumber: number | undefined if (item.dest) { try { - const destination = await pdf.getDestination(item.dest) - const ref = Array.isArray(destination) ? destination[0] : destination - if (typeof ref === 'object' && ref !== null && 'num' in ref) { + let ref: unknown + if (typeof item.dest === 'string') { + const destination = await pdf.getDestination(item.dest) + ref = Array.isArray(destination) ? destination[0] : destination + } else if (Array.isArray(item.dest)) { + const [first] = item.dest + ref = first + } else { + ref = item.dest + } + if (isPdfRef(ref)) { pageNumber = (await pdf.getPageIndex(ref)) + 1 } else if (typeof ref === 'number') { pageNumber = ref + 1 @@ -157,7 +180,7 @@ const DocReader = forwardRef( const [pdfModule, setPdfModule] = useState(null) const [numPages, setNumPages] = useState(0) const [containerWidth, setContainerWidth] = useState(null) - const pdfDocumentRef = useRef(null) + const pdfDocumentRef = useRef(null) const pageRefs = useRef>(new Map()) const tocItemsRef = useRef([]) @@ -256,35 +279,37 @@ const DocReader = forwardRef( } }, [view]) - const handlePdfLoadSuccess = useCallback( - async (pdf: PDFDocumentProxy) => { + const handlePdfLoadSuccess = useCallback( + (pdf) => { pdfDocumentRef.current = pdf setNumPages(pdf.numPages) setLoadingState('idle') - try { - const tocItems = await buildPdfToc(pdf) - tocItemsRef.current = tocItems - onTocChange(tocItems) - if (tocItems.length) { - onActiveAnchorChange(tocItems[0].id) + void (async () => { + try { + const tocItems = await buildPdfToc(pdf) + tocItemsRef.current = tocItems + onTocChange(tocItems) + if (tocItems.length) { + onActiveAnchorChange(tocItems[0].id) + } + } catch { + const fallback: DocTocItem[] = [] + for (let page = 1; page <= pdf.numPages; page += 1) { + fallback.push({ + id: `pdf-page-${page}`, + title: `Page ${page}`, + level: 2, + type: 'pdf', + pageNumber: page, + }) + } + tocItemsRef.current = fallback + onTocChange(fallback) + if (fallback.length) { + onActiveAnchorChange(fallback[0].id) + } } - } catch { - const fallback: DocTocItem[] = [] - for (let page = 1; page <= pdf.numPages; page += 1) { - fallback.push({ - id: `pdf-page-${page}`, - title: `Page ${page}`, - level: 2, - type: 'pdf', - pageNumber: page, - }) - } - tocItemsRef.current = fallback - onTocChange(fallback) - if (fallback.length) { - onActiveAnchorChange(fallback[0].id) - } - } + })() }, [onTocChange, onActiveAnchorChange], ) @@ -478,7 +503,7 @@ const DocReader = forwardRef( ) } - let content: JSX.Element | null = null + let content: ReactElement | null = null if (!view) { content = (