diff --git a/frontend/src/components/shared/RightRail.tsx b/frontend/src/components/shared/RightRail.tsx index 3abec0845..41ac3ae47 100644 --- a/frontend/src/components/shared/RightRail.tsx +++ b/frontend/src/components/shared/RightRail.tsx @@ -12,6 +12,7 @@ import LanguageSelector from '../shared/LanguageSelector'; import { useRainbowThemeContext } from '../shared/RainbowThemeProvider'; import { Tooltip } from '../shared/Tooltip'; import BulkSelectionPanel from '../pageEditor/BulkSelectionPanel'; +import { SearchInterface } from '../viewer/SearchInterface'; export default function RightRail() { const { t } = useTranslation(); @@ -214,15 +215,29 @@ export default function RightRail() {
{/* Search */} - (window as any).togglePdfSearch?.()} - disabled={currentView !== 'viewer'} - > - - + + +
+ + + +
+
+ +
+ {}} + /> +
+
+
diff --git a/frontend/src/components/viewer/EmbedPdfViewer.tsx b/frontend/src/components/viewer/EmbedPdfViewer.tsx index c56328c16..822655da9 100644 --- a/frontend/src/components/viewer/EmbedPdfViewer.tsx +++ b/frontend/src/components/viewer/EmbedPdfViewer.tsx @@ -8,7 +8,6 @@ import { useFileState } from "../../contexts/FileContext"; import { useFileWithUrl } from "../../hooks/useFileWithUrl"; import { LocalEmbedPDF } from './LocalEmbedPDF'; import { PdfViewerToolbar } from './PdfViewerToolbar'; -import { SearchInterface } from './SearchInterface'; import { ThumbnailSidebar } from './ThumbnailSidebar'; export interface EmbedPdfViewerProps { @@ -29,7 +28,6 @@ const EmbedPdfViewer = ({ const { colorScheme } = useMantineColorScheme(); const viewerRef = React.useRef(null); const [isViewerHovered, setIsViewerHovered] = React.useState(false); - const [isSearchVisible, setIsSearchVisible] = React.useState(false); const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] = React.useState(false); // Get current file from FileContext @@ -122,16 +120,11 @@ const EmbedPdfViewer = ({ // Expose toggle functions globally for right rail buttons React.useEffect(() => { - (window as any).togglePdfSearch = () => { - setIsSearchVisible(prev => !prev); - }; - (window as any).toggleThumbnailSidebar = () => { setIsThumbnailSidebarVisible(prev => !prev); }; return () => { - delete (window as any).togglePdfSearch; delete (window as any).toggleThumbnailSidebar; }; }, []); @@ -227,11 +220,6 @@ const EmbedPdfViewer = ({
)} - {/* Search Interface Overlay */} - setIsSearchVisible(false)} - /> {/* Thumbnail Sidebar */} { - if (!query.trim()) return; + if (!query.trim()) { + // If query is empty, clear the search + handleClearSearch(); + return; + } const searchAPI = (window as any).embedPdfSearch; if (searchAPI) { @@ -95,47 +100,60 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { const searchAPI = (window as any).embedPdfSearch; if (searchAPI) { searchAPI.clearSearch(); + // Also try to explicitly clear highlights if available + if (searchAPI.searchAPI && searchAPI.searchAPI.clearHighlights) { + searchAPI.searchAPI.clearHighlights(); + } } setSearchQuery(''); setResultInfo(null); }; + // Sync search query with API state on mount + useEffect(() => { + const searchAPI = (window as any).embedPdfSearch; + if (searchAPI && searchAPI.state && searchAPI.state.query) { + setSearchQuery(searchAPI.state.query); + } + }, []); + + const handleJumpToResult = (index: number) => { + const searchAPI = (window as any).embedPdfSearch; + if (searchAPI && resultInfo && index >= 1 && index <= resultInfo.totalResults) { + searchAPI.goToResult(index); + } + }; + + const handleJumpToSubmit = () => { + const index = parseInt(jumpToValue); + if (index && resultInfo && index >= 1 && index <= resultInfo.totalResults) { + handleJumpToResult(index); + } + }; + + const handleJumpToKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleJumpToSubmit(); + } + }; + const handleClose = () => { handleClearSearch(); onClose(); }; - if (!visible) return null; return ( - {/* Header with close button */} - + {/* Header */} + {t('search.title', 'Search PDF')} - - - {/* Search input */} @@ -143,7 +161,14 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { setSearchQuery(e.currentTarget.value)} + onChange={(e) => { + const newValue = e.currentTarget.value; + setSearchQuery(newValue); + // If user clears the input, clear the search highlights + if (!newValue.trim()) { + handleClearSearch(); + } + }} onKeyDown={handleKeyDown} style={{ flex: 1 }} rightSection={ @@ -162,15 +187,29 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { {/* Results info and navigation */} {resultInfo && ( - - {resultInfo.totalResults === 0 - ? t('search.noResults', 'No results found') - : t('search.resultCount', '{{current}} of {{total}}', { - current: resultInfo.currentIndex, - total: resultInfo.totalResults - }) - } - + {resultInfo.totalResults === 0 ? ( + + {t('search.noResults', 'No results found')} + + ) : ( + + setJumpToValue(e.currentTarget.value)} + onKeyDown={handleJumpToKeyDown} + onBlur={handleJumpToSubmit} + placeholder={resultInfo.currentIndex.toString()} + style={{ width: '50px' }} + type="number" + min="1" + max={resultInfo.totalResults} + /> + + of {resultInfo.totalResults} + + + )} {resultInfo.totalResults > 0 && (