Also add a reindex endpoint and button to rebuild the Meilisearch index from files already on disk, without needing to re-upload them.
97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import { useState } from 'react';
|
|
import FileExplorer from './components/FileExplorer';
|
|
import DocumentEditor from './components/DocumentEditor';
|
|
import SearchBar from './components/SearchBar';
|
|
import SearchResults from './components/SearchResults';
|
|
import UploadButton from './components/UploadButton';
|
|
import ReindexButton from './components/ReindexButton';
|
|
import type { SearchResult, UploadDocumentResponse } from './api/client';
|
|
|
|
export default function App() {
|
|
const [selectedKey, setSelectedKey] = useState<string | null>(null);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
|
const [isSearching, setIsSearching] = useState(false);
|
|
const [isDirty, setIsDirty] = useState(false);
|
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
const [uploadPath] = useState('');
|
|
|
|
function confirmDiscardIfDirty(): boolean {
|
|
if (!isDirty) {
|
|
return true;
|
|
}
|
|
return window.confirm('有未保存的修改,确定要离开吗?');
|
|
}
|
|
|
|
function handleSelectFile(key: string) {
|
|
if (!confirmDiscardIfDirty()) {
|
|
return;
|
|
}
|
|
setSelectedKey(key);
|
|
}
|
|
|
|
function handleJumpToResult(key: string) {
|
|
if (!confirmDiscardIfDirty()) {
|
|
return;
|
|
}
|
|
setSelectedKey(key);
|
|
setSearchQuery('');
|
|
setSearchResults([]);
|
|
setIsSearching(false);
|
|
}
|
|
|
|
function handleQueryChange(query: string) {
|
|
setSearchQuery(query);
|
|
setIsSearching(query.trim() !== '');
|
|
}
|
|
|
|
function handleResults(results: SearchResult[]) {
|
|
setSearchResults(results);
|
|
setIsSearching(false);
|
|
}
|
|
|
|
function handleUploaded(result: UploadDocumentResponse) {
|
|
setSelectedKey(result.key);
|
|
setRefreshKey((prev) => prev + 1);
|
|
}
|
|
|
|
function handleDeleted() {
|
|
setSelectedKey(null);
|
|
setRefreshKey((prev) => prev + 1);
|
|
}
|
|
|
|
return (
|
|
<div className="app">
|
|
<div className="topbar">
|
|
<h1 className="wordmark" aria-label="DocHub">
|
|
DocHub <span className="subtitle">档案 · 检索</span>
|
|
</h1>
|
|
<SearchBar value={searchQuery} onQueryChange={handleQueryChange} onResults={handleResults} />
|
|
<ReindexButton />
|
|
<UploadButton path={uploadPath} onUploaded={handleUploaded} />
|
|
</div>
|
|
<div className="body">
|
|
<FileExplorer onSelectFile={handleSelectFile} selectedKey={selectedKey} refreshKey={refreshKey} />
|
|
<div className="main" data-testid="main-area">
|
|
{selectedKey ? (
|
|
<DocumentEditor documentKey={selectedKey} onDirtyChange={setIsDirty} onDeleted={handleDeleted} />
|
|
) : (
|
|
<div className="empty-state" data-testid="empty-state">
|
|
未选择文档
|
|
</div>
|
|
)}
|
|
</div>
|
|
{searchQuery !== '' && !isSearching && (
|
|
<div className="search-overlay">
|
|
<SearchResults query={searchQuery} results={searchResults} onJumpToResult={handleJumpToResult} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="statusbar">
|
|
<span>DOCHUB</span>
|
|
<span>UTF-8</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|