Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | "use client";
import { useEffect, useState } from "react";
import { pdfjs } from "react-pdf";
type PDFDocumentProxy = pdfjs.PDFDocumentProxy;
export function usePdfSearch(pageRefs: React.RefObject<Map<number, HTMLDivElement>>) {
const [pdfDoc, setPdfDoc] = useState<PDFDocumentProxy | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const [matchPages, setMatchPages] = useState<number[]>([]);
const [matchIndex, setMatchIndex] = useState(0);
const [isSearching, setIsSearching] = useState(false);
const [searched, setSearched] = useState(false);
const currentMatchPage = matchPages[matchIndex] ?? null;
useEffect(() => {
if (currentMatchPage === null) return;
const el = pageRefs.current?.get(currentMatchPage);
if (el) {
el.scrollIntoView({ behavior: "smooth", block: "start" });
}
}, [currentMatchPage, matchIndex, pageRefs]);
async function runSearch() {
if (!pdfDoc || !searchQuery.trim()) return;
setIsSearching(true);
setMatchPages([]);
setMatchIndex(0);
setSearched(false);
const query = searchQuery.trim().toLowerCase();
const found: number[] = [];
for (let i = 1; i <= pdfDoc.numPages; i++) {
const page = await pdfDoc.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map((item) => ("str" in item ? item.str : ""))
.join(" ")
.toLowerCase();
if (pageText.includes(query)) {
found.push(i);
}
}
setMatchPages(found);
setMatchIndex(0);
setSearched(true);
setIsSearching(false);
}
function navigateNext() {
if (searched && matchPages.length > 0) {
setMatchIndex((prev) => (prev + 1) % matchPages.length);
} else {
void runSearch();
}
}
function handleQueryChange(e: React.ChangeEvent<HTMLInputElement>) {
setSearchQuery(e.target.value);
setMatchPages([]);
setMatchIndex(0);
setSearched(false);
}
function handleClearQuery() {
setSearchQuery("");
setMatchPages([]);
setMatchIndex(0);
setSearched(false);
}
function highlightText(text: string) {
if (!searchQuery.trim()) return text;
const escaped = searchQuery.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`(${escaped})`, "gi");
return text.replace(
regex,
`<mark class="bg-yellow-300 text-black rounded-sm">$1</mark>`,
);
}
return {
pdfDoc,
setPdfDoc,
searchQuery,
matchPages,
matchIndex,
isSearching,
searched,
navigateNext,
handleQueryChange,
handleClearQuery,
highlightText,
};
}
|