From 3d42712b3f2befd6abd2d4241c78f50de119d435 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 14 May 2026 14:17:44 +0530 Subject: [PATCH] refactor: replace button elements with Button component for improved consistency and styling across multiple UI components --- .../components/CommunityPromptsContent.tsx | 7 +- .../components/DesktopShortcutsContent.tsx | 9 +- .../components/PromptsContent.tsx | 15 +- .../app/desktop/permissions/page.tsx | 7 +- .../agent-action-log/action-log-item.tsx | 7 +- .../components/assistant-ui/image.tsx | 134 ++++++------------ .../connectors/google-drive-folder-tree.tsx | 23 +-- .../components/documents/DocumentsFilters.tsx | 8 +- .../documents/FolderPickerDialog.tsx | 71 +++++----- .../components/documents/version-history.tsx | 7 +- .../editor/plugins/citation-kit.tsx | 55 +++---- .../components/free-chat/free-composer.tsx | 8 +- .../free-chat/quota-warning-banner.tsx | 9 +- surfsense_web/components/homepage/cta.tsx | 16 ++- .../components/homepage/hero-section.tsx | 76 +++++----- surfsense_web/components/homepage/navbar.tsx | 15 +- .../layout/ui/icon-rail/SearchSpaceAvatar.tsx | 9 +- .../ui/sidebar/AllSharedChatsSidebar.tsx | 14 +- .../layout/ui/sidebar/ChatListItem.tsx | 7 +- .../layout/ui/sidebar/InboxSidebar.tsx | 49 ++++--- .../components/layout/ui/sidebar/Sidebar.tsx | 15 +- .../layout/ui/sidebar/SidebarButton.tsx | 6 - .../layout/ui/sidebar/SidebarUserProfile.tsx | 17 ++- .../components/layout/ui/tabs/TabBar.tsx | 66 ++++----- .../components/new-chat/chat-share-button.tsx | 34 ++--- .../components/sources/DocumentUploadTab.tsx | 82 ++++++----- surfsense_web/components/ui/link-toolbar.tsx | 59 +++----- 27 files changed, 401 insertions(+), 424 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx index 239832b2d..8fe8f40ed 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx @@ -96,13 +96,14 @@ export function CommunityPromptsContent() { {prompt.prompt}

{prompt.prompt.length > 100 && ( - + )} )} - + ); diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx index aa7aa3ff0..a3c824ea0 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx @@ -277,22 +277,25 @@ export function PromptsContent() { {prompt.prompt}

{prompt.prompt.length > 100 && ( - + )}
- + - + )}
diff --git a/surfsense_web/components/agent-action-log/action-log-item.tsx b/surfsense_web/components/agent-action-log/action-log-item.tsx index f951cd5da..954ee9aa5 100644 --- a/surfsense_web/components/agent-action-log/action-log-item.tsx +++ b/surfsense_web/components/agent-action-log/action-log-item.tsx @@ -74,10 +74,11 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt isAlreadyReverted && "opacity-70" )} > - + {isExpanded && (
diff --git a/surfsense_web/components/assistant-ui/image.tsx b/surfsense_web/components/assistant-ui/image.tsx index 59781abcf..6cba01c50 100644 --- a/surfsense_web/components/assistant-ui/image.tsx +++ b/surfsense_web/components/assistant-ui/image.tsx @@ -4,8 +4,9 @@ import type { ImageMessagePartComponent } from "@assistant-ui/react"; import { cva, type VariantProps } from "class-variance-authority"; import { ImageIcon, ImageOffIcon } from "lucide-react"; import NextImage from "next/image"; -import { memo, type PropsWithChildren, useEffect, useRef, useState } from "react"; +import { memo, type PropsWithChildren, useEffect, useState } from "react"; import { createPortal } from "react-dom"; +import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; const imageVariants = cva("aui-image-root relative overflow-hidden rounded-lg", { @@ -44,8 +45,14 @@ function ImageRoot({ className, variant, size, children, ...props }: ImageRootPr ); } -type ImagePreviewProps = Omit, "children"> & { +type ImagePreviewProps = Omit< + React.ComponentProps<"img">, + "children" | "height" | "onError" | "onLoad" | "src" | "width" +> & { containerClassName?: string; + onError?: React.ReactEventHandler; + onLoad?: React.ReactEventHandler; + src?: string; }; function ImagePreview({ @@ -57,18 +64,17 @@ function ImagePreview({ src, ...props }: ImagePreviewProps) { - const imgRef = useRef(null); const [loadedSrc, setLoadedSrc] = useState(undefined); const [errorSrc, setErrorSrc] = useState(undefined); + const imageSrc = src ?? ""; - const loaded = loadedSrc === src; - const error = errorSrc === src; + const loaded = imageSrc !== "" && loadedSrc === imageSrc; + const error = imageSrc === "" || errorSrc === imageSrc; useEffect(() => { - if (typeof src === "string" && imgRef.current?.complete && imgRef.current.naturalWidth > 0) { - setLoadedSrc(src); - } - }, [src]); + setLoadedSrc((current) => (current === imageSrc ? current : undefined)); + setErrorSrc((current) => (current === imageSrc ? current : undefined)); + }, [imageSrc]); return (
@@ -87,55 +93,22 @@ function ImagePreview({ >
- ) : isDataOrBlobUrl(src) ? ( - // biome-ignore lint/performance/noImgElement: data/blob URLs need plain img - {alt} { - if (typeof src === "string") setLoadedSrc(src); - onLoad?.(e); - }} - onError={(e) => { - if (typeof src === "string") setErrorSrc(src); - onError?.(e); - }} - {...props} - /> ) : ( - // biome-ignore lint/performance/noImgElement: intentional for dynamic external URLs - // {alt} { - // if (typeof src === "string") setLoadedSrc(src); - // onLoad?.(e); - // }} - // onError={(e) => { - // if (typeof src === "string") setErrorSrc(src); - // onError?.(e); - // }} - // {...props} - // /> { - if (typeof src === "string") setLoadedSrc(src); - onLoad?.(); + onLoad={(event) => { + setLoadedSrc(imageSrc); + onLoad?.(event); }} - onError={() => { - if (typeof src === "string") setErrorSrc(src); - onError?.(); + onError={(event) => { + setErrorSrc(imageSrc); + onError?.(event); }} - unoptimized={false} + unoptimized={isDataOrBlobUrl(imageSrc)} {...props} /> )} @@ -196,59 +169,40 @@ function ImageZoom({ src, alt = "Image preview", children }: ImageZoomProps) { return ( <> - + {isMounted && isOpen && createPortal( - , + { + e.stopPropagation(); + handleClose(); + }} + unoptimized={isDataOrBlobUrl(src)} + /> + , document.body )} diff --git a/surfsense_web/components/connectors/google-drive-folder-tree.tsx b/surfsense_web/components/connectors/google-drive-folder-tree.tsx index 8146a4bf8..e47d1d185 100644 --- a/surfsense_web/components/connectors/google-drive-folder-tree.tsx +++ b/surfsense_web/components/connectors/google-drive-folder-tree.tsx @@ -13,6 +13,7 @@ import { Presentation, } from "lucide-react"; import { useState } from "react"; +import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Spinner } from "@/components/ui/spinner"; @@ -243,9 +244,11 @@ export function GoogleDriveFolderTree({ )} > {isFolder ? ( - + ) : ( )} @@ -290,13 +293,14 @@ export function GoogleDriveFolderTree({
{isFolder ? ( - + ) : ( {item.name} @@ -332,13 +336,14 @@ export function GoogleDriveFolderTree({ className="shrink-0 h-3.5 w-3.5 sm:h-4 sm:w-4 border-slate-400/20 dark:border-white/20" /> - + diff --git a/surfsense_web/components/documents/DocumentsFilters.tsx b/surfsense_web/components/documents/DocumentsFilters.tsx index 06be4a55c..86e91ee42 100644 --- a/surfsense_web/components/documents/DocumentsFilters.tsx +++ b/surfsense_web/components/documents/DocumentsFilters.tsx @@ -241,9 +241,11 @@ export function DocumentsFilters({ aria-label={t("filter_placeholder")} /> {Boolean(searchValue) && ( - + )} diff --git a/surfsense_web/components/documents/FolderPickerDialog.tsx b/surfsense_web/components/documents/FolderPickerDialog.tsx index 3359f7499..13cff6ace 100644 --- a/surfsense_web/components/documents/FolderPickerDialog.tsx +++ b/surfsense_web/components/documents/FolderPickerDialog.tsx @@ -85,42 +85,47 @@ export function FolderPickerDialog({ const FolderIcon = isExpanded ? FolderOpen : Folder; return [ - - ) : ( - + )} - - {f.name} - , + + , ...(isExpanded ? renderPickerLevel(f.id, depth + 1) : []), ]; }); @@ -143,19 +148,19 @@ export function FolderPickerDialog({
- + {renderPickerLevel(null, 1)}
diff --git a/surfsense_web/components/documents/version-history.tsx b/surfsense_web/components/documents/version-history.tsx index 453c02817..498e89495 100644 --- a/surfsense_web/components/documents/version-history.tsx +++ b/surfsense_web/components/documents/version-history.tsx @@ -177,12 +177,13 @@ function VersionHistoryPanel({ documentId }: { documentId: number }) {
{versions.map((v) => ( -
- + ))}
diff --git a/surfsense_web/components/editor/plugins/citation-kit.tsx b/surfsense_web/components/editor/plugins/citation-kit.tsx index 1908de209..97e8ec723 100644 --- a/surfsense_web/components/editor/plugins/citation-kit.tsx +++ b/surfsense_web/components/editor/plugins/citation-kit.tsx @@ -11,20 +11,16 @@ import { } from "@/lib/citations/citation-parser"; /** - * Plate inline-void node modeling a single `[citation:...]` reference. - * - * Modeled after the existing `MentionPlugin` pattern in - * `inline-mention-editor.tsx` — the only confirmed pattern in this repo - * for non-text inline UI. Inline-void elements satisfy Slate's invariant - * that the editor renders both atomic widgets and surrounding text - * cleanly without breaking selection / caret semantics. + * Plate inline-void node for one `[citation:...]` reference. + * Inline voids keep the citation chip atomic while preserving caret behavior + * around the surrounding text. */ export type CitationElementNode = { type: "citation"; kind: "chunk" | "doc" | "url"; chunkId?: number; url?: string; - /** Original `[citation:...]` substring for traceability/debugging. */ + /** Original literal token that produced this citation node. */ rawText: string; children: [{ text: "" }]; }; @@ -62,17 +58,14 @@ const CitationPlugin = createPlatePlugin({ }, }); -/** Plugin kit shape used elsewhere in the editor. */ export const CitationKit = [CitationPlugin]; // --------------------------------------------------------------------------- -// Slate value transform — runs after MarkdownPlugin.deserialize +// Slate value transform // --------------------------------------------------------------------------- -// Structural shapes used by the value transform. We cannot use Plate's -// generic Element / Text type predicates directly because `Descendant` is a -// constrained union and our predicates would over-narrow. Casting through -// these row types keeps the walker readable without fighting the types. +// Local structural shapes keep the recursive walker readable without forcing +// Plate's broad Descendant union into narrower generic predicates. type SlateText = { text: string } & Record; type SlateElement = { type?: string; children: Descendant[] } & Record; @@ -89,19 +82,15 @@ function asElement(node: Descendant): SlateElement { } /** - * Element types whose subtrees we MUST NOT inject citation void elements - * into. Each rationale documented in the citation plan: - * - `KEYS.codeBlock` / `code_line` — Plate's schema rejects inline elements - * inside code containers; the user expects literal text inside code. - * - `KEYS.link` — ` + {hasUploadedDoc diff --git a/surfsense_web/components/free-chat/quota-warning-banner.tsx b/surfsense_web/components/free-chat/quota-warning-banner.tsx index e013a64a8..828e8006e 100644 --- a/surfsense_web/components/free-chat/quota-warning-banner.tsx +++ b/surfsense_web/components/free-chat/quota-warning-banner.tsx @@ -3,6 +3,7 @@ import { OctagonAlert, Orbit, X } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; +import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; interface QuotaWarningBannerProps { @@ -71,13 +72,15 @@ export function QuotaWarningBanner({ {" "} for $5 of premium credit.

- + ); diff --git a/surfsense_web/components/homepage/cta.tsx b/surfsense_web/components/homepage/cta.tsx index a09d7354c..7e21a7fe2 100644 --- a/surfsense_web/components/homepage/cta.tsx +++ b/surfsense_web/components/homepage/cta.tsx @@ -2,6 +2,7 @@ import { IconMessageCircleQuestion } from "@tabler/icons-react"; import Link from "next/link"; import type React from "react"; +import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; export function CTAHomepage() { @@ -22,15 +23,16 @@ export function CTAHomepage() {

- - - + +
{/*
diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index ec09fa34d..2630f4266 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -4,6 +4,7 @@ import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import Balancer from "react-wrap-balancer"; +import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, @@ -184,24 +185,26 @@ function GetStartedButton() { if (isGoogleAuth) { return ( - + ); } return ( - - Get Started - + Get Started + ); } @@ -212,35 +215,40 @@ function DownloadButton() { if (!primary) { return ( - - - Download for {os} - + + + Download for {os} + + ); } return (
- - - Download for {os} - + + + Download for {os} + + - + {alternatives.map((asset) => ( @@ -284,11 +292,12 @@ const BrowserWindow = () => {
{TAB_ITEMS.map((item, index) => ( - + {index !== TAB_ITEMS.length - 1 && (
)} @@ -354,13 +363,14 @@ const BrowserWindow = () => {

- + +
@@ -385,7 +395,7 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { if (!video) return; video.currentTime = 0; video.play().catch(() => {}); - }, [src]); + }, []); const handleCanPlay = useCallback(() => { setHasLoaded(true); diff --git a/surfsense_web/components/homepage/navbar.tsx b/surfsense_web/components/homepage/navbar.tsx index 04b011c2d..576af2b7d 100644 --- a/surfsense_web/components/homepage/navbar.tsx +++ b/surfsense_web/components/homepage/navbar.tsx @@ -7,6 +7,7 @@ import { SignInButton } from "@/components/auth/sign-in-button"; import { NavbarGitHubStars } from "@/components/homepage/github-stars-badge"; import { Logo } from "@/components/Logo"; import { ThemeTogglerComponent } from "@/components/theme/theme-toggle"; +import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; interface NavItem { @@ -99,7 +100,7 @@ const DesktopNav = ({ navItems, isScrolled, scrolledBgClassName }: DesktopNavPro onMouseEnter={() => setHovered(idx)} onMouseLeave={() => setHovered(null)} className="relative px-4 py-2 text-neutral-600 dark:text-neutral-300" - key={`link=${idx}`} + key={navItem.link} href={navItem.link} > {hovered === idx && ( @@ -179,10 +180,12 @@ const MobileNav = ({ navItems, isScrolled, scrolledBgClassName }: MobileNavProps SurfSense - +
@@ -202,9 +205,9 @@ const MobileNav = ({ navItems, isScrolled, scrolledBgClassName }: MobileNavProps transition={{ duration: 0.2, ease: "easeOut" }} className="absolute inset-x-0 top-full mt-1 z-20 flex w-full flex-col items-start justify-start gap-4 rounded-xl bg-white/90 backdrop-blur-xl border border-white/20 shadow-2xl px-4 py-6 dark:bg-neutral-950/90 dark:border-neutral-800/50" > - {navItems.map((navItem: NavItem, idx: number) => ( + {navItems.map((navItem: NavItem) => ( diff --git a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx index c35d8c708..28fb235fe 100644 --- a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx +++ b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx @@ -3,6 +3,7 @@ import { Settings, Trash2, Users } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; import { ContextMenu, ContextMenuContent, @@ -120,11 +121,13 @@ export function SearchSpaceAvatar({ ); const avatarButton = ( - + ); const menuItems = ( diff --git a/surfsense_web/components/layout/ui/sidebar/AllSharedChatsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/AllSharedChatsSidebar.tsx index d6a25608e..b8073c3c4 100644 --- a/surfsense_web/components/layout/ui/sidebar/AllSharedChatsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/AllSharedChatsSidebar.tsx @@ -347,8 +347,9 @@ export function AllSharedChatsSidebarContent({ return (
{isMobile ? ( - + ) : ( - +

diff --git a/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx b/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx index 4ef7b2965..ea4f946c2 100644 --- a/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx +++ b/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx @@ -56,19 +56,20 @@ export function ChatListItem({ return (

- + {/* Actions dropdown - trigger hidden on mobile, long-press opens it instead */}
- - + {activeTab === "status" && ( - + )}
@@ -642,14 +645,15 @@ export function InboxSidebarContent({ {t("sources") || "Sources"}

- + {statusSourceOptions.map((source) => ( - + ))}
@@ -922,11 +927,12 @@ export function InboxSidebarContent({ {activeTab === "status" ? ( - +

{item.title}

@@ -952,11 +958,12 @@ export function InboxSidebarContent({
) : ( - + )}
diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 3a319347f..7cc65d2b8 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -6,6 +6,7 @@ import { useParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Skeleton } from "@/components/ui/skeleton"; import { useIsAnonymous } from "@/contexts/anonymous-mode"; @@ -204,13 +205,14 @@ export function Sidebar({ alwaysShowAction={!disableTooltips && isSharedChatsPanelOpen} action={ onViewAllSharedChats ? ( - + ) : undefined } > @@ -260,13 +262,14 @@ export function Sidebar({ alwaysShowAction={!disableTooltips && isPrivateChatsPanelOpen} action={ onViewAllPrivateChats ? ( - + ) : undefined } > diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx index 6702cea4a..83ece94ab 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx @@ -12,18 +12,12 @@ interface SidebarButtonProps { isCollapsed?: boolean; isActive?: boolean; badge?: React.ReactNode; - /** Overlay in the top-right corner of the collapsed icon (e.g. status badge) */ collapsedOverlay?: React.ReactNode; - /** Custom icon node for collapsed mode — overrides the default rendering */ collapsedIconNode?: React.ReactNode; - /** Custom icon node for expanded mode — overrides the default rendering */ expandedIconNode?: React.ReactNode; - /** Optional inline trailing content shown in expanded mode */ trailingContent?: React.ReactNode; - /** Optional tooltip content that replaces the default label tooltip */ tooltipContent?: React.ReactNode; className?: string; - /** Extra attributes spread onto the inner + @@ -367,10 +369,11 @@ export function SidebarUserProfile({
- + diff --git a/surfsense_web/components/layout/ui/tabs/TabBar.tsx b/surfsense_web/components/layout/ui/tabs/TabBar.tsx index 6d170409f..3946fdc6d 100644 --- a/surfsense_web/components/layout/ui/tabs/TabBar.tsx +++ b/surfsense_web/components/layout/ui/tabs/TabBar.tsx @@ -10,6 +10,7 @@ import { type Tab, tabsAtom, } from "@/atoms/tabs/tabs.atom"; +import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; interface TabBarProps { @@ -189,22 +190,25 @@ export function TabBar({ )} /> ) : null} - {/* Hover-only gradient + close overlay (sidebar pattern) — keeps pill width fixed and avoids ellipsis shift. */}
- {/* biome-ignore lint/a11y/useSemanticElements: cannot nest button inside button */} - handleTabClose(e, tab.id)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - handleTabClose(e as unknown as React.MouseEvent, tab.id); - } - }} - className="pointer-events-auto rounded-full p-0.5 transition-colors hover:bg-accent hover:text-accent-foreground" + onMouseEnter={() => setHoveredTabIndex(index)} + onMouseLeave={() => setHoveredTabIndex(null)} + className="pointer-events-auto size-4 rounded-full p-0.5 hover:bg-accent hover:text-accent-foreground" > - - + +
- +
); })} @@ -243,14 +243,16 @@ export function TabBar({ "before:bg-gradient-to-r before:from-transparent before:to-panel" )} > - + +
)}
diff --git a/surfsense_web/components/new-chat/chat-share-button.tsx b/surfsense_web/components/new-chat/chat-share-button.tsx index 6e231707e..f8a9f55ff 100644 --- a/surfsense_web/components/new-chat/chat-share-button.tsx +++ b/surfsense_web/components/new-chat/chat-share-button.tsx @@ -143,18 +143,20 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS {hasPublicSnapshots && ( - + + Manage public links @@ -185,14 +187,13 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS const Icon = option.icon; return ( - + ); })} @@ -234,19 +235,18 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
{/* Public Link Option */} - + )}
diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx index 134680b49..a9aec21a1 100644 --- a/surfsense_web/components/sources/DocumentUploadTab.tsx +++ b/surfsense_web/components/sources/DocumentUploadTab.tsx @@ -115,13 +115,15 @@ function buildFolderTree(entries: FolderEntry[]): FolderTreeNode[] { function flattenTree( nodes: FolderTreeNode[], - depth = 0 -): { name: string; isFolder: boolean; depth: number; size?: number }[] { - const items: { name: string; isFolder: boolean; depth: number; size?: number }[] = []; + depth = 0, + parentPath = "" +): { name: string; isFolder: boolean; depth: number; path: string; size?: number }[] { + const items: { name: string; isFolder: boolean; depth: number; path: string; size?: number }[] = []; for (const node of nodes) { - items.push({ name: node.name, isFolder: node.isFolder, depth, size: node.size }); + const path = parentPath ? `${parentPath}/${node.name}` : node.name; + items.push({ name: node.name, isFolder: node.isFolder, depth, path, size: node.size }); if (node.isFolder && node.children.length > 0) { - items.push(...flattenTree(node.children, depth + 1)); + items.push(...flattenTree(node.children, depth + 1, path)); } } return items; @@ -537,37 +539,39 @@ export function DocumentUploadTab({ isElectron ? (
{renderBrowseButton({ compact: true, fullWidth: true })}
) : ( - + ) ) : ( - // biome-ignore lint/a11y/useSemanticElements: cannot use + )}
e.stopPropagation()} @@ -649,9 +653,9 @@ export function DocumentUploadTab({
{folderUpload - ? folderTreeItems.map((item, i) => ( + ? folderTreeItems.map((item) => (
@@ -726,10 +730,11 @@ export function DocumentUploadTab({

{t("processing_mode")}

-
- -
- +
diff --git a/surfsense_web/components/ui/link-toolbar.tsx b/surfsense_web/components/ui/link-toolbar.tsx index ee61e515d..462c716f0 100644 --- a/surfsense_web/components/ui/link-toolbar.tsx +++ b/surfsense_web/components/ui/link-toolbar.tsx @@ -22,7 +22,7 @@ import { } from "platejs/react"; import * as React from "react"; -import { buttonVariants } from "@/components/ui/button"; +import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; const popoverVariants = cva( @@ -116,13 +116,9 @@ export function LinkFloatingToolbar({ state }: { state?: LinkFloatingToolbarStat input ) : (
- + @@ -130,16 +126,9 @@ export function LinkFloatingToolbar({ state }: { state?: LinkFloatingToolbarStat - +
); @@ -158,29 +147,22 @@ export function LinkFloatingToolbar({ state }: { state?: LinkFloatingToolbarStat function LinkOpenButton() { const editor = useEditorRef(); - const selection = useEditorSelection(); + useEditorSelection(); - // biome-ignore lint/correctness/useExhaustiveDependencies: selection triggers recalculation of link attributes - const attributes = React.useMemo(() => { - const entry = editor.api.node({ - match: { type: editor.getType(KEYS.link) }, - }); - if (!entry) { - return {}; - } - const [element] = entry; - return getLinkAttributes(editor, element); - }, [editor, selection]); + const entry = editor.api.node({ + match: { type: editor.getType(KEYS.link) }, + }); + const attributes = entry ? getLinkAttributes(editor, entry[0]) : {}; + const href = typeof attributes.href === "string" ? attributes.href : undefined; return ( - // biome-ignore lint/a11y/noStaticElementInteractions: with spread attributes has dynamic href - // biome-ignore lint/a11y/useAriaPropsSupportedByRole: aria-label needed for icon-only link - { + if (href) window.open(href, "_blank", "noopener,noreferrer"); + }} onMouseOver={(e) => { e.stopPropagation(); }} @@ -188,10 +170,9 @@ function LinkOpenButton() { e.stopPropagation(); }} aria-label="Open link in a new tab" - target="_blank" - rel="noopener noreferrer" + disabled={!href} > - + ); }