diff --git a/web/src/components/canvas/canvas-node-content.tsx b/web/src/components/canvas/canvas-node-content.tsx index 0af4b7f2f..5c837e53c 100644 --- a/web/src/components/canvas/canvas-node-content.tsx +++ b/web/src/components/canvas/canvas-node-content.tsx @@ -329,12 +329,11 @@ function TextContent({ node, theme, isEditingContent, textareaRef, mentionRefere
event.stopPropagation()} onWheel={(event) => event.stopPropagation()} dangerouslySetInnerHTML={{ __html: richTextHTML }} /> ) : ( -
event.stopPropagation()} onWheel={(event) => event.stopPropagation()}> +
event.stopPropagation()}> {node.metadata?.content || 双击编辑文字}
)} diff --git a/web/src/pages/canvas/project.tsx b/web/src/pages/canvas/project.tsx index 0829294a9..bc9245693 100644 --- a/web/src/pages/canvas/project.tsx +++ b/web/src/pages/canvas/project.tsx @@ -1036,7 +1036,9 @@ function InfiniteCanvasPage() { setDrawingNodeId(node.id); } else if (node.type === CanvasNodeType.Script) { setDialogNodeId(null); - } else if (node.type === CanvasNodeType.Text || node.type === CanvasNodeType.Frame) { + } else if (node.type === CanvasNodeType.Text) { + setDialogNodeId(node.id); + } else if (node.type === CanvasNodeType.Frame) { setDialogNodeId((current) => (current === node.id ? current : null)); } else if (node.type === PORTRAIT_CLEARANCE_NODE_TYPE) { setDialogNodeId(null); diff --git a/web/test/canvas-text-selection.test.ts b/web/test/canvas-text-selection.test.ts new file mode 100644 index 000000000..db2e5e0a9 --- /dev/null +++ b/web/test/canvas-text-selection.test.ts @@ -0,0 +1,27 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, test } from "bun:test"; + +const source = readFileSync(new URL("../src/components/canvas/canvas-node-content.tsx", import.meta.url), "utf8"); +const textContent = source.slice(source.indexOf("function TextContent("), source.indexOf("function ", source.indexOf("function TextContent(") + 1)); + +describe("text node selection (#429)", () => { + test("opens the text generation panel on a node click", () => { + const project = readFileSync(new URL("../src/pages/canvas/project.tsx", import.meta.url), "utf8"); + const clickHandler = project.slice(project.indexOf("const handleSelectedNodeClick ="), project.indexOf("const handleNodeBringToFront =")); + expect(clickHandler).toMatch(/node.type === CanvasNodeType.Text\)\s*\{\s*setDialogNodeId\(node.id\)/); + }); + + test("allows plain and rich text previews to reach node selection", () => { + const preview = textContent.split(") : richTextHTML ? (")[1]; + expect(preview).toBeDefined(); + expect(preview).not.toContain("onMouseDown"); + expect(preview).not.toContain("onPointerDown"); + expect(preview?.match(/onWheel=/g)).toHaveLength(2); + }); + + test("keeps editing gestures isolated from canvas dragging", () => { + const editor = textContent.split(") : richTextHTML ? (")[0]; + expect(editor).toContain("onMouseDown={(event) => event.stopPropagation()}"); + expect(editor).toContain("onPointerDown={(event) => event.stopPropagation()}"); + }); +});