diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx index b32ad0ddf..68d971fc4 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx @@ -38,7 +38,6 @@ import { import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Drawer, DrawerContent, @@ -234,6 +233,7 @@ export function DocumentsTableShell({ mentionedDocIds, onToggleChatMention, isSearchMode = false, + onOpenInTab, }: { documents: Document[]; loading: boolean; @@ -253,6 +253,8 @@ export function DocumentsTableShell({ onToggleChatMention?: (doc: Document, mentioned: boolean) => void; /** Whether results are filtered by a search query or type filters */ isSearchMode?: boolean; + /** When provided, desktop "Preview" opens a document tab instead of the popup dialog */ + onOpenInTab?: (doc: Document) => void; }) { const t = useTranslations("documents"); const { openDialog } = useDocumentUploadDialog(); @@ -742,9 +744,9 @@ export function DocumentsTableShell({ - handleViewDocument(doc)}> + onOpenInTab ? onOpenInTab(doc) : handleViewDocument(doc)}> - Preview + Open {isEditable && ( )} - {/* Document Content Viewer */} - !open && handleCloseViewer()}> - - - + {/* Document Content Viewer (mobile drawer) */} + !open && handleCloseViewer()}> + + + + {viewingDoc?.title} - - + +
)}
-
-
+ + {/* Document Metadata Viewer (Ctrl+Click) */} - Preview + Open {mobileActionDoc && EDITABLE_DOCUMENT_TYPES.includes( diff --git a/surfsense_web/components/documents/DocumentNode.tsx b/surfsense_web/components/documents/DocumentNode.tsx index da488a199..64b98cdc2 100644 --- a/surfsense_web/components/documents/DocumentNode.tsx +++ b/surfsense_web/components/documents/DocumentNode.tsx @@ -203,7 +203,7 @@ export const DocumentNode = React.memo(function DocumentNode({ - + e.stopPropagation()}> onPreview(doc)}> Open @@ -243,7 +243,7 @@ export const DocumentNode = React.memo(function DocumentNode({ {contextMenuOpen && ( - + e.stopPropagation()}> onPreview(doc)}> Open diff --git a/surfsense_web/components/editor-panel/editor-panel.tsx b/surfsense_web/components/editor-panel/editor-panel.tsx index 7530a1d80..f2f66eb48 100644 --- a/surfsense_web/components/editor-panel/editor-panel.tsx +++ b/surfsense_web/components/editor-panel/editor-panel.tsx @@ -255,7 +255,7 @@ function MobileEditorDrawer() { shouldScaleBackground={false} > diff --git a/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx b/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx index e8bc1a6cd..a75752217 100644 --- a/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx +++ b/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx @@ -355,7 +355,7 @@ function MobileHitlEditDrawer() { shouldScaleBackground={false} > diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 0baf403a9..460121f22 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -7,6 +7,7 @@ import { useParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; +import { MarkdownViewer } from "@/components/markdown-viewer"; import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems"; import { DocumentsFilters } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters"; import { @@ -27,6 +28,14 @@ import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog"; import { FolderTreeView } from "@/components/documents/FolderTreeView"; import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; +import { + Drawer, + DrawerContent, + DrawerHandle, + DrawerHeader, + DrawerTitle, +} from "@/components/ui/drawer"; +import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { DocumentTypeEnum } from "@/contracts/types/document.types"; @@ -34,7 +43,9 @@ import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { useDocumentSearch } from "@/hooks/use-document-search"; import { useDocuments } from "@/hooks/use-documents"; import { useMediaQuery } from "@/hooks/use-media-query"; +import { useIsMobile } from "@/hooks/use-mobile"; import { foldersApiService } from "@/lib/apis/folders-api.service"; +import { documentsApiService } from "@/lib/apis/documents-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; import { queries } from "@/zero/queries/index"; import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel"; @@ -81,6 +92,8 @@ export function DocumentsSidebar({ const { data: connectors } = useAtomValue(connectorsAtom); const connectorCount = connectors?.length ?? 0; + const isMobileLayout = useIsMobile(); + const [search, setSearch] = useState(""); const debouncedSearch = useDebouncedValue(search, 250); const [activeTypes, setActiveTypes] = useState([]); @@ -333,6 +346,31 @@ export function DocumentsSidebar({ [] ); + // Document popup viewer state (for tree view "Open" and mobile preview) + const [viewingDoc, setViewingDoc] = useState(null); + const [viewingContent, setViewingContent] = useState(""); + const [viewingLoading, setViewingLoading] = useState(false); + + const handleViewDocumentPopup = useCallback(async (doc: DocumentNodeDoc) => { + setViewingDoc(doc); + setViewingLoading(true); + try { + const fullDoc = await documentsApiService.getDocument({ id: doc.id }); + setViewingContent(fullDoc.content); + } catch (err) { + console.error("[DocumentsSidebar] Failed to fetch document content:", err); + setViewingContent("Failed to load document content."); + } finally { + setViewingLoading(false); + } + }, []); + + const handleCloseViewer = useCallback(() => { + setViewingDoc(null); + setViewingContent(""); + setViewingLoading(false); + }, []); + const handleToggleChatMention = useCallback( (doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => { if (isMentioned) { @@ -622,6 +660,11 @@ export function DocumentsSidebar({ mentionedDocIds={mentionedDocIds} onToggleChatMention={handleToggleChatMention} isSearchMode={isSearchMode || activeTypes.length > 0} + onOpenInTab={!isMobileLayout ? (doc) => openDocumentTab({ + documentId: doc.id, + searchSpaceId, + title: doc.title, + }) : undefined} /> ) : ( { - openDocumentTab({ - documentId: doc.id, - searchSpaceId, - title: doc.title, - }); + if (isMobileLayout) { + handleViewDocumentPopup(doc); + } else { + openDocumentTab({ + documentId: doc.id, + searchSpaceId, + title: doc.title, + }); + } }} onEditDocument={(doc) => { - openDocumentTab({ - documentId: doc.id, - searchSpaceId, - title: doc.title, - }); + if (!isMobileLayout) { + openDocumentTab({ + documentId: doc.id, + searchSpaceId, + title: doc.title, + }); + } }} onDeleteDocument={(doc) => handleDeleteDocument(doc.id)} onMoveDocument={handleMoveDocument} @@ -675,6 +724,26 @@ export function DocumentsSidebar({ parentFolderName={createFolderParentName} onConfirm={handleCreateFolderConfirm} /> + + !open && handleCloseViewer()}> + + + + + {viewingDoc?.title} + + +
+ {viewingLoading ? ( +
+ +
+ ) : ( + + )} +
+
+
); diff --git a/surfsense_web/components/report-panel/report-panel.tsx b/surfsense_web/components/report-panel/report-panel.tsx index f7d256a95..2dbdd894f 100644 --- a/surfsense_web/components/report-panel/report-panel.tsx +++ b/surfsense_web/components/report-panel/report-panel.tsx @@ -455,7 +455,7 @@ function MobileReportDrawer() { shouldScaleBackground={false} >