mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 09:16:22 +02:00
chore: linting
This commit is contained in:
parent
23b4f91754
commit
64c913baa3
47 changed files with 908 additions and 895 deletions
|
|
@ -45,7 +45,7 @@ export function CreateFolderDialog({
|
|||
onConfirm(trimmed);
|
||||
onOpenChange(false);
|
||||
},
|
||||
[name, onConfirm, onOpenChange],
|
||||
[name, onConfirm, onOpenChange]
|
||||
);
|
||||
|
||||
const isSubfolder = !!parentFolderName;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
Eye,
|
||||
MoreHorizontal,
|
||||
Move,
|
||||
Pencil,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import React, { useCallback, useRef } from "react";
|
||||
import { Eye, MoreHorizontal, Move, Pencil, Trash2 } from "lucide-react";
|
||||
import React, { useCallback } from "react";
|
||||
import { useDrag } from "react-dnd";
|
||||
import { getDocumentTypeIcon } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
|
|
@ -25,7 +20,6 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { getDocumentTypeIcon } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon";
|
||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DND_TYPES } from "./FolderNode";
|
||||
|
|
@ -62,9 +56,7 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
const statusState = doc.status?.state ?? "ready";
|
||||
const isSelectable = statusState !== "pending" && statusState !== "processing";
|
||||
const isEditable =
|
||||
doc.document_type === "NOTE" &&
|
||||
statusState !== "pending" &&
|
||||
statusState !== "processing";
|
||||
doc.document_type === "NOTE" && statusState !== "pending" && statusState !== "processing";
|
||||
|
||||
const handleCheckChange = useCallback(() => {
|
||||
if (isSelectable) {
|
||||
|
|
@ -78,7 +70,7 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
item: { id: doc.id },
|
||||
collect: (monitor) => ({ isDragging: monitor.isDragging() }),
|
||||
}),
|
||||
[doc.id],
|
||||
[doc.id]
|
||||
);
|
||||
|
||||
const isProcessing = statusState === "pending" || statusState === "processing";
|
||||
|
|
@ -86,15 +78,24 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild>
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: div required for drag ref */}
|
||||
<div
|
||||
ref={drag}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
"group flex h-8 items-center gap-1.5 rounded-md px-1 text-sm hover:bg-accent/50 cursor-pointer select-none",
|
||||
isMentioned && "bg-accent/30",
|
||||
isDragging && "opacity-40",
|
||||
isDragging && "opacity-40"
|
||||
)}
|
||||
style={{ paddingLeft: `${depth * 16 + 4}px` }}
|
||||
onClick={handleCheckChange}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
handleCheckChange();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isSelectable ? (
|
||||
<Checkbox
|
||||
|
|
@ -110,7 +111,7 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
"h-2 w-2 rounded-full",
|
||||
statusState === "processing" && "animate-pulse bg-amber-500",
|
||||
statusState === "pending" && "bg-muted-foreground/40",
|
||||
statusState === "failed" && "bg-destructive",
|
||||
statusState === "failed" && "bg-destructive"
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -119,7 +120,10 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
<span className="flex-1 min-w-0 truncate">{doc.title}</span>
|
||||
|
||||
<span className="shrink-0">
|
||||
{getDocumentTypeIcon(doc.document_type as DocumentTypeEnum, "h-3.5 w-3.5 text-muted-foreground")}
|
||||
{getDocumentTypeIcon(
|
||||
doc.document_type as DocumentTypeEnum,
|
||||
"h-3.5 w-3.5 text-muted-foreground"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<DropdownMenu>
|
||||
|
|
@ -134,10 +138,10 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-44">
|
||||
<DropdownMenuItem onClick={() => onPreview(doc)}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onPreview(doc)}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
{isEditable && (
|
||||
<DropdownMenuItem onClick={() => onEdit(doc)}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
|
|
@ -163,10 +167,10 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
</ContextMenuTrigger>
|
||||
|
||||
<ContextMenuContent className="w-44">
|
||||
<ContextMenuItem onClick={() => onPreview(doc)}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => onPreview(doc)}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</ContextMenuItem>
|
||||
{isEditable && (
|
||||
<ContextMenuItem onClick={() => onEdit(doc)}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
|
|
|
|||
|
|
@ -58,13 +58,20 @@ interface FolderNodeProps {
|
|||
onDelete: (folder: FolderDisplay) => void;
|
||||
onMove: (folder: FolderDisplay) => void;
|
||||
onCreateSubfolder: (parentId: number) => void;
|
||||
onDropIntoFolder?: (itemType: "folder" | "document", itemId: number, targetFolderId: number) => void;
|
||||
onDropIntoFolder?: (
|
||||
itemType: "folder" | "document",
|
||||
itemId: number,
|
||||
targetFolderId: number
|
||||
) => void;
|
||||
onReorderFolder?: (folderId: number, beforePos: string | null, afterPos: string | null) => void;
|
||||
siblingPositions?: { before: string | null; after: string | null };
|
||||
disabledDropIds?: Set<number>;
|
||||
}
|
||||
|
||||
function getDropZone(monitor: { getClientOffset: () => { y: number } | null }, element: HTMLElement): DropZone {
|
||||
function getDropZone(
|
||||
monitor: { getClientOffset: () => { y: number } | null },
|
||||
element: HTMLElement
|
||||
): DropZone {
|
||||
const offset = monitor.getClientOffset();
|
||||
if (!offset) return "middle";
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
|
@ -104,7 +111,7 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
item: { id: folder.id, position: folder.position, parentId: folder.parentId },
|
||||
collect: (monitor) => ({ isDragging: monitor.isDragging() }),
|
||||
}),
|
||||
[folder.id, folder.position, folder.parentId],
|
||||
[folder.id, folder.position, folder.parentId]
|
||||
);
|
||||
|
||||
const [{ isOver, canDrop }, drop] = useDrop(
|
||||
|
|
@ -147,7 +154,14 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[folder.id, folder.position, disabledDropIds, onDropIntoFolder, onReorderFolder, siblingPositions],
|
||||
[
|
||||
folder.id,
|
||||
folder.position,
|
||||
disabledDropIds,
|
||||
onDropIntoFolder,
|
||||
onReorderFolder,
|
||||
siblingPositions,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -159,7 +173,7 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
rowRef.current = node;
|
||||
drag(drop(node));
|
||||
},
|
||||
[drag, drop],
|
||||
[drag, drop]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -188,7 +202,7 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
onCancelRename();
|
||||
}
|
||||
},
|
||||
[handleRenameSubmit, folder.name, onCancelRename],
|
||||
[handleRenameSubmit, folder.name, onCancelRename]
|
||||
);
|
||||
|
||||
const startRename = useCallback(() => {
|
||||
|
|
@ -201,8 +215,11 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild disabled={isRenaming}>
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: div required for drag/drop refs */}
|
||||
<div
|
||||
ref={attachRef}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
"group relative flex h-8 items-center gap-1 rounded-md px-1 text-sm hover:bg-accent/50 cursor-pointer select-none",
|
||||
isExpanded && "font-medium",
|
||||
|
|
@ -210,10 +227,16 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
isOver && canDrop && dropZone === "middle" && "bg-accent ring-1 ring-primary/40",
|
||||
isOver && canDrop && dropZone === "top" && "border-t-2 border-primary",
|
||||
isOver && canDrop && dropZone === "bottom" && "border-b-2 border-primary",
|
||||
isOver && !canDrop && "cursor-not-allowed",
|
||||
isOver && !canDrop && "cursor-not-allowed"
|
||||
)}
|
||||
style={{ paddingLeft: `${depth * 16 + 4}px` }}
|
||||
onClick={() => onToggleExpand(folder.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
onToggleExpand(folder.id);
|
||||
}
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startRename();
|
||||
|
|
@ -322,7 +345,10 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
Move to...
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem className="text-destructive focus:text-destructive" onClick={() => onDelete(folder)}>
|
||||
<ContextMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={() => onDelete(folder)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ export function FolderPickerDialog({
|
|||
const map: Record<string, FolderDisplay[]> = {};
|
||||
for (const f of folders) {
|
||||
const key = f.parentId ?? "root";
|
||||
(map[key] ??= []).push(f);
|
||||
if (!map[key]) map[key] = [];
|
||||
map[key].push(f);
|
||||
}
|
||||
return map;
|
||||
}, [folders]);
|
||||
|
|
@ -88,7 +89,7 @@ export function FolderPickerDialog({
|
|||
"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||
isSelected && "bg-accent text-accent-foreground",
|
||||
!isSelected && !isDisabled && "hover:bg-accent/50",
|
||||
isDisabled && "cursor-not-allowed opacity-40",
|
||||
isDisabled && "cursor-not-allowed opacity-40"
|
||||
)}
|
||||
style={{ paddingLeft: `${depth * 16 + 8}px` }}
|
||||
onClick={() => {
|
||||
|
|
@ -96,7 +97,8 @@ export function FolderPickerDialog({
|
|||
}}
|
||||
>
|
||||
{hasChildren ? (
|
||||
<span
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-4 w-4 shrink-0 items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -108,7 +110,7 @@ export function FolderPickerDialog({
|
|||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<span className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
|
|
@ -134,7 +136,7 @@ export function FolderPickerDialog({
|
|||
className={cn(
|
||||
"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||
selectedId === null && "bg-accent text-accent-foreground",
|
||||
selectedId !== null && "hover:bg-accent/50",
|
||||
selectedId !== null && "hover:bg-accent/50"
|
||||
)}
|
||||
onClick={() => setSelectedId(null)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { HTML5Backend } from "react-dnd-html5-backend";
|
|||
import { renamingFolderIdAtom } from "@/atoms/documents/folder.atoms";
|
||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||
import { DocumentNode, type DocumentNodeDoc } from "./DocumentNode";
|
||||
import { FolderNode, type FolderDisplay } from "./FolderNode";
|
||||
import { type FolderDisplay, FolderNode } from "./FolderNode";
|
||||
|
||||
interface FolderTreeViewProps {
|
||||
folders: FolderDisplay[];
|
||||
|
|
@ -16,7 +16,10 @@ interface FolderTreeViewProps {
|
|||
expandedIds: Set<number>;
|
||||
onToggleExpand: (folderId: number) => void;
|
||||
mentionedDocIds: Set<number>;
|
||||
onToggleChatMention: (doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => void;
|
||||
onToggleChatMention: (
|
||||
doc: { id: number; title: string; document_type: string },
|
||||
isMentioned: boolean
|
||||
) => void;
|
||||
onRenameFolder: (folder: FolderDisplay, newName: string) => void;
|
||||
onDeleteFolder: (folder: FolderDisplay) => void;
|
||||
onMoveFolder: (folder: FolderDisplay) => void;
|
||||
|
|
@ -26,7 +29,11 @@ interface FolderTreeViewProps {
|
|||
onDeleteDocument: (doc: DocumentNodeDoc) => void;
|
||||
onMoveDocument: (doc: DocumentNodeDoc) => void;
|
||||
activeTypes: DocumentTypeEnum[];
|
||||
onDropIntoFolder?: (itemType: "folder" | "document", itemId: number, targetFolderId: number | null) => void;
|
||||
onDropIntoFolder?: (
|
||||
itemType: "folder" | "document",
|
||||
itemId: number,
|
||||
targetFolderId: number | null
|
||||
) => void;
|
||||
onReorderFolder?: (folderId: number, beforePos: string | null, afterPos: string | null) => void;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +41,8 @@ function groupBy<T>(items: T[], keyFn: (item: T) => string | number): Record<str
|
|||
const result: Record<string | number, T[]> = {};
|
||||
for (const item of items) {
|
||||
const key = keyFn(item);
|
||||
(result[key] ??= []).push(item);
|
||||
if (!result[key]) result[key] = [];
|
||||
result[key].push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -58,15 +66,9 @@ export function FolderTreeView({
|
|||
onDropIntoFolder,
|
||||
onReorderFolder,
|
||||
}: FolderTreeViewProps) {
|
||||
const foldersByParent = useMemo(
|
||||
() => groupBy(folders, (f) => f.parentId ?? "root"),
|
||||
[folders],
|
||||
);
|
||||
const foldersByParent = useMemo(() => groupBy(folders, (f) => f.parentId ?? "root"), [folders]);
|
||||
|
||||
const docsByFolder = useMemo(
|
||||
() => groupBy(documents, (d) => d.folderId ?? "root"),
|
||||
[documents],
|
||||
);
|
||||
const docsByFolder = useMemo(() => groupBy(documents, (d) => d.folderId ?? "root"), [documents]);
|
||||
|
||||
const folderChildCounts = useMemo(() => {
|
||||
const counts: Record<number, number> = {};
|
||||
|
|
@ -82,12 +84,9 @@ export function FolderTreeView({
|
|||
const [renamingFolderId, setRenamingFolderId] = useAtom(renamingFolderIdAtom);
|
||||
const handleStartRename = useCallback(
|
||||
(folderId: number) => setRenamingFolderId(folderId),
|
||||
[setRenamingFolderId],
|
||||
);
|
||||
const handleCancelRename = useCallback(
|
||||
() => setRenamingFolderId(null),
|
||||
[setRenamingFolderId],
|
||||
[setRenamingFolderId]
|
||||
);
|
||||
const handleCancelRename = useCallback(() => setRenamingFolderId(null), [setRenamingFolderId]);
|
||||
|
||||
const hasDescendantMatch = useMemo(() => {
|
||||
if (activeTypes.length === 0) return null;
|
||||
|
|
@ -96,7 +95,7 @@ export function FolderTreeView({
|
|||
function check(folderId: number): boolean {
|
||||
if (match[folderId] !== undefined) return match[folderId];
|
||||
const childDocs = (docsByFolder[folderId] ?? []).some((d) =>
|
||||
activeTypes.includes(d.document_type as DocumentTypeEnum),
|
||||
activeTypes.includes(d.document_type as DocumentTypeEnum)
|
||||
);
|
||||
if (childDocs) {
|
||||
match[folderId] = true;
|
||||
|
|
@ -127,10 +126,9 @@ export function FolderTreeView({
|
|||
const visibleFolders = hasDescendantMatch
|
||||
? childFolders.filter((f) => hasDescendantMatch[f.id])
|
||||
: childFolders;
|
||||
const childDocs = (docsByFolder[key] ?? [])
|
||||
.filter(
|
||||
(d) => activeTypes.length === 0 || activeTypes.includes(d.document_type as DocumentTypeEnum),
|
||||
);
|
||||
const childDocs = (docsByFolder[key] ?? []).filter(
|
||||
(d) => activeTypes.length === 0 || activeTypes.includes(d.document_type as DocumentTypeEnum)
|
||||
);
|
||||
|
||||
const nodes: React.ReactNode[] = [];
|
||||
|
||||
|
|
@ -159,7 +157,7 @@ export function FolderTreeView({
|
|||
onDropIntoFolder={onDropIntoFolder}
|
||||
onReorderFolder={onReorderFolder}
|
||||
siblingPositions={siblingPositions}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
|
||||
if (expandedIds.has(f.id)) {
|
||||
|
|
@ -179,7 +177,7 @@ export function FolderTreeView({
|
|||
onEdit={onEditDocument}
|
||||
onDelete={onDeleteDocument}
|
||||
onMove={onMoveDocument}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -208,9 +206,7 @@ export function FolderTreeView({
|
|||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-2 py-1">
|
||||
{treeNodes}
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-2 py-1">{treeNodes}</div>
|
||||
</DndProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue