feat(connection): add copy connection name

This commit is contained in:
Terry Yao 2026-07-24 23:48:52 +08:00 committed by GitHub
parent 7ee335e38a
commit f2ce70dd68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 9 deletions

View File

@ -11,7 +11,7 @@ import { filterSidebarSearchRootsByConnectionState, filterSidebarTree, filterSid
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut } from "@/lib/editor/keyboardShortcuts";
import { copyNameForTreeNode, objectSourceKindForTreeNode } from "@/lib/sidebar/treeNodeClick";
import { copyToClipboard } from "@/lib/common/clipboard";
import { connectionPasteTargetGroupId, selectedConnectionClipboardNodes, selectedConnectionEditTarget } from "@/lib/sidebar/sidebarConnectionSelection";
import { connectionPasteTargetGroupId, copySelectedConnectionsToClipboards, selectedConnectionEditTarget } from "@/lib/sidebar/sidebarConnectionSelection";
import { isEditableSidebarTypeSearchTarget, sidebarTypeSearchNextQuery } from "@/lib/sidebar/sidebarTypeSearch";
import { usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
import { connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
@ -1304,11 +1304,10 @@ function requestSelectedConnectionEdit(): boolean {
function copySelectedSidebarNames(): boolean {
const nodes = selectedSidebarNodesInVisibleOrder();
if (nodes.length === 0) return false;
const connectionNodes = selectedConnectionClipboardNodes(nodes);
if (connectionNodes.length > 0) {
const copiedCount = store.copyConnectionsToTreeClipboard(connectionNodes.map((node) => node.connectionId));
if (copiedCount > 0) toast(t("connection.copied"), 2000);
return copiedCount > 0;
const copiedCount = copySelectedConnectionsToClipboards(nodes, (connectionIds) => store.copyConnectionsToTreeClipboard(connectionIds), copyToClipboard);
if (copiedCount > 0) {
toast(t("connection.copied"), 2000);
return true;
}
const tableNodes = nodes.filter((node) => node.type === "table" && !!node.connectionId && !!node.database);
store.treeClipboard =

View File

@ -1285,7 +1285,14 @@ async function copySelectedNames() {
const connectionTargets = selectedConnectionClipboardTargets(activeNode.value, nodes);
if (connectionTargets.length > 0) {
const copiedCount = connectionStore.copyConnectionsToTreeClipboard(connectionTargets.map((node) => node.connectionId));
if (copiedCount > 0) toast(t("connection.copied"), 2000);
if (copiedCount > 0) {
try {
await copyToClipboard(connectionTargets.map(copyNameForTreeNode).join("\n"));
} catch {
/* system clipboard copy is best-effort */
}
toast(t("connection.copied"), 2000);
}
return;
}
updateTreeClipboardForNodes(nodes);
@ -3504,6 +3511,9 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean
} else {
items.push({ label: t("contextMenu.closeConnection"), action: disconnectConnection, icon: Unplug });
}
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
if (currentDatabaseType() === "redis") {
items.push({ label: t("contextMenu.instanceInfo"), action: openRedisInstanceInfo, icon: Info });

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { selectedConnectionDeleteTargets, selectedConnectionDuplicateTargets } from "@/lib/sidebar/sidebarConnectionSelection";
import { describe, expect, it, vi } from "vitest";
import { copySelectedConnectionsToClipboards, selectedConnectionDeleteTargets, selectedConnectionDuplicateTargets } from "@/lib/sidebar/sidebarConnectionSelection";
import type { TreeNode } from "@/types/database";
function node(id: string, type: TreeNode["type"] = "connection"): TreeNode {
@ -32,4 +32,22 @@ describe("sidebar connection selection", () => {
expect(selectedConnectionDuplicateTargets(current, selected)).toEqual(selectedConnectionDeleteTargets(current, selected));
});
it("copies selected connection names to the system clipboard for the sidebar copy shortcut", () => {
const copyConnectionsToTreeClipboard = vi.fn(() => 2);
const copyToSystemClipboard = vi.fn(() => Promise.resolve());
expect(copySelectedConnectionsToClipboards([node("conn-1"), node("conn-2")], copyConnectionsToTreeClipboard, copyToSystemClipboard)).toBe(2);
expect(copyConnectionsToTreeClipboard).toHaveBeenCalledWith(["conn-1", "conn-2"]);
expect(copyToSystemClipboard).toHaveBeenCalledWith("conn-1\nconn-2");
});
it("keeps the tree clipboard copy when system clipboard access is denied", async () => {
const copyConnectionsToTreeClipboard = vi.fn(() => 1);
const copyToSystemClipboard = vi.fn(() => Promise.reject(new Error("denied")));
expect(copySelectedConnectionsToClipboards([node("conn-1")], copyConnectionsToTreeClipboard, copyToSystemClipboard)).toBe(1);
await Promise.resolve();
expect(copyConnectionsToTreeClipboard).toHaveBeenCalledWith(["conn-1"]);
});
});

View File

@ -1,4 +1,5 @@
import type { TreeNode } from "@/types/database";
import { copyNameForTreeNode } from "@/lib/sidebar/treeNodeClick";
type ConnectionTreeNode = TreeNode & { connectionId: string };
@ -39,6 +40,18 @@ export function selectedConnectionClipboardNodes(selectedNodes: TreeNode[]): Con
return selectedNodes;
}
export function copySelectedConnectionsToClipboards(selectedNodes: TreeNode[], copyConnectionsToTreeClipboard: (connectionIds: string[]) => number, copyToSystemClipboard: (text: string) => Promise<void>): number {
const connectionNodes = selectedConnectionClipboardNodes(selectedNodes);
if (connectionNodes.length === 0) return 0;
const copiedCount = copyConnectionsToTreeClipboard(connectionNodes.map((node) => node.connectionId));
if (copiedCount > 0) {
// Connection duplication uses the tree clipboard, so keep it available even if OS clipboard access is denied.
void copyToSystemClipboard(connectionNodes.map(copyNameForTreeNode).join("\n")).catch(() => {});
}
return copiedCount;
}
export function connectionPasteTargetGroupId(node: TreeNode | null | undefined, groupIdForConnection: (connectionId: string) => string | null): string | null {
if (!node) return null;
if (node.type === "connection-group") return node.id;