mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-01 20:10:35 +01:00
Chore/v2/ctrlf (#5217)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
ae72344317
commit
f29d85565a
@ -44,8 +44,10 @@ export function CustomSearchLayer({
|
||||
}
|
||||
|
||||
const unsubscribe = searchProvides.onSearchResultStateChange?.((state: SearchResultState) => {
|
||||
if (!state) return;
|
||||
|
||||
// Auto-scroll to active search result
|
||||
if (state?.results && state.activeResultIndex !== undefined && state.activeResultIndex >= 0) {
|
||||
if (state.results && state.activeResultIndex !== undefined && state.activeResultIndex >= 0) {
|
||||
const activeResult = state.results[state.activeResultIndex];
|
||||
if (activeResult) {
|
||||
const pageNumber = activeResult.pageIndex + 1; // Convert to 1-based page number
|
||||
|
||||
@ -43,6 +43,8 @@ const EmbedPdfViewerContent = ({
|
||||
isThumbnailSidebarVisible,
|
||||
toggleThumbnailSidebar,
|
||||
isBookmarkSidebarVisible,
|
||||
isSearchInterfaceVisible,
|
||||
searchInterfaceActions,
|
||||
zoomActions,
|
||||
panActions: _panActions,
|
||||
rotationActions: _rotationActions,
|
||||
@ -184,7 +186,7 @@ const EmbedPdfViewerContent = ({
|
||||
onZoomOut: zoomActions.zoomOut,
|
||||
});
|
||||
|
||||
// Handle keyboard zoom shortcuts
|
||||
// Handle keyboard shortcuts (zoom and search)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (!isViewerHovered) return;
|
||||
@ -199,6 +201,16 @@ const EmbedPdfViewerContent = ({
|
||||
// Ctrl+- for zoom out
|
||||
event.preventDefault();
|
||||
zoomActions.zoomOut();
|
||||
} else if (event.key === 'f' || event.key === 'F') {
|
||||
// Ctrl+F for search
|
||||
event.preventDefault();
|
||||
if (isSearchInterfaceVisible) {
|
||||
// If already open, trigger refocus event
|
||||
window.dispatchEvent(new CustomEvent('refocus-search-input'));
|
||||
} else {
|
||||
// Open search interface
|
||||
searchInterfaceActions.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -207,7 +219,7 @@ const EmbedPdfViewerContent = ({
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [isViewerHovered]);
|
||||
}, [isViewerHovered, isSearchInterfaceVisible, zoomActions, searchInterfaceActions]);
|
||||
|
||||
// Register checker for unsaved changes (annotations only for now)
|
||||
useEffect(() => {
|
||||
|
||||
@ -28,11 +28,13 @@ export function SearchAPIBridge() {
|
||||
if (!search) return;
|
||||
|
||||
const unsubscribe = search.onSearchResultStateChange?.((state: any) => {
|
||||
if (!state) return;
|
||||
|
||||
const newState = {
|
||||
results: state?.results || null,
|
||||
activeIndex: (state?.activeResultIndex || 0) + 1 // Convert to 1-based index
|
||||
results: state.results || null,
|
||||
activeIndex: (state.activeResultIndex || 0) + 1 // Convert to 1-based index
|
||||
};
|
||||
|
||||
|
||||
setLocalState(prevState => {
|
||||
// Only update if state actually changed
|
||||
if (prevState.results !== newState.results || prevState.activeIndex !== newState.activeIndex) {
|
||||
@ -52,16 +54,42 @@ export function SearchAPIBridge() {
|
||||
state: localState,
|
||||
api: {
|
||||
search: async (query: string) => {
|
||||
search.startSearch();
|
||||
return search.searchAllPages(query);
|
||||
if (search?.startSearch && search?.searchAllPages) {
|
||||
search.startSearch();
|
||||
return search.searchAllPages(query);
|
||||
}
|
||||
},
|
||||
clear: () => {
|
||||
search.stopSearch();
|
||||
try {
|
||||
if (search?.stopSearch) {
|
||||
search.stopSearch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error stopping search:', error);
|
||||
}
|
||||
setLocalState({ results: null, activeIndex: 0 });
|
||||
},
|
||||
next: () => search.nextResult(),
|
||||
previous: () => search.previousResult(),
|
||||
goToResult: (index: number) => search.goToResult(index),
|
||||
next: () => {
|
||||
try {
|
||||
search?.nextResult?.();
|
||||
} catch (error) {
|
||||
console.warn('Error navigating to next result:', error);
|
||||
}
|
||||
},
|
||||
previous: () => {
|
||||
try {
|
||||
search?.previousResult?.();
|
||||
} catch (error) {
|
||||
console.warn('Error navigating to previous result:', error);
|
||||
}
|
||||
},
|
||||
goToResult: (index: number) => {
|
||||
try {
|
||||
search?.goToResult?.(index);
|
||||
} catch (error) {
|
||||
console.warn('Error going to result:', error);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Box, TextInput, ActionIcon, Text, Group } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LocalIcon } from '@app/components/shared/LocalIcon';
|
||||
@ -12,7 +12,9 @@ interface SearchInterfaceProps {
|
||||
export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
|
||||
const { t } = useTranslation();
|
||||
const viewerContext = React.useContext(ViewerContext);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const searchState = viewerContext?.getSearchState();
|
||||
const searchResults = searchState?.results;
|
||||
const searchActiveIndex = searchState?.activeIndex;
|
||||
@ -26,6 +28,61 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
|
||||
} | null>(null);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
// Auto-focus search input when visible
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
// Listen for refocus event (when Ctrl+F pressed while already open)
|
||||
useEffect(() => {
|
||||
const handleRefocus = () => {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
};
|
||||
|
||||
window.addEventListener('refocus-search-input', handleRefocus);
|
||||
return () => {
|
||||
window.removeEventListener('refocus-search-input', handleRefocus);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Auto-search as user types (debounced)
|
||||
useEffect(() => {
|
||||
// Clear existing timeout
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
}
|
||||
|
||||
// If query is empty, clear search immediately
|
||||
if (!searchQuery.trim()) {
|
||||
searchActions?.clear();
|
||||
setResultInfo(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce search by 300ms
|
||||
searchTimeoutRef.current = setTimeout(async () => {
|
||||
if (searchQuery.trim() && searchActions) {
|
||||
setIsSearching(true);
|
||||
try {
|
||||
await searchActions.search(searchQuery.trim());
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [searchQuery, searchActions]);
|
||||
|
||||
// Monitor search state changes
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
@ -59,30 +116,21 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
|
||||
return () => clearInterval(interval);
|
||||
}, [visible, searchResults, searchActiveIndex, searchQuery]);
|
||||
|
||||
const handleSearch = async (query: string) => {
|
||||
if (!query.trim()) {
|
||||
// If query is empty, clear the search
|
||||
handleClearSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.trim() && searchActions) {
|
||||
setIsSearching(true);
|
||||
try {
|
||||
await searchActions.search(query.trim());
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleSearch(searchQuery);
|
||||
// Navigate to next result on Enter
|
||||
event.preventDefault();
|
||||
handleNext();
|
||||
} else if (event.key === 'Escape') {
|
||||
onClose();
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
// Navigate to next result
|
||||
event.preventDefault();
|
||||
handleNext();
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
// Navigate to previous result
|
||||
event.preventDefault();
|
||||
handlePrevious();
|
||||
}
|
||||
};
|
||||
|
||||
@ -103,17 +151,17 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
|
||||
// No longer need to sync with external API on mount - removed
|
||||
|
||||
const handleJumpToResult = (index: number) => {
|
||||
// Use context actions instead of window API - functionality simplified for now
|
||||
if (resultInfo && index >= 1 && index <= resultInfo.totalResults) {
|
||||
// Note: goToResult functionality would need to be implemented in SearchAPIBridge
|
||||
console.log('Jump to result:', index);
|
||||
// Convert to 0-based index for the API
|
||||
searchActions?.goToResult?.(index - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleJumpToSubmit = () => {
|
||||
const index = parseInt(jumpToValue);
|
||||
if (index && resultInfo && index >= 1 && index <= resultInfo.totalResults) {
|
||||
const index = parseInt(jumpToValue, 10);
|
||||
if (!isNaN(index) && resultInfo && index >= 1 && index <= resultInfo.totalResults) {
|
||||
handleJumpToResult(index);
|
||||
setJumpToValue(''); // Clear the input after jumping
|
||||
}
|
||||
};
|
||||
|
||||
@ -123,7 +171,14 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const _handleClose = () => {
|
||||
const handleInputBlur = () => {
|
||||
// Close popover on blur if no text is entered
|
||||
if (!searchQuery.trim()) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseClick = () => {
|
||||
handleClearSearch();
|
||||
onClose();
|
||||
};
|
||||
@ -135,100 +190,99 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
|
||||
padding: '0px'
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
{/* Header with close button */}
|
||||
<Group mb="md" justify="space-between">
|
||||
<Text size="sm" fw={600}>
|
||||
{t('search.title', 'Search PDF')}
|
||||
</Text>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={handleCloseClick}
|
||||
aria-label="Close search"
|
||||
>
|
||||
<LocalIcon icon="close" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/* Search input */}
|
||||
<Group mb="md">
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
placeholder={t('search.placeholder', 'Enter search term...')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
const newValue = e.currentTarget.value;
|
||||
setSearchQuery(newValue);
|
||||
// If user clears the input, clear the search highlights
|
||||
if (!newValue.trim()) {
|
||||
handleClearSearch();
|
||||
}
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
style={{ flex: 1 }}
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
onClick={() => handleSearch(searchQuery)}
|
||||
disabled={!searchQuery.trim() || isSearching}
|
||||
loading={isSearching}
|
||||
>
|
||||
<LocalIcon icon="search" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
searchQuery.trim() && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
onClick={handleClearSearch}
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<LocalIcon icon="close" width="0.875rem" height="0.875rem" />
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{/* Results info and navigation */}
|
||||
{resultInfo && (
|
||||
<Group justify="space-between" align="center">
|
||||
{resultInfo.totalResults === 0 ? (
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('search.noResults', 'No results found')}
|
||||
</Text>
|
||||
) : (
|
||||
<Group gap="xs" align="center">
|
||||
<TextInput
|
||||
size="xs"
|
||||
value={jumpToValue}
|
||||
onChange={(e) => setJumpToValue(e.currentTarget.value)}
|
||||
onKeyDown={handleJumpToKeyDown}
|
||||
onBlur={handleJumpToSubmit}
|
||||
placeholder={resultInfo.currentIndex.toString()}
|
||||
style={{ width: '3rem' }}
|
||||
type="number"
|
||||
min="1"
|
||||
max={resultInfo.totalResults}
|
||||
/>
|
||||
<Text size="sm" c="dimmed">
|
||||
of {resultInfo.totalResults}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{resultInfo.totalResults > 0 && (
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={handlePrevious}
|
||||
disabled={resultInfo.currentIndex <= 1}
|
||||
aria-label="Previous result"
|
||||
>
|
||||
<LocalIcon icon="keyboard-arrow-up" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={handleNext}
|
||||
disabled={resultInfo.currentIndex >= resultInfo.totalResults}
|
||||
aria-label="Next result"
|
||||
>
|
||||
<LocalIcon icon="keyboard-arrow-down" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={handleClearSearch}
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<LocalIcon icon="close" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
)}
|
||||
{/* Results info and navigation - always show */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Group gap="xs" align="center">
|
||||
<TextInput
|
||||
size="xs"
|
||||
value={jumpToValue}
|
||||
onChange={(e) => {
|
||||
const newValue = e.currentTarget.value;
|
||||
setJumpToValue(newValue);
|
||||
|
||||
// Jump immediately as user types
|
||||
const index = parseInt(newValue, 10);
|
||||
if (resultInfo && !isNaN(index) && index >= 1 && index <= resultInfo.totalResults) {
|
||||
handleJumpToResult(index);
|
||||
}
|
||||
}}
|
||||
onKeyDown={handleJumpToKeyDown}
|
||||
onBlur={() => setJumpToValue('')} // Clear on blur instead of submit
|
||||
placeholder={(resultInfo?.currentIndex || 0).toString()}
|
||||
style={{ width: '3rem' }}
|
||||
type="number"
|
||||
min="1"
|
||||
max={resultInfo?.totalResults || 0}
|
||||
disabled={!resultInfo || resultInfo.totalResults === 0}
|
||||
/>
|
||||
<Text size="sm" c="dimmed">
|
||||
of {resultInfo?.totalResults || 0}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={handlePrevious}
|
||||
disabled={!resultInfo || resultInfo.currentIndex <= 1}
|
||||
aria-label="Previous result"
|
||||
>
|
||||
<LocalIcon icon="keyboard-arrow-up" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={handleNext}
|
||||
disabled={!resultInfo || resultInfo.currentIndex >= resultInfo.totalResults}
|
||||
aria-label="Next result"
|
||||
>
|
||||
<LocalIcon icon="keyboard-arrow-down" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{/* Loading state */}
|
||||
{isSearching && (
|
||||
|
||||
@ -36,7 +36,14 @@ export function useViewerRightRailButtons() {
|
||||
order: 10,
|
||||
render: ({ disabled }) => (
|
||||
<Tooltip content={searchLabel} position={tooltipPosition} offset={12} arrow portalTarget={document.body}>
|
||||
<Popover position={tooltipPosition} withArrow shadow="md" offset={8}>
|
||||
<Popover
|
||||
position={tooltipPosition}
|
||||
withArrow
|
||||
shadow="md"
|
||||
offset={8}
|
||||
opened={viewer.isSearchInterfaceVisible}
|
||||
onClose={viewer.searchInterfaceActions.close}
|
||||
>
|
||||
<Popover.Target>
|
||||
<div style={{ display: 'inline-flex' }}>
|
||||
<ActionIcon
|
||||
@ -45,6 +52,7 @@ export function useViewerRightRailButtons() {
|
||||
className="right-rail-icon"
|
||||
disabled={disabled}
|
||||
aria-label={searchLabel}
|
||||
onClick={viewer.searchInterfaceActions.toggle}
|
||||
>
|
||||
<LocalIcon icon="search" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
@ -52,7 +60,7 @@ export function useViewerRightRailButtons() {
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<div style={{ minWidth: '20rem' }}>
|
||||
<SearchInterface visible={true} onClose={() => {}} />
|
||||
<SearchInterface visible={viewer.isSearchInterfaceVisible} onClose={viewer.searchInterfaceActions.close} />
|
||||
</div>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
@ -80,6 +80,14 @@ interface ViewerContextType {
|
||||
isBookmarkSidebarVisible: boolean;
|
||||
toggleBookmarkSidebar: () => void;
|
||||
|
||||
// Search interface visibility
|
||||
isSearchInterfaceVisible: boolean;
|
||||
searchInterfaceActions: {
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
toggle: () => void;
|
||||
};
|
||||
|
||||
// Annotation visibility toggle
|
||||
isAnnotationsVisible: boolean;
|
||||
toggleAnnotationsVisibility: () => void;
|
||||
@ -145,6 +153,7 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
// UI state - only state directly managed by this context
|
||||
const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] = useState(false);
|
||||
const [isBookmarkSidebarVisible, setIsBookmarkSidebarVisible] = useState(false);
|
||||
const [isSearchInterfaceVisible, setSearchInterfaceVisible] = useState(false);
|
||||
const [isAnnotationsVisible, setIsAnnotationsVisible] = useState(true);
|
||||
const [isAnnotationMode, setIsAnnotationModeState] = useState(false);
|
||||
const [activeFileIndex, setActiveFileIndex] = useState(0);
|
||||
@ -207,6 +216,12 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
setIsBookmarkSidebarVisible(prev => !prev);
|
||||
};
|
||||
|
||||
const searchInterfaceActions = {
|
||||
open: () => setSearchInterfaceVisible(true),
|
||||
close: () => setSearchInterfaceVisible(false),
|
||||
toggle: () => setSearchInterfaceVisible(prev => !prev),
|
||||
};
|
||||
|
||||
const toggleAnnotationsVisibility = () => {
|
||||
setIsAnnotationsVisible(prev => !prev);
|
||||
};
|
||||
@ -294,6 +309,10 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
isBookmarkSidebarVisible,
|
||||
toggleBookmarkSidebar,
|
||||
|
||||
// Search interface
|
||||
isSearchInterfaceVisible,
|
||||
searchInterfaceActions,
|
||||
|
||||
// Annotation controls
|
||||
isAnnotationsVisible,
|
||||
toggleAnnotationsVisibility,
|
||||
|
||||
@ -52,6 +52,7 @@ export interface SearchActions {
|
||||
next: () => void;
|
||||
previous: () => void;
|
||||
clear: () => void;
|
||||
goToResult: (index: number) => void;
|
||||
}
|
||||
|
||||
export interface ExportActions {
|
||||
@ -287,6 +288,12 @@ export function createViewerActions({
|
||||
api.clear();
|
||||
}
|
||||
},
|
||||
goToResult: (index: number) => {
|
||||
const api = registry.current.search?.api;
|
||||
if (api?.goToResult) {
|
||||
api.goToResult(index);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const exportActions: ExportActions = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user