Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
133
frontend/src/features/config/hooks/useSourceSearch.ts
Normal file
133
frontend/src/features/config/hooks/useSourceSearch.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// 源码模式的文档内搜索 —— 从旧 pages/ConfigPage.tsx 逐字提取。
|
||||
// 手写 indexOf 扫描 + 光标定位(大小写不敏感、回绕),不依赖 @codemirror/search 面板。
|
||||
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
|
||||
export function useSourceSearch() {
|
||||
const editorRef = useRef<ReactCodeMirrorRef | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<{ current: number; total: number }>({
|
||||
current: 0,
|
||||
total: 0,
|
||||
});
|
||||
const [lastSearchedQuery, setLastSearchedQuery] = useState('');
|
||||
|
||||
const performSearch = useCallback((query: string, direction: 'next' | 'prev' = 'next') => {
|
||||
if (!query || !editorRef.current?.view) return;
|
||||
|
||||
const view = editorRef.current.view;
|
||||
const doc = view.state.doc.toString();
|
||||
const matches: number[] = [];
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const lowerDoc = doc.toLowerCase();
|
||||
|
||||
let pos = 0;
|
||||
while (pos < lowerDoc.length) {
|
||||
const index = lowerDoc.indexOf(lowerQuery, pos);
|
||||
if (index === -1) break;
|
||||
matches.push(index);
|
||||
pos = index + 1;
|
||||
}
|
||||
|
||||
if (matches.length === 0) {
|
||||
setSearchResults({ current: 0, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
// Find current match based on cursor position
|
||||
const selection = view.state.selection.main;
|
||||
const cursorPos = direction === 'prev' ? selection.from : selection.to;
|
||||
let currentIndex = 0;
|
||||
|
||||
if (direction === 'next') {
|
||||
// Find next match after cursor
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
if (matches[i] > cursorPos) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
// If no match after cursor, wrap to first
|
||||
if (i === matches.length - 1) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Find previous match before cursor
|
||||
for (let i = matches.length - 1; i >= 0; i--) {
|
||||
if (matches[i] < cursorPos) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
// If no match before cursor, wrap to last
|
||||
if (i === 0) {
|
||||
currentIndex = matches.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const matchPos = matches[currentIndex];
|
||||
setSearchResults({ current: currentIndex + 1, total: matches.length });
|
||||
|
||||
// Scroll to and select the match
|
||||
view.dispatch({
|
||||
selection: { anchor: matchPos, head: matchPos + query.length },
|
||||
scrollIntoView: true,
|
||||
});
|
||||
view.focus();
|
||||
}, []);
|
||||
|
||||
const handleSearchChange = useCallback((value: string) => {
|
||||
setSearchQuery(value);
|
||||
// Do not auto-search on each keystroke. Clear previous results when query changes.
|
||||
if (!value) {
|
||||
setSearchResults({ current: 0, total: 0 });
|
||||
setLastSearchedQuery('');
|
||||
} else {
|
||||
setSearchResults({ current: 0, total: 0 });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const executeSearch = useCallback(
|
||||
(direction: 'next' | 'prev' = 'next') => {
|
||||
if (!searchQuery) return;
|
||||
setLastSearchedQuery(searchQuery);
|
||||
performSearch(searchQuery, direction);
|
||||
},
|
||||
[searchQuery, performSearch]
|
||||
);
|
||||
|
||||
const handleSearchKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
executeSearch(e.shiftKey ? 'prev' : 'next');
|
||||
}
|
||||
},
|
||||
[executeSearch]
|
||||
);
|
||||
|
||||
const handlePrevMatch = useCallback(() => {
|
||||
if (!lastSearchedQuery) return;
|
||||
performSearch(lastSearchedQuery, 'prev');
|
||||
}, [lastSearchedQuery, performSearch]);
|
||||
|
||||
const handleNextMatch = useCallback(() => {
|
||||
if (!lastSearchedQuery) return;
|
||||
performSearch(lastSearchedQuery, 'next');
|
||||
}, [lastSearchedQuery, performSearch]);
|
||||
|
||||
return {
|
||||
editorRef,
|
||||
searchQuery,
|
||||
searchResults,
|
||||
lastSearchedQuery,
|
||||
handleSearchChange,
|
||||
executeSearch,
|
||||
handleSearchKeyDown,
|
||||
handlePrevMatch,
|
||||
handleNextMatch,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseSourceSearchResult = ReturnType<typeof useSourceSearch>;
|
||||
Loading…
Reference in a new issue