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 && (