diff --git a/apps/web/src/components/NotebookTreeItem.tsx b/apps/web/src/components/NotebookTreeItem.tsx index 85ec306a..94b148bc 100644 --- a/apps/web/src/components/NotebookTreeItem.tsx +++ b/apps/web/src/components/NotebookTreeItem.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef, type DragEvent } from "react"; import { useTranslation } from "react-i18next"; -import { ChevronDown, ChevronRight, Notebook as NotebookIcon, Plus, Pencil, Trash2 } from "lucide-react"; +import { ChevronDown, ChevronRight, MoreHorizontal, Notebook as NotebookIcon, Plus, Pencil, Trash2 } from "lucide-react"; import type { NotebookNode, NotebookDropPosition } from "@/lib/app-helpers"; import { hasMemoDragData, @@ -58,6 +58,8 @@ export const NotebookTreeItem = ({ const isInbox = node.slug === "inbox"; const hasSelectedDescendant = selectedNotebookId ? notebookTreeContainsId(node.children, selectedNotebookId) : false; const [dropPosition, setDropPosition] = useState(null); + const [actionsOpen, setActionsOpen] = useState(false); + const actionsRef = useRef(null); const expandTimerRef = useRef(null); const clearExpandTimer = () => { @@ -71,6 +73,21 @@ export const NotebookTreeItem = ({ useEffect(() => () => clearExpandTimer(), []); + useEffect(() => { + if (!actionsOpen) { + return; + } + + const closeOnOutsidePointer = (event: PointerEvent) => { + if (!actionsRef.current?.contains(event.target as Node)) { + setActionsOpen(false); + } + }; + + document.addEventListener("pointerdown", closeOnOutsidePointer); + return () => document.removeEventListener("pointerdown", closeOnOutsidePointer); + }, [actionsOpen]); + useEffect(() => { if (hasSelectedDescendant) { setOpen(true); @@ -256,50 +273,69 @@ export const NotebookTreeItem = ({ {node.memoCount} - + {actionsOpen && ( +
+ + + {!isInbox && ( + <> +
+ + + )} +
)} - type="button" - title={t("notebookTree.newChild")} - aria-label={t("notebookTree.newChildAria", { name: node.name })} - onClick={(event) => { - event.stopPropagation(); - onCreateNotebook(node.id); - }} - > - - - - {!isInbox ? ( - - ) : null} +
{dropPosition === "before" && (
)} diff --git a/package.json b/package.json index 0aa1d4e3..81ca70c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "edgeever", - "version": "1.6.48", + "version": "1.6.49", "private": true, "type": "module", "workspaces": [ diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts index a85446ef..5b8f3009 100644 --- a/packages/shared/src/i18n/en-US.ts +++ b/packages/shared/src/i18n/en-US.ts @@ -154,6 +154,8 @@ export const enUS = { switchTo: "Switch to {{name}}", currentWithCount: "Current: {{name}}, {{count}} notes", switchToWithCount: "Switch to {{name}}, {{count}} notes", + actions: "Notebook actions", + actionsAria: "Open actions for {{name}}", newChild: "New child notebook", newChildAria: "Create child notebook under {{name}}", rename: "Rename", diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts index b929587c..81b73fd7 100644 --- a/packages/shared/src/i18n/zh-CN.ts +++ b/packages/shared/src/i18n/zh-CN.ts @@ -154,6 +154,8 @@ export const zhCN = { switchTo: "切换到 {{name}}", currentWithCount: "当前:{{name}},{{count}} 条笔记", switchToWithCount: "切换到 {{name}},{{count}} 条笔记", + actions: "笔记本操作", + actionsAria: "打开 {{name}} 的操作菜单", newChild: "新建子笔记本", newChildAria: "在 {{name}} 下新建子笔记本", rename: "重命名",