文件
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

130 行
3.7 KiB
TypeScript

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;
paused: boolean;
onHighlightedWordsChange: (highlightedWords: (ComplexTerm | string)[])=> void;
ftsEnabled: number;
}
const useResults = (props: Props) => {
const [notes, setNotes] = useState<NoteEntity[]>([]);
const [highlightedWord, setHighlightedWord] = 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 && !props.paused) {
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 searchTerms = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
if (event.cancelled) return;
props.onHighlightedWordsChange(searchTerms);
setHighlightedWord(SearchEngine.instance().createQueryFromTerms(searchTerms));
setNotes(notes);
} finally {
setIsProcessing(false);
}
}, [query, props.paused, ftsEnabled], { interval: 200 });
return {
notes,
highlightedWord,
isPending: isProcessing,
};
};
const useIsLongRunning = (isPending: boolean) => {
const [isLongRunning, setIsLongRunning] = useState(false);
const isPendingRef = useRef(isPending);
isPendingRef.current = isPending;
type TimeoutType = ReturnType<typeof shim.setTimeout>;
const timeoutRef = useRef<TimeoutType|null>(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> = props => {
const { notes, highlightedWord, 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 = <View aria-hidden={!progressVisible}>
<ProgressBar indeterminate={true} visible={progressVisible}/>
</View>;
return (
<View style={containerStyle}>
{progressBar}
<FlatList
data={notes}
extraData={highlightedWord}
keyExtractor={(item) => item.id}
renderItem={event => <NoteItem note={event.item} highlightedWord={highlightedWord} />}
/>
</View>
);
};
export default SearchResults;