fix(sidebar): fix PR hint for fork PRs, remove badge styling, add hover effect
- Replace REST API PR lookup with `gh pr view` which handles fork PRs - Fix state comparison to use lowercase (matching server output) - Remove pill/badge styling, show plain icon + #number - Add brighter hover colors and underline for clickability hint
This commit is contained in:
@@ -104,6 +104,11 @@ const GITHUB_PR_STATE_COLORS: Record<PrHint["state"], string> = {
|
|||||||
merged: "#a371f7",
|
merged: "#a371f7",
|
||||||
closed: "#f85149",
|
closed: "#f85149",
|
||||||
};
|
};
|
||||||
|
const GITHUB_PR_STATE_COLORS_HOVER: Record<PrHint["state"], string> = {
|
||||||
|
open: "#56d364",
|
||||||
|
merged: "#bc8cff",
|
||||||
|
closed: "#ff7b72",
|
||||||
|
};
|
||||||
|
|
||||||
interface SidebarWorkspaceListProps {
|
interface SidebarWorkspaceListProps {
|
||||||
projects: SidebarProjectEntry[];
|
projects: SidebarProjectEntry[];
|
||||||
@@ -164,6 +169,9 @@ interface WorkspaceRowInnerProps {
|
|||||||
|
|
||||||
function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
||||||
const color = GITHUB_PR_STATE_COLORS[hint.state];
|
const color = GITHUB_PR_STATE_COLORS[hint.state];
|
||||||
|
const hoverColor = GITHUB_PR_STATE_COLORS_HOVER[hint.state];
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const activeColor = isHovered ? hoverColor : color;
|
||||||
|
|
||||||
const handlePressIn = useCallback((event: GestureResponderEvent) => {
|
const handlePressIn = useCallback((event: GestureResponderEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -184,17 +192,22 @@ function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
|||||||
hitSlop={4}
|
hitSlop={4}
|
||||||
onPressIn={handlePressIn}
|
onPressIn={handlePressIn}
|
||||||
onPress={handlePress}
|
onPress={handlePress}
|
||||||
|
onPointerEnter={() => setIsHovered(true)}
|
||||||
|
onPointerLeave={() => setIsHovered(false)}
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
styles.workspacePrBadge,
|
styles.workspacePrBadge,
|
||||||
{
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: `${color}1a`,
|
|
||||||
},
|
|
||||||
pressed && styles.workspacePrBadgePressed,
|
pressed && styles.workspacePrBadgePressed,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<GitPullRequest size={12} color={color} />
|
<GitPullRequest size={12} color={activeColor} />
|
||||||
<Text style={[styles.workspacePrBadgeText, { color }]} numberOfLines={1}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.workspacePrBadgeText,
|
||||||
|
{ color: activeColor },
|
||||||
|
isHovered && styles.workspacePrBadgeTextHovered,
|
||||||
|
]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
#{hint.number}
|
#{hint.number}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@@ -2160,10 +2173,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
paddingHorizontal: theme.spacing[2],
|
|
||||||
paddingVertical: 3,
|
|
||||||
borderRadius: theme.borderRadius.full,
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
},
|
||||||
workspacePrBadgePressed: {
|
workspacePrBadgePressed: {
|
||||||
opacity: 0.82,
|
opacity: 0.82,
|
||||||
@@ -2173,6 +2182,9 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
lineHeight: 14,
|
lineHeight: 14,
|
||||||
},
|
},
|
||||||
|
workspacePrBadgeTextHovered: {
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
},
|
||||||
workspaceCreatingText: {
|
workspaceCreatingText: {
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ function selectWorkspacePrHint(payload: CheckoutPrStatusPayload): PrHint | null
|
|||||||
return {
|
return {
|
||||||
url: status.url,
|
url: status.url,
|
||||||
number,
|
number,
|
||||||
state: status.isMerged || status.state === "MERGED"
|
state: status.isMerged || status.state === "merged"
|
||||||
? "merged"
|
? "merged"
|
||||||
: status.state === "OPEN"
|
: status.state === "open"
|
||||||
? "open"
|
? "open"
|
||||||
: "closed",
|
: "closed",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1745,61 +1745,6 @@ async function resolveGitHubRepo(cwd: string): Promise<string | null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listPullRequestsForHead(options: {
|
|
||||||
cwd: string;
|
|
||||||
repo: string;
|
|
||||||
owner: string;
|
|
||||||
head: string;
|
|
||||||
state: "open" | "closed";
|
|
||||||
}): Promise<any[]> {
|
|
||||||
const { stdout } = await execFileAsync(
|
|
||||||
"gh",
|
|
||||||
[
|
|
||||||
"api",
|
|
||||||
`repos/${options.repo}/pulls`,
|
|
||||||
"-X",
|
|
||||||
"GET",
|
|
||||||
"-F",
|
|
||||||
`head=${options.owner}:${options.head}`,
|
|
||||||
"-F",
|
|
||||||
`state=${options.state}`,
|
|
||||||
],
|
|
||||||
{ cwd: options.cwd },
|
|
||||||
);
|
|
||||||
const parsed = JSON.parse(stdout.trim());
|
|
||||||
return Array.isArray(parsed) ? parsed : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPullRequestStatus(current: any, fallbackHead: string): PullRequestStatus | null {
|
|
||||||
if (!current || typeof current !== "object") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const url = current.html_url ?? current.url;
|
|
||||||
const title = current.title;
|
|
||||||
if (typeof url !== "string" || typeof title !== "string" || !url || !title) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedAt =
|
|
||||||
typeof current.merged_at === "string" && current.merged_at.trim().length > 0
|
|
||||||
? current.merged_at
|
|
||||||
: null;
|
|
||||||
const state =
|
|
||||||
mergedAt !== null
|
|
||||||
? "merged"
|
|
||||||
: typeof current.state === "string" && current.state.trim().length > 0
|
|
||||||
? current.state
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
title,
|
|
||||||
state,
|
|
||||||
baseRefName: current.base?.ref ?? "",
|
|
||||||
headRefName: current.head?.ref ?? fallbackHead,
|
|
||||||
isMerged: mergedAt !== null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createPullRequest(
|
export async function createPullRequest(
|
||||||
cwd: string,
|
cwd: string,
|
||||||
@@ -1844,9 +1789,8 @@ export async function createPullRequest(
|
|||||||
|
|
||||||
export async function getPullRequestStatus(cwd: string): Promise<PullRequestStatusResult> {
|
export async function getPullRequestStatus(cwd: string): Promise<PullRequestStatusResult> {
|
||||||
await requireGitRepo(cwd);
|
await requireGitRepo(cwd);
|
||||||
const repo = await resolveGitHubRepo(cwd);
|
|
||||||
const head = await getCurrentBranch(cwd);
|
const head = await getCurrentBranch(cwd);
|
||||||
if (!repo || !head) {
|
if (!head) {
|
||||||
return {
|
return {
|
||||||
status: null,
|
status: null,
|
||||||
githubFeaturesEnabled: false,
|
githubFeaturesEnabled: false,
|
||||||
@@ -1860,69 +1804,51 @@ export async function getPullRequestStatus(cwd: string): Promise<PullRequestStat
|
|||||||
githubFeaturesEnabled: false,
|
githubFeaturesEnabled: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const owner = repo.split("/")[0];
|
|
||||||
let openPulls: any[];
|
|
||||||
try {
|
try {
|
||||||
openPulls = await listPullRequestsForHead({
|
const { stdout } = await execFileAsync(
|
||||||
cwd,
|
"gh",
|
||||||
repo,
|
[
|
||||||
owner,
|
"pr",
|
||||||
head,
|
"view",
|
||||||
state: "open",
|
"--json",
|
||||||
});
|
"url,title,state,baseRefName,headRefName,mergedAt",
|
||||||
|
],
|
||||||
|
{ cwd },
|
||||||
|
);
|
||||||
|
const pr = JSON.parse(stdout.trim());
|
||||||
|
if (!pr || typeof pr !== "object" || !pr.url || !pr.title) {
|
||||||
|
return { status: null, githubFeaturesEnabled: true };
|
||||||
|
}
|
||||||
|
const mergedAt =
|
||||||
|
typeof pr.mergedAt === "string" && pr.mergedAt.trim().length > 0
|
||||||
|
? pr.mergedAt
|
||||||
|
: null;
|
||||||
|
const state =
|
||||||
|
mergedAt !== null
|
||||||
|
? "merged"
|
||||||
|
: typeof pr.state === "string" && pr.state.trim().length > 0
|
||||||
|
? pr.state.toLowerCase()
|
||||||
|
: "";
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
url: pr.url,
|
||||||
|
title: pr.title,
|
||||||
|
state,
|
||||||
|
baseRefName: pr.baseRefName ?? "",
|
||||||
|
headRefName: pr.headRefName ?? head,
|
||||||
|
isMerged: mergedAt !== null,
|
||||||
|
},
|
||||||
|
githubFeaturesEnabled: true,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isGhAuthError(error)) {
|
if (isGhAuthError(error)) {
|
||||||
return {
|
return { status: null, githubFeaturesEnabled: false };
|
||||||
status: null,
|
}
|
||||||
githubFeaturesEnabled: false,
|
// gh pr view exits non-zero when no PR exists for the branch
|
||||||
};
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
if (message.includes("no pull requests found") || message.includes("Could not resolve")) {
|
||||||
|
return { status: null, githubFeaturesEnabled: true };
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const openPull = openPulls[0] ?? null;
|
|
||||||
const openStatus = buildPullRequestStatus(openPull, head);
|
|
||||||
if (openStatus) {
|
|
||||||
return {
|
|
||||||
status: openStatus,
|
|
||||||
githubFeaturesEnabled: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let closedPulls: any[];
|
|
||||||
try {
|
|
||||||
closedPulls = await listPullRequestsForHead({
|
|
||||||
cwd,
|
|
||||||
repo,
|
|
||||||
owner,
|
|
||||||
head,
|
|
||||||
state: "closed",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (isGhAuthError(error)) {
|
|
||||||
return {
|
|
||||||
status: null,
|
|
||||||
githubFeaturesEnabled: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
const mergedClosedPull =
|
|
||||||
closedPulls.find(
|
|
||||||
(entry) =>
|
|
||||||
entry &&
|
|
||||||
typeof entry === "object" &&
|
|
||||||
typeof entry.merged_at === "string" &&
|
|
||||||
entry.merged_at.trim().length > 0,
|
|
||||||
) ?? null;
|
|
||||||
const mergedStatus = buildPullRequestStatus(mergedClosedPull, head);
|
|
||||||
if (!mergedStatus) {
|
|
||||||
return {
|
|
||||||
status: null,
|
|
||||||
githubFeaturesEnabled: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status: mergedStatus,
|
|
||||||
githubFeaturesEnabled: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user