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()}");
+ });
+});