From 78e66477cf88f90608f317c52c3c4da5bbaece51 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:40:32 +0530 Subject: [PATCH] feat: implement document metadata viewer with Ctrl+Click functionality in DocumentsTableShell --- .../components/DocumentsTableShell.tsx | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) 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 17569fd7a..aa712da5b 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 @@ -19,6 +19,7 @@ import { useTranslations } from "next-intl"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup"; +import { JsonMetadataViewer } from "@/components/json-metadata-viewer"; import { MarkdownViewer } from "@/components/markdown-viewer"; import { AlertDialog, @@ -351,6 +352,10 @@ export function DocumentsTableShell({ const [viewingDoc, setViewingDoc] = useState(null); const [viewingContent, setViewingContent] = useState(""); const [viewingLoading, setViewingLoading] = useState(false); + + const [metadataDoc, setMetadataDoc] = useState(null); + const [metadataJson, setMetadataJson] = useState | null>(null); + const [metadataLoading, setMetadataLoading] = useState(false); const [previewScrollPos, setPreviewScrollPos] = useState<"top" | "middle" | "bottom">("top"); const handlePreviewScroll = useCallback((e: React.UIEvent) => { const el = e.currentTarget; @@ -418,6 +423,20 @@ export function DocumentsTableShell({ setViewingLoading(false); }, []); + const handleViewMetadata = useCallback(async (doc: Document) => { + setMetadataDoc(doc); + setMetadataLoading(true); + try { + const fullDoc = await documentsApiService.getDocument({ id: doc.id }); + setMetadataJson(fullDoc.document_metadata ?? {}); + } catch (err) { + console.error("[DocumentsTableShell] Failed to fetch document metadata:", err); + setMetadataJson({ error: "Failed to load document metadata" }); + } finally { + setMetadataLoading(false); + } + }, []); + const handleDeleteFromMenu = useCallback(async () => { if (!deleteDoc) return; setIsDeleting(true); @@ -573,11 +592,20 @@ export function DocumentsTableShell({ {sorted.map((doc) => { const isMentioned = mentionedDocIds?.has(doc.id) ?? false; const canInteract = isSelectable(doc); - const handleRowClick = () => { + const handleRowToggle = () => { if (canInteract && onToggleChatMention) { onToggleChatMention(doc, isMentioned); } }; + const handleRowClick = (e: React.MouseEvent) => { + if (e.ctrlKey || e.metaKey) { + e.preventDefault(); + e.stopPropagation(); + handleViewMetadata(doc); + return; + } + handleRowToggle(); + }; return ( handleRowClick()} + onCheckedChange={() => handleRowToggle()} disabled={!canInteract} aria-label={isMentioned ? "Remove from chat" : "Add to chat"} className={`border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary ${!canInteract ? "opacity-40 cursor-not-allowed" : ""}`} @@ -690,7 +718,13 @@ export function DocumentsTableShell({ {sorted.map((doc) => { const isMentioned = mentionedDocIds?.has(doc.id) ?? false; const canInteract = isSelectable(doc); - const handleCardClick = () => { + const handleCardClick = (e?: React.MouseEvent) => { + if (e && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + e.stopPropagation(); + handleViewMetadata(doc); + return; + } if (canInteract && onToggleChatMention) { onToggleChatMention(doc, isMentioned); } @@ -776,6 +810,21 @@ export function DocumentsTableShell({ + {/* Document Metadata Viewer (Ctrl+Click) */} + { + if (!open) { + setMetadataDoc(null); + setMetadataJson(null); + setMetadataLoading(false); + } + }} + /> + {/* Delete Confirmation Dialog */} !open && setDeleteDoc(null)}>