import { useState } from "react"; import { editComment } from "~/fns/issues"; import { useAction } from "~/hooks/use-action"; import { useViewer } from "~/hooks/use-viewer"; import { ReactionBar } from "./ReactionBar"; import { MarkdownEditor } from "./MarkdownEditor"; import { Avatar } from "~/ui/Avatar"; import { Badge } from "~/ui/Badge"; import { Markdown } from "~/ui/Markdown"; import { RelativeTime } from "~/ui/RelativeTime"; import { Button } from "~/ui/Button"; import type { getIssue } from "~/fns/issues"; import type { JsonValue } from "~/lib/types"; import { pullEventText } from "~/lib/pull-event-text"; import { TextLink } from "~/ui/TextLink"; import { ReportLink } from "./ReportLink"; type TimelineItem = Awaited>["timeline"][number]; const REVIEW_COPY: Record = { approved: { label: "approved these changes", tone: "success" }, changes_requested: { label: "requested changes", tone: "danger" }, commented: { label: "reviewed", tone: "neutral" }, dismissed: { label: "review dismissed", tone: "neutral" }, }; function list(value: JsonValue | undefined): string { return Array.isArray(value) ? value.map(String).join(", ") : typeof value === "string" ? value : ""; } function eventText(kind: string, data: { [key: string]: JsonValue }): string { switch (kind) { case "closed": return data.commit ? `closed this in ${String(data.commit).slice(0, 7)}` : "closed this"; case "reopened": return "reopened this"; case "renamed": return `changed the title from “${list(data.from)}” to “${list(data.to)}”`; case "labeled": return `set labels: ${list(data.labels) || "none"}`; case "assigned": return `set assignees: ${list(data.usernames) || "none"}`; case "merged": return `merged commit ${String(data.sha ?? "").slice(0, 7)} (${list(data.method)})`; case "head_pushed": return `pushed ${String(data.after ?? "").slice(0, 7)}`; case "head_ref_deleted": return `deleted the ${list(data.ref)} branch`; case "base_changed": return `changed the base branch from ${list(data.from)} to ${list(data.to)}${data.reason ? ` because ${list(data.reason)}` : ""}`; case "review_requested": return `requested review from ${list(data.usernames)}`; case "converted_to_draft": return "marked this as a draft"; case "ready_for_review": return "marked this ready for review"; case "locked": return "locked the conversation"; case "unlocked": return "unlocked the conversation"; case "milestoned": return `added this to the ${list(data.milestone)} milestone`; case "demilestoned": return "removed this from its milestone"; case "typed": return data.type ? `set the type to ${list(data.type)}` : "cleared the issue type"; case "sub_issue_added": return `added sub-issue #${String(data.number ?? "")}`; case "sub_issue_removed": return `removed sub-issue #${String(data.number ?? "")}`; case "pinned": return "pinned this issue"; case "transferred": return `transferred this issue from ${list(data.from)}`; case "converted_to_discussion": return `converted this issue into discussion #${String(data.number ?? "")}`; default: return pullEventText(kind, data) ?? kind.replace(/_/g, " "); } } function CommentCard({ item, owner, repo, }: { item: Extract; owner: string; repo: string; }) { const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(item.body); const save = useAction(editComment); const viewer = useViewer(); return (
{item.author.name} commented {item.edited && " · edited"} {item.canEdit && !editing && (
)}
{editing ? (
) : (
)}
); } export function Timeline({ items, owner, repo }: { items: TimelineItem[]; owner: string; repo: string }) { return (
    {items.map((item) => (
  1. {item.type === "comment" && } {item.type === "event" && (
    {item.actor.name} {eventText(item.kind, item.data)}
    )} {item.type === "review" && (
    {item.author.name} {REVIEW_COPY[item.state]?.label ?? item.state}
    {item.html.trim() && }
    )}
  2. ))}
); }