使用openai进行了代码review
This commit is contained in:
@@ -1,15 +1,28 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { Page, useEditorStore } from "@/lib/store";
|
||||
import { ChevronRight, FileText, Folder, FolderOpen, MoreHorizontal, Trash2, FilePlus, FolderPlus, Download, Upload } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { exportPageAsMarkdown, exportFolderAsZip } from "@/lib/export";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useImport } from "@/components/import-context";
|
||||
import { useSortable, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import {
|
||||
ChevronRight,
|
||||
FileText,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
MoreHorizontal,
|
||||
Trash2,
|
||||
FilePlus,
|
||||
FolderPlus,
|
||||
Download,
|
||||
Upload,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Page, useEditorStore } from "@/lib/store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { exportPageAsMarkdown, exportFolderAsZip } from "@/lib/export";
|
||||
import { useImport } from "@/components/import-context";
|
||||
import { useConfirm } from "@/components/confirm-provider";
|
||||
|
||||
interface TreeViewProps {
|
||||
pages: Page[];
|
||||
@@ -17,33 +30,32 @@ interface TreeViewProps {
|
||||
level?: number;
|
||||
}
|
||||
|
||||
function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
node: Page,
|
||||
pages: Page[],
|
||||
level: number,
|
||||
expanded: Record<string, boolean>,
|
||||
toggleExpand: (id: string) => void
|
||||
function TreeNode({
|
||||
node,
|
||||
pages,
|
||||
level,
|
||||
expanded,
|
||||
toggleExpand,
|
||||
}: {
|
||||
node: Page;
|
||||
pages: Page[];
|
||||
level: number;
|
||||
expanded: Record<string, boolean>;
|
||||
toggleExpand: (id: string) => void;
|
||||
}) {
|
||||
const { activePageId, setActivePageId, addPage, deletePage } = useEditorStore();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { triggerImport } = useImport();
|
||||
const confirm = useConfirm();
|
||||
|
||||
const isFolder = node.type === 'folder';
|
||||
const hasChildren = pages.some(p => p.parentId === node.id);
|
||||
const isFolder = node.type === "folder";
|
||||
const hasChildren = pages.some((p) => p.parentId === node.id);
|
||||
const isExpanded = expanded[node.id];
|
||||
|
||||
// DnD Hooks - Sorting
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: node.id,
|
||||
data: { type: node.type, title: node.title, parentId: node.parentId }
|
||||
data: { type: node.type, title: node.title, parentId: node.parentId },
|
||||
});
|
||||
|
||||
const style = {
|
||||
@@ -60,17 +72,16 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
"group flex items-center justify-between px-2 py-1 text-sm rounded-md transition-colors cursor-pointer select-none border border-transparent",
|
||||
activePageId === node.id
|
||||
? "bg-accent text-accent-foreground font-medium"
|
||||
: "text-muted-foreground hover:bg-muted/50 hover:text-foreground",
|
||||
// Use simple hover effect for "drop over" visual or rely on drag overlay
|
||||
: "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
||||
)}
|
||||
onClick={() => {
|
||||
setActivePageId(node.id);
|
||||
if (pathname !== '/') router.push('/');
|
||||
if (pathname !== "/") router.push("/");
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 flex-1 min-w-0">
|
||||
<button
|
||||
onPointerDown={(e) => e.stopPropagation()} // Prevent drag start on expand button
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleExpand(node.id);
|
||||
@@ -102,7 +113,7 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button
|
||||
onPointerDown={(e) => e.stopPropagation()} // Prevent drag
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
className="opacity-100 lg:opacity-0 lg:group-hover:opacity-100 p-1 hover:bg-muted-foreground/20 rounded text-muted-foreground transition-opacity"
|
||||
>
|
||||
<MoreHorizontal size={14} />
|
||||
@@ -114,13 +125,10 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent cursor-pointer outline-none"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const targetParentId = node.type === 'folder' ? node.id : node.parentId;
|
||||
// If creating sibling (node.type !== 'folder'), insert after current node
|
||||
// Note: Order might be float/int. We just increment for now.
|
||||
// Ideally we'd find the mid-point, but simpler logic: just +1 and let sort handle basic "after"
|
||||
const order = node.type === 'folder' ? undefined : (node.order || 0) + 1;
|
||||
addPage(targetParentId, 'file', undefined, order);
|
||||
if (node.type === 'folder') toggleExpand(node.id);
|
||||
const targetParentId = node.type === "folder" ? node.id : node.parentId;
|
||||
const order = node.type === "folder" ? undefined : (node.order || 0) + 1;
|
||||
addPage(targetParentId, "file", undefined, order);
|
||||
if (node.type === "folder") toggleExpand(node.id);
|
||||
}}
|
||||
>
|
||||
<FilePlus size={14} />
|
||||
@@ -130,10 +138,10 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent cursor-pointer outline-none"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const targetParentId = node.type === 'folder' ? node.id : node.parentId;
|
||||
const order = node.type === 'folder' ? undefined : (node.order || 0) + 1;
|
||||
addPage(targetParentId, 'folder', undefined, order);
|
||||
if (node.type === 'folder') toggleExpand(node.id);
|
||||
const targetParentId = node.type === "folder" ? node.id : node.parentId;
|
||||
const order = node.type === "folder" ? undefined : (node.order || 0) + 1;
|
||||
addPage(targetParentId, "folder", undefined, order);
|
||||
if (node.type === "folder") toggleExpand(node.id);
|
||||
}}
|
||||
>
|
||||
<FolderPlus size={14} />
|
||||
@@ -143,9 +151,9 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent cursor-pointer outline-none"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const targetParentId = node.type === 'folder' ? node.id : node.parentId;
|
||||
const targetParentId = node.type === "folder" ? node.id : node.parentId;
|
||||
triggerImport(targetParentId);
|
||||
if (node.type === 'folder') toggleExpand(node.id);
|
||||
if (node.type === "folder") toggleExpand(node.id);
|
||||
}}
|
||||
>
|
||||
<Upload size={14} />
|
||||
@@ -155,7 +163,7 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-accent cursor-pointer outline-none"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (node.type === 'folder') {
|
||||
if (node.type === "folder") {
|
||||
exportFolderAsZip(node.id, pages, node.title);
|
||||
} else {
|
||||
exportPageAsMarkdown(node);
|
||||
@@ -168,9 +176,16 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
<DropdownMenu.Separator className="h-px bg-muted my-1" />
|
||||
<DropdownMenu.Item
|
||||
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-red-50 text-red-600 cursor-pointer outline-none"
|
||||
onClick={(e) => {
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm("确定要删除吗?此操作无法撤销。")) {
|
||||
const ok = await confirm({
|
||||
title: "确认删除",
|
||||
description: "此操作无法撤销。",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
tone: "danger",
|
||||
});
|
||||
if (ok) {
|
||||
deletePage(node.id);
|
||||
}
|
||||
}}
|
||||
@@ -191,27 +206,22 @@ function TreeNode({ node, pages, level, expanded, toggleExpand }: {
|
||||
}
|
||||
|
||||
export function TreeView({ pages, parentId, level = 0 }: TreeViewProps) {
|
||||
// Sort nodes by order field
|
||||
const nodes = pages
|
||||
.filter(p => p.parentId === parentId)
|
||||
.filter((p) => p.parentId === parentId)
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
|
||||
// Simple state for expansion
|
||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||
|
||||
const toggleExpand = (id: string) => {
|
||||
setExpanded(prev => ({ ...prev, [id]: !prev[id] }));
|
||||
setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||
};
|
||||
|
||||
if (nodes.length === 0) return null;
|
||||
|
||||
return (
|
||||
<SortableContext
|
||||
items={nodes.map(n => n.id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<SortableContext items={nodes.map((n) => n.id)} strategy={verticalListSortingStrategy}>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{nodes.map(node => (
|
||||
{nodes.map((node) => (
|
||||
<TreeNode
|
||||
key={node.id}
|
||||
node={node}
|
||||
|
||||
Reference in New Issue
Block a user