DocHub/frontend/src/App.tsx
Tianyang 542361c63a fix: natural-sort file tree, indent nested folders, fix editor layout
Also add a reindex endpoint and button to rebuild the Meilisearch
index from files already on disk, without needing to re-upload them.
2026-08-01 17:32:32 +08:00

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>
);
}