# Cross-Page Search Implementation for NaviDocs ## Agent 8 Task: Implement cross-page search functionality ### Overview This document describes the implementation of Apple Preview-style cross-page search functionality in NaviDocs DocumentView.vue. ### Changes Required #### 1. State Variables (Already Added) ```javascript const allPagesHitList = ref([]) // Stores all hits across all pages const isSearchingAllPages = ref(false) ``` #### 2. New Function: searchAllPages() ```javascript // Search all pages and build comprehensive hit list async function searchAllPages(query) { if (!pdfDoc || !query) return [] const allResults = [] const normalizedQuery = query.toLowerCase().trim() try { isSearchingAllPages.value = true for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) { const page = await pdfDoc.getPage(pageNum) const textContent = await page.getTextContent() const pageText = textContent.items.map(item => item.str).join(' ') const pageLowerText = pageText.toLowerCase() // Find all matches in this page let matchIndex = 0 let searchIndex = 0 while ((searchIndex = pageLowerText.indexOf(normalizedQuery, searchIndex)) !== -1) { // Extract snippet around the match const snippetStart = Math.max(0, searchIndex - 40) const snippetEnd = Math.min(pageText.length, searchIndex + normalizedQuery.length + 40) let snippet = pageText.substring(snippetStart, snippetEnd) // Add ellipsis if truncated if (snippetStart > 0) snippet = '...' + snippet if (snippetEnd < pageText.length) snippet = snippet + '...' allResults.push({ page: pageNum, matchIndex: matchIndex, snippet: snippet, textPosition: searchIndex }) matchIndex++ searchIndex += normalizedQuery.length } } } catch (err) { console.error('Error searching all pages:', err) } finally { isSearchingAllPages.value = false } return allResults } ``` #### 3. Update highlightSearchTerms() Replace the end of the function with: ```javascript // Update current page hit list (for scrolling within page) hitList.value = hits // If we have cross-page results, use those for total count and navigation if (allPagesHitList.value.length > 0) { totalHits.value = allPagesHitList.value.length // Find the first hit on the current page in the all-pages list const firstHitOnCurrentPage = allPagesHitList.value.findIndex(h => h.page === currentPage.value) if (firstHitOnCurrentPage !== -1) { currentHitIndex.value = firstHitOnCurrentPage } } else { totalHits.value = hits.length currentHitIndex.value = 0 } // Highlight all matches on the current page (Apple Preview style) updateHighlightsForCurrentPage() // Scroll to first match on current page if (hits.length > 0) { scrollToHit(0) } ``` #### 4. Update nextHit() to handle cross-page navigation ```javascript async function nextHit() { if (totalHits.value === 0) return // If we have cross-page results, check if we need to navigate to a different page if (allPagesHitList.value.length > 0) { const nextIndex = (currentHitIndex.value + 1) % totalHits.value const nextHit = allPagesHitList.value[nextIndex] if (nextHit && nextHit.page !== currentPage.value) { // Navigate to the page with the next hit currentHitIndex.value = nextIndex currentPage.value = nextHit.page pageInput.value = nextHit.page await renderPage(nextHit.page) } else { // Stay on current page, just move to next hit currentHitIndex.value = nextIndex scrollToHit(currentHitIndex.value) } } else { // Single page search currentHitIndex.value = (currentHitIndex.value + 1) % totalHits.value scrollToHit(currentHitIndex.value) } } ``` #### 5. Update prevHit() to handle cross-page navigation ```javascript async function prevHit() { if (totalHits.value === 0) return // If we have cross-page results, check if we need to navigate to a different page if (allPagesHitList.value.length > 0) { const prevIndex = currentHitIndex.value === 0 ? totalHits.value - 1 : currentHitIndex.value - 1 const prevHit = allPagesHitList.value[prevIndex] if (prevHit && prevHit.page !== currentPage.value) { // Navigate to the page with the previous hit currentHitIndex.value = prevIndex currentPage.value = prevHit.page pageInput.value = prevHit.page await renderPage(prevHit.page) } else { // Stay on current page, just move to previous hit currentHitIndex.value = prevIndex scrollToHit(currentHitIndex.value) } } else { // Single page search currentHitIndex.value = currentHitIndex.value === 0 ? totalHits.value - 1 : currentHitIndex.value - 1 scrollToHit(currentHitIndex.value) } } ``` #### 6. Update jumpToHit() to handle cross-page navigation ```javascript async function jumpToHit(index) { // If we're using cross-page results, index refers to allPagesHitList if (allPagesHitList.value.length > 0) { if (index < 0 || index >= allPagesHitList.value.length) return const hit = allPagesHitList.value[index] if (!hit) return currentHitIndex.value = index jumpListOpen.value = false // Navigate to the page if necessary if (hit.page !== currentPage.value) { currentPage.value = hit.page pageInput.value = hit.page await renderPage(hit.page) } else { // Already on the right page, just scroll to it scrollToHit(index) } } else { // Single page search if (index < 0 || index >= hitList.value.length) return currentHitIndex.value = index scrollToHit(index) jumpListOpen.value = false } } ``` #### 7. Update performSearch() to call searchAllPages ```javascript async function performSearch() { const query = searchInput.value.trim() if (!query) { clearSearch() return } searchQuery.value = query // Search all pages in the background searchAllPages(query).then(results => { allPagesHitList.value = results console.log(`Found ${results.length} matches across ${new Set(results.map(r => r.page)).size} pages`) // Update the hit list display if we're still showing the same query if (searchQuery.value === query) { // Re-highlight current page with updated counts if (textLayer.value) { highlightSearchTerms() } } }) // Re-highlight search terms on current page immediately if (textLayer.value) { highlightSearchTerms() } } ``` #### 8. Update clearSearch() to clear allPagesHitList ```javascript function clearSearch() { searchInput.value = '' searchQuery.value = '' totalHits.value = 0 hitList.value = [] allPagesHitList.value = [] // ADD THIS LINE currentHitIndex.value = 0 jumpListOpen.value = false // Remove highlights if (textLayer.value) { const marks = textLayer.value.querySelectorAll('mark.search-highlight') marks.forEach(mark => { const text = mark.textContent mark.replaceWith(text) }) } } ``` #### 9. Update Template Jump List Buttons (both locations) **Location 1: Full version (line ~171)** ```vue