feat: rename graph titles with select-then-edit interaction

This commit is contained in:
2026-11-06 01:40:40 +08:00
parent c9ce5fc2f1
commit af9988f78d
11 changed files with 212 additions and 9 deletions
+19 -3
View File
@@ -7,9 +7,10 @@ import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { Check, ChevronsUpDown, FilePlus2, Home, Network, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings } from "lucide-react";
import { IconButton } from "@/components/ui/icon-button";
import { api, errorText, type Profile } from "@/lib/api";
import { boardHref, createBoard, DEFAULT_BOARD_TITLE, loadWorkspaceHome, type Board, type Workspace } from "@/lib/workspace";
import { boardHref, createBoard, renameBoard, DEFAULT_BOARD_TITLE, loadWorkspaceHome, type Board, type Workspace } from "@/lib/workspace";
import { useActiveBoard } from "@/components/workspace/active-board";
import toolbar from "@/components/ui/toolbar.module.css";
import { EditableTitle } from "@/components/workspace/editable-title";
import { UserAvatar } from "@/components/workspace/user-avatar";
import { AccountPanel } from "@/components/workspace/account-panel";
import { readDemoBoards } from "@/lib/demo-workspace";
@@ -17,7 +18,7 @@ import styles from "@/components/workspace/workspace.module.css";
export default function DashboardSidebar() {
const router = useRouter();
const { board: activeBoard } = useActiveBoard();
const { board: activeBoard, setBoard: setActiveBoard } = useActiveBoard();
const reduceMotion = useReducedMotion();
const params = useParams<{ workspace?: string; board?: string }>();
const searchParams = useSearchParams();
@@ -139,6 +140,19 @@ export default function DashboardSidebar() {
const filtered = boards.filter((b) => b.title.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
const canCreate = status === "ready" && (!workspace || Boolean(currentWorkspace && currentWorkspace.role !== "viewer"));
async function renameTitle(title: string) {
const id = params.board;
if (!id || !workspace) return;
try {
if (demo) {
const list = readDemoBoards(workspace);
localStorage.setItem("endoleap_demo_boards_" + workspace, JSON.stringify(list.map((board) => board.id === id ? { ...board, title } : board)));
} else await renameBoard(api, workspace, id, title);
setBoards((list) => list.map((board) => board.id === id ? { ...board, title } : board));
setActiveBoard((board) => board?.id === id && board.workspace_id === workspace ? { ...board, title } : board);
} catch (error) { throw new Error(errorText(error)); }
}
const createButton = <IconButton icon={FilePlus2} label={creating ? "正在创建图谱" : "新建页面"} disabled={!canCreate || creating} onClick={newPage} aria-busy={creating} />;
const transition = { duration: reduceMotion ? 0 : 0.24, ease: [0.22, 1, 0.36, 1] as const };
@@ -159,7 +173,9 @@ export default function DashboardSidebar() {
srcSet="/brand/png/logo-light/endoleap-32.png 1x, /brand/png/logo-light/endoleap-64.png 2x, /brand/png/logo-light/endoleap-128.png 4x"
width={20} height={20} alt="" />
</Link>
<h1 className={styles.floatingTitle} title={currentTitle}>{currentTitle}</h1>
<EditableTitle key={workspace + ":" + (params.board || "home")} title={currentTitle}
editable={Boolean(params.board && activeBoard?.id === params.board && activeBoard.workspace_id === workspace && canCreate)}
onSave={renameTitle} className={styles.floatingTitle} />
<button type="button" className={styles.floatingAvatarButton} popoverTarget="workspace-account" aria-label="账户和工作空间">
<UserAvatar name={name} url={profile?.avatar_url} />
</button>