import type { ContextMenuItem } from "@t3tools/contracts";import { writeTextToClipboard } from "~/hooks/useCopyToClipboard";import { readLocalApi } from "~/localApi";import { toastManager } from "../ui/toast";export type PullRequestLinkContextMenuAction = "copy-link" | "open-external";/** Named for the host rather than "externally": the point is where you will land. */export const OPEN_ON_HOST_LABELS: Partial<Record<string, string>> = { github: "Open on GitHub", gitlab: "Open on GitLab", bitbucket: "Open on Bitbucket", "azure-devops": "Open on Azure DevOps",};export const openOnHostLabel = (provider: string): string => OPEN_ON_HOST_LABELS[provider] ?? "Open on host";/** Copy first: it is the reason to right-click a number rather than click it. */export function pullRequestLinkContextMenuItems( openLabel: string,): readonly ContextMenuItem<PullRequestLinkContextMenuAction>[] { return [ { id: "copy-link", label: "Copy link", icon: "copy" }, { id: "open-external", label: openLabel }, ];}/** * The right-click on a change request's number. Everywhere else that number is written it is a * link, and the gesture that copies a link is the one hand reaches for — so without this the * platform's own edit menu opens over a control that has nothing to cut, paste or select. * * The host is named by the caller rather than guessed here: the same number belongs to GitHub, * GitLab, Bitbucket or Azure DevOps depending on where it was read, and the `url` the contract * carries is already whichever of them it came from. */export async function showPullRequestLinkContextMenu({ url, openLabel, position,}: { readonly url: string; readonly openLabel: string; readonly position: { readonly x: number; readonly y: number };}): Promise<void> { const api = readLocalApi(); if (!api) return; let action: PullRequestLinkContextMenuAction | null = null; try { action = await api.contextMenu.show(pullRequestLinkContextMenuItems(openLabel), position); } catch { // A menu that could not be shown has already cost the reader their right-click; there is // nothing to say about it that a second popup would not make worse. return; } try { if (action === "copy-link") await writeTextToClipboard(url, "link"); else if (action === "open-external") await api.shell.openExternal(url); } catch { toastManager.add({ type: "error", title: action === "copy-link" ? "Could not copy the link" : "Could not open the link", }); }}
import type { ContextMenuItem } from "@t3tools/contracts";
import { writeTextToClipboard } from "~/hooks/useCopyToClipboard";
import { readLocalApi } from "~/localApi";
import { toastManager } from "../ui/toast";
export type PullRequestLinkContextMenuAction = "copy-link" | "open-external";
/** Named for the host rather than "externally": the point is where you will land. */
export const OPEN_ON_HOST_LABELS: Partial<Record<string, string>> = {
github: "Open on GitHub",
gitlab: "Open on GitLab",
bitbucket: "Open on Bitbucket",
"azure-devops": "Open on Azure DevOps",
};
export const openOnHostLabel = (provider: string): string =>
OPEN_ON_HOST_LABELS[provider] ?? "Open on host";
/** Copy first: it is the reason to right-click a number rather than click it. */
export function pullRequestLinkContextMenuItems(
openLabel: string,
): readonly ContextMenuItem<PullRequestLinkContextMenuAction>[] {
return [
{ id: "copy-link", label: "Copy link", icon: "copy" },
{ id: "open-external", label: openLabel },
];
}
/**
* The right-click on a change request's number. Everywhere else that number is written it is a
* link, and the gesture that copies a link is the one hand reaches for — so without this the
* platform's own edit menu opens over a control that has nothing to cut, paste or select.
*
* The host is named by the caller rather than guessed here: the same number belongs to GitHub,
* GitLab, Bitbucket or Azure DevOps depending on where it was read, and the `url` the contract
* carries is already whichever of them it came from.
*/
export async function showPullRequestLinkContextMenu({
url,
openLabel,
position,
}: {
readonly url: string;
readonly openLabel: string;
readonly position: { readonly x: number; readonly y: number };
}): Promise<void> {
const api = readLocalApi();
if (!api) return;
let action: PullRequestLinkContextMenuAction | null = null;
try {
action = await api.contextMenu.show(pullRequestLinkContextMenuItems(openLabel), position);
} catch {
// A menu that could not be shown has already cost the reader their right-click; there is
// nothing to say about it that a second popup would not make worse.
return;
}
try {
if (action === "copy-link") await writeTextToClipboard(url, "link");
else if (action === "open-external") await api.shell.openExternal(url);
} catch {
toastManager.add({
type: "error",
title: action === "copy-link" ? "Could not copy the link" : "Could not open the link",
});
}
}