diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 810016bdb..db9d9bc72 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -81,6 +81,7 @@ import { usesTreeSchemaMode, } from "@/lib/databaseCapabilities"; import { + copyNameForTreeNode, objectSourceKindForTreeNode, sidebarSelectionCopyAction, treeNodeRowAction, @@ -684,7 +685,7 @@ async function confirmDelete() { async function copyName() { try { - await copyToClipboard(props.node.label); + await copyToClipboard(copyNameForTreeNode(props.node)); toast(t("connection.copied"), 2000); } catch (e: any) { toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000); diff --git a/apps/desktop/src/lib/treeNodeClick.ts b/apps/desktop/src/lib/treeNodeClick.ts index c070c83ac..17c7d9b8c 100644 --- a/apps/desktop/src/lib/treeNodeClick.ts +++ b/apps/desktop/src/lib/treeNodeClick.ts @@ -1,4 +1,4 @@ -import type { ObjectSourceKind, TreeNodeType } from "@/types/database"; +import type { ObjectSourceKind, TreeNode, TreeNodeType } from "@/types/database"; import { matchesShortcut, type ShortcutLikeEvent } from "@/lib/keyboardShortcuts"; export type TreeNodeRowAction = "open-data" | "toggle" | "none"; @@ -17,6 +17,20 @@ const dataNodeTypes = new Set(["table", "view"]); const toggleLeafNodeTypes = new Set(["redis-db", "mongo-collection"]); const objectBrowserNodeTypes = new Set(["database", "schema", "object-browser"]); const sourceNodeTypes = new Set(["procedure", "function", "package", "package-body"]); +const tableChildGroupNodeTypes = new Set([ + "group-columns", + "group-indexes", + "group-fkeys", + "group-triggers", + "group-partitions", +]); +const databaseChildGroupNodeTypes = new Set([ + "group-tables", + "group-views", + "group-procedures", + "group-functions", + "group-packages", +]); export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null { if (type === "view") return "VIEW"; @@ -61,3 +75,9 @@ export function treeNodeRowDoubleClickAction( export function sidebarSelectionCopyAction(event: ShortcutLikeEvent): SidebarSelectionCopyAction { return matchesShortcut(event, "Mod+C") ? "copy-name" : "none"; } + +export function copyNameForTreeNode(node: TreeNode): string { + if (tableChildGroupNodeTypes.has(node.type) && node.tableName) return node.tableName; + if (databaseChildGroupNodeTypes.has(node.type)) return node.schema || node.database || node.label; + return node.label; +} diff --git a/packages/app-tests/treeNodeClick.test.ts b/packages/app-tests/treeNodeClick.test.ts index 0775a727d..58e492ca7 100644 --- a/packages/app-tests/treeNodeClick.test.ts +++ b/packages/app-tests/treeNodeClick.test.ts @@ -1,6 +1,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { + copyNameForTreeNode, objectSourceKindForTreeNode, sidebarSelectionCopyAction, treeNodeRowAction, @@ -92,3 +93,46 @@ test("single click navigation mode copies the selected sidebar row name", () => assert.equal(sidebarSelectionCopyAction({ key: "c", metaKey: true }), "copy-name"); assert.equal(sidebarSelectionCopyAction({ key: "C", ctrlKey: true }), "copy-name"); }); + +test("copying table child group rows uses the parent table name", () => { + assert.equal( + copyNameForTreeNode({ + id: "conn:db:public:orders:__columns", + label: "tree.columns", + type: "group-columns", + tableName: "orders", + }), + "orders", + ); + assert.equal( + copyNameForTreeNode({ + id: "conn:db:public:orders:__indexes", + label: "tree.indexes", + type: "group-indexes", + tableName: "orders", + }), + "orders", + ); +}); + +test("copying database object group rows uses the parent schema or database name", () => { + assert.equal( + copyNameForTreeNode({ + id: "conn:db:public:__tables", + label: "tree.tables", + type: "group-tables", + database: "db", + schema: "public", + }), + "public", + ); + assert.equal( + copyNameForTreeNode({ + id: "conn:db:__views", + label: "tree.views", + type: "group-views", + database: "db", + }), + "db", + ); +});