// The Issues view (spec §8). import * as vscode from "vscode"; import type * as api from "./hubapi"; import { HubAuth } from "./auth"; import { hubUrl } from "./hub"; import type { HubRepo } from "./proposals"; type Node = { kind: "repo"; repo: HubRepo } | { kind: "issue"; repo: HubRepo; i: api.Issue }; export type IssueNode = Extract; export class IssuesProvider implements vscode.TreeDataProvider { private readonly emitter = new vscode.EventEmitter(); readonly onDidChangeTreeData = this.emitter.event; constructor( private readonly repos: () => Promise, private readonly auth: HubAuth, ) {} refresh(): void { this.emitter.fire(); } getTreeItem(node: Node): vscode.TreeItem { if (node.kind === "repo") { const item = new vscode.TreeItem(`${node.repo.owner}/${node.repo.name}`, vscode.TreeItemCollapsibleState.Expanded); item.iconPath = new vscode.ThemeIcon("repo"); return item; } const i = node.i; const item = new vscode.TreeItem(`#${i.number} ${i.title}`, vscode.TreeItemCollapsibleState.None); item.description = [i.labels.map((l) => l.name).join(", "), i.assignees.length ? "→ " + i.assignees.join(", ") : "", i.milestone ?? ""].filter(Boolean).join(" · "); item.iconPath = new vscode.ThemeIcon(i.state === "open" ? "issue-opened" : "issue-closed"); item.contextValue = "issue"; item.tooltip = new vscode.MarkdownString(`**${i.title}**\n\nopened by ${i.author} · ${i.comments} comment(s)\n\n${i.body.slice(0, 400)}`); item.command = { title: "Open", command: "oxygenhub.openIssue", arguments: [node] }; return item; } async getChildren(node?: Node): Promise { if (!node) { return (await this.repos()).map((repo) => ({ kind: "repo", repo })); } if (node.kind === "repo") { try { const me = this.auth.sessionFor(node.repo.base)?.username; const list = await this.auth.clientFor(node.repo.base).issues(node.repo.owner, node.repo.name, "open"); list.sort((a, b) => Number(b.assignees.includes(me ?? "")) - Number(a.assignees.includes(me ?? "")) || b.updated_ms - a.updated_ms); return list.map((i) => ({ kind: "issue", repo: node.repo, i })); } catch (e) { void vscode.window.showWarningMessage(`Oxygenhub: ${(e as Error).message}`); return []; } } return []; } } export async function showIssue(auth: HubAuth, repo: HubRepo, i: api.Issue): Promise { const comments = await auth.clientFor(repo.base).issueComments(repo.owner, repo.name, i.number); const panel = vscode.window.createWebviewPanel("oxygenhub.issue", `#${i.number} ${i.title}`, vscode.ViewColumn.Beside, { enableCommandUris: true }); const esc = (s: string) => s.replace(/&/g, "&").replace(//g, ">"); const arg = encodeURIComponent(JSON.stringify([repo.base, repo.owner, repo.name, i.number])); const thread = comments.map((c) => `
${esc(c.author)} ${new Date(c.created_ms).toLocaleString()}
${esc(c.body)}
`).join(""); panel.webview.html = `

#${i.number} ${esc(i.title)} ${i.state}${i.reason ? ": " + i.reason : ""}

opened by ${esc(i.author)} · ${i.labels.map((l) => esc(l.name)).join(", ") || "no labels"} · ${i.assignees.length ? "assigned to " + i.assignees.map(esc).join(", ") : "unassigned"}${i.milestone ? " · " + esc(i.milestone) : ""}

Open on hub · Comment · ${i.state === "open" ? "Close" : "Reopen"} · Assign to me

${esc(i.body) || "No description."}
${thread} `; }