From 0d59255edbb6807cab8105adbc5c8a9f4a545ca0 Mon Sep 17 00:00:00 2001 From: Minsoo Kim Date: Tue, 10 Mar 2026 22:08:58 +0900 Subject: [PATCH] feat: add PDF page navigation, folder deletion, and dirty file indicator - Add Page Up/Down buttons and editable page indicator to PDF preview toolbar - Track current visible page via scroll with rAF throttling - Implement proper recursive folder deletion in sidebar and document store - Replace text asterisk with blue dot indicator for modified files in sidebar - Extract shared scrollToPage helper to deduplicate scroll math in pdf-viewer Co-Authored-By: Claude Opus 4.6 --- .../workspace/preview/pdf-preview.tsx | 66 ++++++++++++++++++- .../workspace/preview/pdf-viewer.tsx | 64 ++++++++++++++---- .../src/components/workspace/sidebar.tsx | 20 +++--- apps/desktop/src/lib/tauri/fs.ts | 4 ++ apps/desktop/src/stores/document-store.ts | 59 +++++++++++++++++ 5 files changed, 191 insertions(+), 22 deletions(-) diff --git a/apps/desktop/src/components/workspace/preview/pdf-preview.tsx b/apps/desktop/src/components/workspace/preview/pdf-preview.tsx index 28cec0c..ce471ac 100644 --- a/apps/desktop/src/components/workspace/preview/pdf-preview.tsx +++ b/apps/desktop/src/components/workspace/preview/pdf-preview.tsx @@ -11,6 +11,8 @@ import { HistoryIcon, MousePointerClickIcon, CrosshairIcon, + ChevronUpIcon, + ChevronDownIcon, } from "lucide-react"; import { writeFile, mkdir, exists } from "@tauri-apps/plugin-fs"; import { join } from "@tauri-apps/api/path"; @@ -83,6 +85,10 @@ export function PdfPreview() { const [pdfError, setPdfError] = useState(null); const [numPages, setNumPages] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [pageInputValue, setPageInputValue] = useState("1"); + const [isEditingPage, setIsEditingPage] = useState(false); + const scrollToPageRef = useRef<((page: number) => void) | null>(null); const [scale, setScale] = useState(1.0); const [captureMode, setCaptureMode] = useState(false); const [fitMode, setFitMode] = useState(null); @@ -368,6 +374,29 @@ export function PdfPreview() { await writeFile(filePath, new Uint8Array(pdfData)); }; + const handleCurrentPageChange = useCallback((page: number) => { + setCurrentPage((prev) => { + if (prev === page) return prev; + if (!isEditingPage) setPageInputValue(String(page)); + return page; + }); + }, [isEditingPage]); + + const goToPage = useCallback((page: number) => { + const clamped = Math.max(1, Math.min(numPages, page)); + scrollToPageRef.current?.(clamped); + }, [numPages]); + + const handlePageInputCommit = useCallback(() => { + setIsEditingPage(false); + const parsed = parseInt(pageInputValue, 10); + if (!isNaN(parsed) && parsed >= 1 && parsed <= numPages) { + goToPage(parsed); + } else { + setPageInputValue(String(currentPage)); + } + }, [pageInputValue, numPages, currentPage, goToPage]); + const handleLoadSuccess = (pages: number) => setNumPages(pages); const handleScaleChange = (newScale: number) => { setFitMode(null); setScale(newScale); }; @@ -563,6 +592,8 @@ export function PdfPreview() { onTextSelect={isActive ? handleTextSelect : undefined} onFirstPageSize={isActive ? (w, h) => setFirstPageSize({ width: w, height: h }) : undefined} onContainerResize={isActive ? (w, h) => setContainerSize({ width: w, height: h }) : undefined} + onCurrentPageChange={isActive ? handleCurrentPageChange : undefined} + scrollToPageRef={isActive ? scrollToPageRef : undefined} captureMode={isActive ? captureMode : false} onCapture={isActive ? handleCapture : undefined} onCancelCapture={isActive ? () => setCaptureMode(false) : undefined} @@ -608,7 +639,40 @@ export function PdfPreview() {
{pdfData && ( <> - {numPages} {numPages === 1 ? "page" : "pages"} + + {isEditingPage ? ( + setPageInputValue(e.target.value)} + onBlur={handlePageInputCommit} + onKeyDown={(e) => { + if (e.key === "Enter") handlePageInputCommit(); + if (e.key === "Escape") { + setIsEditingPage(false); + setPageInputValue(String(currentPage)); + } + }} + /> + ) : ( + + )} + / {numPages} + +