From e24abd7eac396ae4332410d97d101049ff5716e9 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:49:41 +0530 Subject: [PATCH] refactor(sidebar): streamline DocumentsSidebar and FolderTreeView for improved performance - Removed unnecessary filtering logic in FolderTreeView to simplify document rendering. - Updated DocumentsSidebar to eliminate redundant props and enhance layout consistency. - Adjusted Sidebar component to conditionally render DocumentsSidebar based on the documentsPanel state. --- .../components/documents/FolderTreeView.tsx | 20 +- .../layout/ui/shell/LayoutShell.tsx | 10 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 947 ++---------------- .../layout/ui/sidebar/MobileSidebar.tsx | 9 + .../components/layout/ui/sidebar/Sidebar.tsx | 6 +- 5 files changed, 102 insertions(+), 890 deletions(-) diff --git a/surfsense_web/components/documents/FolderTreeView.tsx b/surfsense_web/components/documents/FolderTreeView.tsx index daa23408f..ed06abcee 100644 --- a/surfsense_web/components/documents/FolderTreeView.tsx +++ b/surfsense_web/components/documents/FolderTreeView.tsx @@ -260,16 +260,6 @@ export function FolderTreeView({ const nodes: React.ReactNode[] = []; - if (parentId === null) { - const processingDocs = childDocs.filter((d) => { - const state = d.status?.state; - return state === "pending" || state === "processing"; - }); - for (const d of processingDocs) { - nodes.push(renderDocumentNode(d, depth)); - } - } - for (let i = 0; i < visibleFolders.length; i++) { const f = visibleFolders[i]; const siblingPositions = { @@ -314,15 +304,7 @@ export function FolderTreeView({ } } - const remainingDocs = - parentId === null - ? childDocs.filter((d) => { - const state = d.status?.state; - return state !== "pending" && state !== "processing"; - }) - : childDocs; - - for (const d of remainingDocs) { + for (const d of childDocs) { nodes.push(renderDocumentNode(d, depth)); } diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index 870a87407..ea63d3583 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -27,7 +27,6 @@ import { RightPanelToggleButton, } from "../right-panel/RightPanel"; import { - DocumentsSidebar, InboxSidebarContent, MobileSidebar, MobileSidebarTrigger, @@ -313,6 +312,7 @@ export function LayoutShell({ onChatArchive={onChatArchive} onViewAllChats={onViewAllChats} isAllChatsActive={isAllChatsPage} + documentsPanel={documentsPanel} user={user} onSettings={onSettings} onManageMembers={onManageMembers} @@ -366,14 +366,6 @@ export function LayoutShell({ )} - - {/* Mobile Documents Sidebar - separate (not part of slide-out group) */} - {documentsPanel && ( - - )} diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 234ce7124..60cb6659f 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -2,42 +2,18 @@ import { useQuery } from "@rocicorp/zero/react"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { - ChevronLeft, - ChevronRight, - FileText, - FolderClock, - FolderPlus, - Laptop, - ListFilter, - Lock, - Paperclip, - Server, - SlidersVertical, - Trash2, - Upload, - X, -} from "lucide-react"; -import dynamic from "next/dynamic"; -import Link from "next/link"; +import { FolderPlus, ListFilter, SlidersVertical, Trash2 } from "lucide-react"; import { useParams } from "next/navigation"; import { useTranslations } from "next-intl"; -import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom"; import { makeFolderMention, mentionedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom"; import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms"; import { expandedFolderIdsAtom } from "@/atoms/documents/folder.atoms"; import { agentCreatedDocumentsAtom } from "@/atoms/documents/ui.atoms"; import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom"; -import { - folderWatchDialogOpenAtom, - folderWatchInitialFolderAtom, -} from "@/atoms/folder-sync/folder-sync.atoms"; import { CreateFolderDialog } from "@/components/documents/CreateFolderDialog"; import type { DocumentNodeDoc } from "@/components/documents/DocumentNode"; -import { DocumentsFilters } from "@/components/documents/DocumentsFilters"; import { getDocumentTypeIcon } from "@/components/documents/DocumentTypeIcon"; import type { FolderDisplay } from "@/components/documents/FolderNode"; import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog"; @@ -45,10 +21,7 @@ import { FolderTreeView } from "@/components/documents/FolderTreeView"; import { VersionHistoryDialog } from "@/components/documents/version-history"; import { useRuntimeConfig } from "@/components/providers/runtime-config"; import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems"; -import { - DEFAULT_EXCLUDE_PATTERNS, - FolderWatchDialog, -} from "@/components/sources/FolderWatchDialog"; +import { DEFAULT_EXCLUDE_PATTERNS } from "@/components/sources/FolderWatchDialog"; import { AlertDialog, AlertDialogAction, @@ -60,7 +33,6 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; -import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer"; import { DropdownMenu, DropdownMenuCheckboxItem, @@ -73,15 +45,10 @@ import { } from "@/components/ui/dropdown-menu"; import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useAnonymousMode, useIsAnonymous } from "@/contexts/anonymous-mode"; import { useLoginGate } from "@/contexts/login-gate"; import type { DocumentTypeEnum } from "@/contracts/types/document.types"; -import { useDebouncedValue } from "@/hooks/use-debounced-value"; -import { useMediaQuery } from "@/hooks/use-media-query"; import { useElectronAPI, usePlatform } from "@/hooks/use-platform"; -import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { foldersApiService } from "@/lib/apis/folders-api.service"; import { authenticatedFetch } from "@/lib/auth-fetch"; @@ -93,12 +60,6 @@ import { getWorkspaceIdNumber } from "@/lib/route-params"; import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; import { queries } from "@/zero/queries/index"; import { SidebarSection } from "./SidebarSection"; -import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel"; - -const DesktopLocalTabContent = dynamic( - () => import("./DesktopLocalTabContent").then((mod) => mod.DesktopLocalTabContent), - { ssr: false } -); const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = ["USER_MEMORY", "TEAM_MEMORY"]; const MEMORY_DOCUMENTS: DocumentNodeDoc[] = [ @@ -202,9 +163,6 @@ export function EmbeddedDocumentsMenu({ ); } -const LOCAL_FILESYSTEM_TRUST_KEY = "surfsense.local-filesystem-trust.v1"; -const MAX_LOCAL_FILESYSTEM_ROOTS = 10; - function CloudDocumentsSkeleton() { const rows = [ { id: "row-1", widthClass: "w-44" }, @@ -231,12 +189,6 @@ function CloudDocumentsSkeleton() { ); } -type FilesystemSettings = { - mode: "cloud" | "desktop_local_folder"; - localRootPaths: string[]; - updatedAt: string; -}; - interface WatchedFolderEntry { path: string; name: string; @@ -248,14 +200,8 @@ interface WatchedFolderEntry { } interface DocumentsSidebarProps { - open: boolean; - onOpenChange: (open: boolean) => void; - isDocked?: boolean; - onDockedChange?: (docked: boolean) => void; /** When true, renders content without any wrapper — parent provides the container */ embedded?: boolean; - /** Optional action element rendered in the header row (e.g. collapse button) */ - headerAction?: React.ReactNode; } export function DocumentsSidebar(props: DocumentsSidebarProps) { @@ -280,162 +226,19 @@ function AuthenticatedWebDocumentsSidebar(props: DocumentsSidebarProps) { } function AuthenticatedDocumentsSidebarBase({ - open, - onOpenChange, - isDocked = false, - onDockedChange, embedded = false, - headerAction, desktopFeaturesEnabled, }: DocumentsSidebarProps & { desktopFeaturesEnabled: boolean }) { const t = useTranslations("documents"); - const tSidebar = useTranslations("sidebar"); const params = useParams(); - const isMobile = !useMediaQuery("(min-width: 640px)"); const platformElectronAPI = useElectronAPI(); const electronAPI = desktopFeaturesEnabled ? platformElectronAPI : null; const { etlService } = useRuntimeConfig(); const searchSpaceId = getWorkspaceIdNumber(params) ?? 0; const openEditorPanel = useSetAtom(openEditorPanelAtom); - const { data: agentFlags } = useAtomValue(agentFlagsAtom); - const [search, setSearch] = useState(""); - const debouncedSearch = useDebouncedValue(search, 250); const [activeTypes, setActiveTypes] = useState([]); - const [filesystemSettings, setFilesystemSettings] = useState(null); - const [localTrustDialogOpen, setLocalTrustDialogOpen] = useState(false); - const [pendingLocalPath, setPendingLocalPath] = useState(null); const [watchedFolderIds, setWatchedFolderIds] = useState>(new Set()); - const [folderWatchOpen, setFolderWatchOpen] = useAtom(folderWatchDialogOpenAtom); - const [watchInitialFolder, setWatchInitialFolder] = useAtom(folderWatchInitialFolderAtom); - const localFilesystemEnabled = agentFlags?.enable_desktop_local_filesystem === true; - const isElectron = - desktopFeaturesEnabled && typeof window !== "undefined" && !!window.electronAPI; - - useEffect(() => { - if (!electronAPI?.getAgentFilesystemSettings) return; - let mounted = true; - electronAPI - .getAgentFilesystemSettings(searchSpaceId) - .then((settings: FilesystemSettings) => { - if (!mounted) return; - setFilesystemSettings(settings); - }) - .catch(() => { - if (!mounted) return; - setFilesystemSettings({ - mode: "cloud", - localRootPaths: [], - updatedAt: new Date().toISOString(), - }); - }); - return () => { - mounted = false; - }; - }, [electronAPI, searchSpaceId]); - - const hasLocalFilesystemTrust = useCallback(() => { - try { - return window.localStorage.getItem(LOCAL_FILESYSTEM_TRUST_KEY) === "true"; - } catch { - return false; - } - }, []); - - const localRootPaths = filesystemSettings?.localRootPaths ?? []; - const canAddMoreLocalRoots = localRootPaths.length < MAX_LOCAL_FILESYSTEM_ROOTS; - - const applyLocalRootPath = useCallback( - async (path: string) => { - if (!electronAPI?.setAgentFilesystemSettings) return; - const nextLocalRootPaths = [path, ...localRootPaths] - .filter((rootPath, index, allPaths) => allPaths.indexOf(rootPath) === index) - .slice(0, MAX_LOCAL_FILESYSTEM_ROOTS); - if (nextLocalRootPaths.length === localRootPaths.length) return; - const updated = await electronAPI.setAgentFilesystemSettings( - { - mode: "desktop_local_folder", - localRootPaths: nextLocalRootPaths, - }, - searchSpaceId - ); - setFilesystemSettings(updated); - }, - [electronAPI, localRootPaths, searchSpaceId] - ); - - const runPickLocalRoot = useCallback(async () => { - if (!electronAPI?.pickAgentFilesystemRoot) return; - const picked = await electronAPI.pickAgentFilesystemRoot(); - if (!picked) return; - await applyLocalRootPath(picked); - }, [applyLocalRootPath, electronAPI]); - - const handlePickFilesystemRoot = useCallback(async () => { - if (!canAddMoreLocalRoots) return; - if (hasLocalFilesystemTrust()) { - await runPickLocalRoot(); - return; - } - if (!electronAPI?.pickAgentFilesystemRoot) return; - const picked = await electronAPI.pickAgentFilesystemRoot(); - if (!picked) return; - setPendingLocalPath(picked); - setLocalTrustDialogOpen(true); - }, [canAddMoreLocalRoots, electronAPI, hasLocalFilesystemTrust, runPickLocalRoot]); - - const handleRemoveFilesystemRoot = useCallback( - async (rootPathToRemove: string) => { - if (!electronAPI?.setAgentFilesystemSettings) return; - const updated = await electronAPI.setAgentFilesystemSettings( - { - mode: "desktop_local_folder", - localRootPaths: localRootPaths.filter((rootPath) => rootPath !== rootPathToRemove), - }, - searchSpaceId - ); - setFilesystemSettings(updated); - }, - [electronAPI, localRootPaths, searchSpaceId] - ); - - const handleClearFilesystemRoots = useCallback(async () => { - if (!electronAPI?.setAgentFilesystemSettings) return; - const updated = await electronAPI.setAgentFilesystemSettings( - { - mode: "desktop_local_folder", - localRootPaths: [], - }, - searchSpaceId - ); - setFilesystemSettings(updated); - }, [electronAPI, searchSpaceId]); - - const handleFilesystemTabChange = useCallback( - async (tab: "cloud" | "local") => { - if (!electronAPI?.setAgentFilesystemSettings) return; - const updated = await electronAPI.setAgentFilesystemSettings( - { - mode: tab === "cloud" ? "cloud" : "desktop_local_folder", - }, - searchSpaceId - ); - setFilesystemSettings(updated); - }, - [electronAPI, searchSpaceId] - ); - - const handleWatchLocalFolder = useCallback(async () => { - const api = window.electronAPI; - if (!api?.selectFolder) return; - - const folderPath = await api.selectFolder(); - if (!folderPath) return; - - const folderName = folderPath.split("/").pop() || folderPath.split("\\").pop() || folderPath; - setWatchInitialFolder({ path: folderPath, name: folderName }); - setFolderWatchOpen(true); - }, [setWatchInitialFolder, setFolderWatchOpen]); const refreshWatchedIds = useCallback(async () => { if (!electronAPI?.getWatchedFolders) return; @@ -1021,12 +824,6 @@ function AuthenticatedDocumentsSidebarBase({ [treeDocuments] ); - const searchFilteredDocuments = useMemo(() => { - const query = debouncedSearch.trim().toLowerCase(); - if (!query) return treeDocumentsWithMemory; - return treeDocumentsWithMemory.filter((d) => d.title.toLowerCase().includes(query)); - }, [treeDocumentsWithMemory, debouncedSearch]); - const openMemoryDocument = useCallback( (doc: DocumentNodeDoc) => { if (doc.document_type === "USER_MEMORY") { @@ -1162,30 +959,13 @@ function AuthenticatedDocumentsSidebarBase({ [deleteDocumentMutation, t, setSidebarDocs] ); - useEffect(() => { - const handleEscape = (e: KeyboardEvent) => { - if (e.key === "Escape" && open && isMobile) { - onOpenChange(false); - } - }; - document.addEventListener("keydown", handleEscape); - return () => document.removeEventListener("keydown", handleEscape); - }, [open, onOpenChange, isMobile]); - - const showFilesystemTabs = - !isMobile && !!electronAPI && !!filesystemSettings && localFilesystemEnabled; - const currentFilesystemTab = - localFilesystemEnabled && filesystemSettings?.mode === "desktop_local_folder" - ? "local" - : "cloud"; const showCloudSkeleton = - currentFilesystemTab === "cloud" && - (zeroFoldersResult.type !== "complete" || zeroAllDocsResult.type !== "complete"); + zeroFoldersResult.type !== "complete" || zeroAllDocsResult.type !== "complete"; const renderDocumentTree = ({ - documents = searchFilteredDocuments, + documents = treeDocumentsWithMemory, activeTypesForTree = activeTypes, - searchQuery = debouncedSearch.trim() || undefined, + searchQuery, }: { documents?: DocumentNodeDoc[]; activeTypesForTree?: DocumentTypeEnum[]; @@ -1257,303 +1037,6 @@ function AuthenticatedDocumentsSidebarBase({ ); - const cloudContent = ( - <> - {isElectron && ( - - )} - -
-
- handleCreateFolder(null)} - /> -
- - {renderDocumentTree()} -
- - ); - - const localContent = ( - { - openEditorPanel({ - kind: "local_file", - localFilePath, - title: localFilePath.split("/").pop() || localFilePath, - searchSpaceId, - }); - }} - electronAvailable={!!electronAPI} - /> - ); - - const documentsContent = ( - <> -
-
-
- {isMobile && ( - - )} -

{t("title") || "Documents"}

- {showFilesystemTabs && ( - { - void handleFilesystemTabChange(value === "local" ? "local" : "cloud"); - }} - > - - - - Cloud - - - - Local - - - - )} -
-
- {!isMobile && onDockedChange && ( - - - - - - {isDocked ? "Collapse panel" : "Expand panel"} - - - )} - {headerAction} -
-
-
- {showFilesystemTabs ? ( - { - void handleFilesystemTabChange(value === "local" ? "local" : "cloud"); - }} - className="flex min-h-0 flex-1 flex-col" - > - - {cloudContent} - - - {currentFilesystemTab === "local" ? localContent : null} - - - ) : ( - cloudContent - )} - - {versionDocId !== null && ( - { - if (!open) setVersionDocId(null); - }} - documentId={versionDocId} - /> - )} - - {isElectron && ( - { - setFolderWatchOpen(nextOpen); - if (!nextOpen) setWatchInitialFolder(null); - }} - searchSpaceId={searchSpaceId} - initialFolder={watchInitialFolder} - onSuccess={refreshWatchedIds} - /> - )} - { - setLocalTrustDialogOpen(nextOpen); - if (!nextOpen) setPendingLocalPath(null); - }} - > - - - Trust this workspace? - - Local mode can read and edit files inside the folders you select. Continue only if you - trust this workspace and its contents. - - {pendingLocalPath && ( - - Folder path: {pendingLocalPath} - - )} - - - Cancel - { - try { - window.localStorage.setItem(LOCAL_FILESYSTEM_TRUST_KEY, "true"); - } catch {} - setLocalTrustDialogOpen(false); - const path = pendingLocalPath; - setPendingLocalPath(null); - if (path) { - await applyLocalRootPath(path); - } else { - await runPickLocalRoot(); - } - }} - > - I trust this workspace - - - - - - - - - - !open && !isBulkDeleting && setBulkDeleteConfirmOpen(false)} - > - - - - Delete {deletableSelectedIds.length} document - {deletableSelectedIds.length !== 1 ? "s" : ""}? - - - This action cannot be undone.{" "} - {deletableSelectedIds.length === 1 - ? "This document" - : `These ${deletableSelectedIds.length} documents`}{" "} - will be permanently deleted from your search space. - - - - Cancel - { - e.preventDefault(); - handleBulkDeleteSelected(); - }} - disabled={isBulkDeleting} - className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90" - > - Delete - {isBulkDeleting && } - - - - - - { - if (!open) { - setExportWarningOpen(false); - setExportWarningContext(null); - } - }} - > - - - Some documents are still processing - - {exportWarningContext?.pendingCount} document - {exportWarningContext?.pendingCount !== 1 ? "s are" : " is"} currently being processed - and will be excluded from the export. Do you want to continue? - - - - Cancel - - Export anyway - - - - - - ); - if (embedded) { return ( <> @@ -1574,113 +1057,110 @@ function AuthenticatedDocumentsSidebarBase({ {renderDocumentTree()} + {versionDocId !== null && ( + { + if (!open) setVersionDocId(null); + }} + documentId={versionDocId} + /> + )} + + + + + !open && !isBulkDeleting && setBulkDeleteConfirmOpen(false)} + > + + + + Delete {deletableSelectedIds.length} document + {deletableSelectedIds.length !== 1 ? "s" : ""}? + + + This action cannot be undone.{" "} + {deletableSelectedIds.length === 1 + ? "This document" + : `These ${deletableSelectedIds.length} documents`}{" "} + will be permanently deleted from your search space. + + + + Cancel + { + e.preventDefault(); + handleBulkDeleteSelected(); + }} + disabled={isBulkDeleting} + className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + Delete + {isBulkDeleting && } + + + + + + { + if (!open) { + setExportWarningOpen(false); + setExportWarningContext(null); + } + }} + > + + + Some documents are still processing + + {exportWarningContext?.pendingCount} document + {exportWarningContext?.pendingCount !== 1 ? "s are" : " is"} currently being + processed and will be excluded from the export. Do you want to continue? + + + + Cancel + + Export anyway + + + + ); } - if (isDocked && open && !isMobile) { - return ( - - ); - } - - return ( - - {documentsContent} - - ); + return null; } // --------------------------------------------------------------------------- // Anonymous Documents Sidebar // --------------------------------------------------------------------------- -const ANON_ALLOWED_EXTENSIONS = new Set([ - ".md", - ".markdown", - ".txt", - ".text", - ".json", - ".jsonl", - ".yaml", - ".yml", - ".toml", - ".ini", - ".cfg", - ".conf", - ".xml", - ".css", - ".scss", - ".py", - ".js", - ".jsx", - ".ts", - ".tsx", - ".java", - ".kt", - ".go", - ".rs", - ".rb", - ".php", - ".c", - ".h", - ".cpp", - ".hpp", - ".cs", - ".swift", - ".sh", - ".sql", - ".log", - ".rst", - ".tex", - ".vue", - ".svelte", - ".astro", - ".tf", - ".proto", - ".csv", - ".tsv", - ".html", - ".htm", - ".xhtml", -]); - -const ANON_ACCEPT = Array.from(ANON_ALLOWED_EXTENSIONS).join(","); - -function AnonymousDocumentsSidebar({ - open, - onOpenChange, - isDocked = false, - onDockedChange, - embedded = false, - headerAction, -}: DocumentsSidebarProps) { +function AnonymousDocumentsSidebar({ embedded = false }: DocumentsSidebarProps) { const t = useTranslations("documents"); - const tSidebar = useTranslations("sidebar"); - const isMobile = !useMediaQuery("(min-width: 640px)"); const anonMode = useAnonymousMode(); const { gate } = useLoginGate(); - const fileInputRef = useRef(null); - const [isUploading, setIsUploading] = useState(false); - const [search, setSearch] = useState(""); - const [sidebarDocs, setSidebarDocs] = useAtom(mentionedDocumentsAtom); const mentionedDocKeys = useMemo( () => new Set(sidebarDocs.map((d) => getMentionDocKey(d))), @@ -1713,51 +1193,6 @@ function AnonymousDocumentsSidebar({ const uploadedDoc = anonMode.isAnonymous ? anonMode.uploadedDoc : null; const hasDoc = uploadedDoc !== null; - const handleAnonUploadClick = useCallback(() => { - if (hasDoc) { - gate("upload more documents"); - return; - } - fileInputRef.current?.click(); - }, [hasDoc, gate]); - - const handleFileChange = useCallback( - async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - e.target.value = ""; - - const ext = `.${file.name.split(".").pop()?.toLowerCase()}`; - if (!ANON_ALLOWED_EXTENSIONS.has(ext)) { - gate("upload PDFs, Word documents, images, and more"); - return; - } - - setIsUploading(true); - try { - const result = await anonymousChatApiService.uploadDocument(file); - if (!result.ok) { - if (result.reason === "quota_exceeded") gate("upload more documents"); - return; - } - const data = result.data; - if (anonMode.isAnonymous) { - anonMode.setUploadedDoc({ - filename: data.filename, - sizeBytes: data.size_bytes, - }); - } - toast.success(`Uploaded "${data.filename}"`); - } catch (err) { - console.error("Upload failed:", err); - toast.error(err instanceof Error ? err.message : "Upload failed"); - } finally { - setIsUploading(false); - } - }, - [gate, anonMode] - ); - const handleRemoveDoc = useCallback(() => { if (anonMode.isAnonymous) { anonMode.setUploadedDoc(null); @@ -1777,176 +1212,6 @@ function AnonymousDocumentsSidebar({ ]; }, [anonMode]); - const searchFilteredDocs = useMemo(() => { - const q = search.trim().toLowerCase(); - if (!q) return treeDocuments; - return treeDocuments.filter((d) => d.title.toLowerCase().includes(q)); - }, [treeDocuments, search]); - - useEffect(() => { - const handleEscape = (e: KeyboardEvent) => { - if (e.key === "Escape" && open && isMobile) { - onOpenChange(false); - } - }; - document.addEventListener("keydown", handleEscape); - return () => document.removeEventListener("keydown", handleEscape); - }, [open, onOpenChange, isMobile]); - - const documentsContent = ( - <> - - - {/* Header */} -
-
-
-

{t("title") || "Documents"}

-
-
- {isMobile && ( - - )} - {!isMobile && onDockedChange && ( - - - - - - {isDocked ? "Collapse panel" : "Expand panel"} - - - )} - {headerAction} -
-
-
- - {/* Filters & upload */} -
-
- {}} - activeTypes={[]} - onCreateFolder={() => gate("create folders")} - /> -
- -
- {}} - mentionedDocKeys={mentionedDocKeys} - onToggleChatMention={handleToggleChatMention} - onToggleFolderSelect={() => {}} - onRenameFolder={() => gate("rename folders")} - onDeleteFolder={() => gate("delete folders")} - onMoveFolder={() => gate("organize folders")} - onCreateFolder={() => gate("create folders")} - searchQuery={search.trim() || undefined} - onPreviewDocument={() => gate("preview documents")} - onEditDocument={() => gate("edit documents")} - onDeleteDocument={async () => { - handleRemoveDoc(); - setSidebarDocs((prev) => prev.filter((d) => d.kind !== "doc" || d.id !== -1)); - return true; - }} - onMoveDocument={() => gate("organize documents")} - onExportDocument={() => gate("export documents")} - onVersionHistory={() => gate("view version history")} - activeTypes={[]} - onDropIntoFolder={async () => gate("organize documents")} - onReorderFolder={async () => gate("organize folders")} - watchedFolderIds={new Set()} - onRescanFolder={() => gate("watch local folders")} - onStopWatchingFolder={() => gate("watch local folders")} - onExportFolder={() => gate("export folders")} - /> - - {!hasDoc && ( -
- -

- Text, code, CSV, and HTML files only. Create an account for PDFs, images, and 30+ - connectors. -

-
- )} -
-
- - {/* CTA footer */} -
-
- - Create an account to unlock: -
-
    -
  • - PDF, Word, images, audio uploads -
  • -
  • - Unlimited documents -
  • -
- -
- - ); - if (embedded) { return ( - {documentsContent} - - ); - } - - if (isMobile) { - return ( - - - {t("title") || "Documents"} - -
{documentsContent}
-
-
- ); - } - - return ( - - {documentsContent} - - ); + return null; } diff --git a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx index 40a563ab2..22305e014 100644 --- a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx @@ -28,6 +28,10 @@ interface MobileSidebarProps { onChatArchive?: (chat: ChatItem) => void; onViewAllChats?: () => void; isAllChatsActive?: boolean; + documentsPanel?: { + open: boolean; + onOpenChange: (open: boolean) => void; + }; user: User; onSettings?: () => void; onManageMembers?: () => void; @@ -76,6 +80,7 @@ export function MobileSidebar({ onChatArchive, onViewAllChats, isAllChatsActive = false, + documentsPanel, user, onSettings, onManageMembers, @@ -94,6 +99,9 @@ export function MobileSidebar({ const handleNavItemClick = (item: NavItem) => { onNavItemClick?.(item); + if (item.url === "#documents") { + return; + } onOpenChange(false); }; @@ -167,6 +175,7 @@ export function MobileSidebar({ : undefined } isAllChatsActive={isAllChatsActive} + documentsPanel={documentsPanel} user={user} onSettings={ onSettings diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 6477e27a8..2e784298c 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -337,11 +337,7 @@ export function Sidebar({
{documentsPanel?.open ? (
- +
) : null}