import * as React from 'react'; import { FlatList, View } from 'react-native'; import NoteItem from '../../NoteItem'; import { useEffect, useRef, useState } from 'react'; import useQueuedAsyncEffect from '@joplin/lib/hooks/useQueuedAsyncEffect'; import { NoteEntity } from '@joplin/lib/services/database/types'; import SearchEngineUtils from '@joplin/lib/services/search/SearchEngineUtils'; import Note from '@joplin/lib/models/Note'; import SearchEngine, { ComplexTerm } from '@joplin/lib/services/search/SearchEngine'; import { ProgressBar } from 'react-native-paper'; import shim from '@joplin/lib/shim'; interface Props { query: string; onHighlightedWordsChange: (highlightedWords: (ComplexTerm | string)[])=> void; ftsEnabled: number; } const useResults = (props: Props) => { const [notes, setNotes] = useState([]); const [isProcessing, setIsProcessing] = useState(false); const query = props.query; const ftsEnabled = props.ftsEnabled; useQueuedAsyncEffect(async (event) => { let notes: NoteEntity[] = []; setIsProcessing(true); try { if (query) { if (ftsEnabled) { const r = await SearchEngineUtils.notesForQuery(query, true, { appendWildCards: true }); notes = r.notes; } else { const p = query.split(' '); const temp = []; for (let i = 0; i < p.length; i++) { const t = p[i].trim(); if (!t) continue; temp.push(t); } notes = await Note.previews(null, { anywherePattern: `*${temp.join('*')}*`, }); } } if (event.cancelled) return; const parsedQuery = await SearchEngine.instance().parseQuery(query); const highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery); props.onHighlightedWordsChange(highlightedWords); setNotes(notes); } finally { setIsProcessing(false); } }, [query, ftsEnabled], { interval: 200 }); return { notes, isPending: isProcessing, }; }; const useIsLongRunning = (isPending: boolean) => { const [isLongRunning, setIsLongRunning] = useState(false); const isPendingRef = useRef(isPending); isPendingRef.current = isPending; type TimeoutType = ReturnType; const timeoutRef = useRef(null); useEffect(() => { if (timeoutRef.current !== null) { shim.clearTimeout(timeoutRef.current); timeoutRef.current = null; } if (isPending) { const longRunningTimeout = 1000; timeoutRef.current = shim.setTimeout(() => { timeoutRef.current = null; setIsLongRunning(isPendingRef.current); }, longRunningTimeout); } else { setIsLongRunning(false); } }, [isPending]); return isLongRunning; }; const containerStyle = { flex: 1 }; const SearchResults: React.FC = props => { const { notes, isPending } = useResults(props); // Don't show the progress bar immediately, only show if the search // is taking some time. const longRunning = useIsLongRunning(isPending); const progressVisible = longRunning; // To have the correct height on web, the progress bar needs to be wrapped: const progressBar = ; return ( {progressBar} item.id} renderItem={event => } /> ); }; export default SearchResults;