| ... | ... | @@ -16,7 +16,6 @@ import { |
| 16 | 16 | BookOpenIcon, |
| 17 | 17 | CircleDotIcon, |
| 18 | 18 | ChevronDownIcon, |
| 19 | | CopyIcon, |
| 20 | 19 | ExternalLinkIcon, |
| 21 | 20 | FileDiffIcon, |
| 22 | 21 | FolderGit2Icon, |
| ... | ... | @@ -284,6 +283,68 @@ const openNumberContextMenu = ( |
| 284 | 283 | }); |
| 285 | 284 | }; |
| 286 | 285 | |
| 286 | function PullRequestCopyableCode({ |
| 287 | value, |
| 288 | target, |
| 289 | copyLabel, |
| 290 | copiedLabel, |
| 291 | className, |
| 292 | tooltipSide = "top", |
| 293 | onError, |
| 294 | }: { |
| 295 | readonly value: string; |
| 296 | readonly target: string; |
| 297 | readonly copyLabel: string; |
| 298 | readonly copiedLabel: string; |
| 299 | readonly className?: string; |
| 300 | readonly tooltipSide?: "top" | "bottom"; |
| 301 | readonly onError?: (error: Error) => void; |
| 302 | }) { |
| 303 | const { copyToClipboard, isCopied } = useCopyToClipboard({ |
| 304 | target, |
| 305 | timeout: 1600, |
| 306 | ...(onError ? { onError } : {}), |
| 307 | }); |
| 308 | return ( |
| 309 | <Tooltip> |
| 310 | <TooltipTrigger |
| 311 | render={ |
| 312 | <button |
| 313 | type="button" |
| 314 | className={cn( |
| 315 | "relative grid w-fit min-w-0 max-w-full shrink cursor-pointer rounded px-1 py-0.5 text-left outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent/45 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", |
| 316 | className, |
| 317 | )} |
| 318 | aria-label={isCopied ? copiedLabel : copyLabel} |
| 319 | onClick={() => copyToClipboard(value)} |
| 320 | /> |
| 321 | } |
| 322 | > |
| 323 | <code |
| 324 | className={cn( |
| 325 | "col-start-1 row-start-1 min-w-0 truncate transition-opacity duration-150 motion-reduce:transition-none", |
| 326 | isCopied ? "opacity-0" : "opacity-100", |
| 327 | )} |
| 328 | > |
| 329 | {value} |
| 330 | </code> |
| 331 | <span |
| 332 | aria-hidden="true" |
| 333 | className={cn( |
| 334 | "col-start-1 row-start-1 truncate text-center transition-opacity duration-150 motion-reduce:transition-none", |
| 335 | isCopied ? "opacity-100" : "opacity-0", |
| 336 | )} |
| 337 | > |
| 338 | Copied |
| 339 | </span> |
| 340 | </TooltipTrigger> |
| 341 | <TooltipPopup className="max-w-96 wrap-anywhere font-mono" side={tooltipSide}> |
| 342 | {`${isCopied ? "Copied" : copyLabel}: ${value}`} |
| 343 | </TooltipPopup> |
| 344 | </Tooltip> |
| 345 | ); |
| 346 | } |
| 347 | |
| 287 | 348 | /** |
| 288 | 349 | * The stale-branch warning, said beside the branch it is about rather than as a bar of its own. |
| 289 | 350 | * The banner this replaces held a row of chrome open across the top of every pull request that |
| ... | ... | @@ -466,20 +527,6 @@ export function PullRequestDetailPanel({ |
| 466 | 527 | // Which handoff is preparing, keyed so a per-finding button can say "Preparing..." on itself |
| 467 | 528 | // alone. One at a time whatever the key: they all check the same pull request out. |
| 468 | 529 | const [handoff, setHandoff] = useState<string | null>(null); |
| 469 | | const { copyToClipboard: copyBranchToClipboard, isCopied: isBranchCopied } = useCopyToClipboard({ |
| 470 | | target: "branch name", |
| 471 | | timeout: 1600, |
| 472 | | }); |
| 473 | | const { copyToClipboard: copyCheckoutCommandToClipboard } = useCopyToClipboard({ |
| 474 | | target: "pull request checkout command", |
| 475 | | onCopy: () => toastManager.add({ type: "success", title: "Checkout command copied" }), |
| 476 | | onError: (error) => |
| 477 | | toastManager.add({ |
| 478 | | type: "error", |
| 479 | | title: "Could not copy checkout command", |
| 480 | | description: error.message, |
| 481 | | }), |
| 482 | | }); |
| 483 | 530 | // The chunk is fetched as soon as the panel exists rather than waiting for the Code tab to be |
| 484 | 531 | // clicked, so a reader who does click it lands on a chunk already in the module cache. |
| 485 | 532 | useEffect(() => { |
| ... | ... | @@ -1697,10 +1744,30 @@ export function PullRequestDetailPanel({ |
| 1697 | 1744 | </div> |
| 1698 | 1745 | </div> |
| 1699 | 1746 | )} |
| 1700 | | <PullRequestMetaLine className="mt-2 text-xs text-muted-foreground"> |
| 1701 | | <PullRequestActorLabel actor={detail.author} className="font-medium" /> |
| 1702 | | <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span> |
| 1703 | | </PullRequestMetaLine> |
| 1747 | <div className="mt-2 flex min-w-0 items-center gap-2 text-xs text-muted-foreground"> |
| 1748 | <PullRequestMetaLine className="min-w-0 whitespace-nowrap"> |
| 1749 | <PullRequestActorLabel actor={detail.author} className="font-medium" /> |
| 1750 | <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span> |
| 1751 | </PullRequestMetaLine> |
| 1752 | {checkoutCommand ? ( |
| 1753 | <PullRequestCopyableCode |
| 1754 | key={checkoutCommand} |
| 1755 | value={checkoutCommand} |
| 1756 | target="pull request checkout command" |
| 1757 | copyLabel="Copy checkout command" |
| 1758 | copiedLabel="Checkout command copied" |
| 1759 | className="ml-auto font-mono" |
| 1760 | tooltipSide="bottom" |
| 1761 | onError={(error) => |
| 1762 | toastManager.add({ |
| 1763 | type: "error", |
| 1764 | title: "Could not copy checkout command", |
| 1765 | description: error.message, |
| 1766 | }) |
| 1767 | } |
| 1768 | /> |
| 1769 | ) : null} |
| 1770 | </div> |
| 1704 | 1771 | |
| 1705 | 1772 | <div className="mt-4 flex min-w-0 items-center gap-2 text-xs text-muted-foreground"> |
| 1706 | 1773 | <span className="flex min-w-0 flex-1 items-center gap-1.5 font-mono text-xs text-muted-foreground/70"> |
| ... | ... | @@ -1736,62 +1803,13 @@ export function PullRequestDetailPanel({ |
| 1736 | 1803 | aria-label="receives changes from" |
| 1737 | 1804 | className="size-3.5 shrink-0 opacity-60" |
| 1738 | 1805 | /> |
| 1739 | | <Tooltip> |
| 1740 | | <TooltipTrigger |
| 1741 | | render={ |
| 1742 | | <button |
| 1743 | | type="button" |
| 1744 | | className="grid w-fit min-w-0 max-w-full shrink cursor-pointer rounded px-1 py-0.5 text-left outline-none transition-colors hover:bg-accent/45 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background" |
| 1745 | | aria-label={ |
| 1746 | | isBranchCopied ? "Branch name copied" : "Copy pull request branch" |
| 1747 | | } |
| 1748 | | onClick={() => copyBranchToClipboard(detail.headBranch)} |
| 1749 | | /> |
| 1750 | | } |
| 1751 | | > |
| 1752 | | <code |
| 1753 | | className={cn( |
| 1754 | | "col-start-1 row-start-1 min-w-0 truncate transition-opacity duration-150 motion-reduce:transition-none", |
| 1755 | | isBranchCopied ? "opacity-0" : "opacity-100", |
| 1756 | | )} |
| 1757 | | > |
| 1758 | | {detail.headBranch} |
| 1759 | | </code> |
| 1760 | | <span |
| 1761 | | aria-hidden="true" |
| 1762 | | className={cn( |
| 1763 | | "col-start-1 row-start-1 truncate text-center transition-opacity duration-150 motion-reduce:transition-none", |
| 1764 | | isBranchCopied ? "opacity-100" : "opacity-0", |
| 1765 | | )} |
| 1766 | | > |
| 1767 | | Copied |
| 1768 | | </span> |
| 1769 | | </TooltipTrigger> |
| 1770 | | <TooltipPopup side="top"> |
| 1771 | | {`${isBranchCopied ? "Copied" : "Copy pull request branch"}: ${detail.headBranch}`} |
| 1772 | | </TooltipPopup> |
| 1773 | | </Tooltip> |
| 1774 | | {checkoutCommand ? ( |
| 1775 | | <Tooltip> |
| 1776 | | <TooltipTrigger |
| 1777 | | render={ |
| 1778 | | <Button |
| 1779 | | size="micro" |
| 1780 | | variant="outline" |
| 1781 | | className="ml-1 min-w-0 basis-40 shrink-[0.75] gap-1 font-normal text-muted-foreground" |
| 1782 | | aria-label={`Copy checkout command: ${checkoutCommand}`} |
| 1783 | | onClick={() => copyCheckoutCommandToClipboard(checkoutCommand)} |
| 1784 | | /> |
| 1785 | | } |
| 1786 | | > |
| 1787 | | <code className="min-w-0 truncate">{checkoutCommand}</code> |
| 1788 | | <CopyIcon aria-hidden /> |
| 1789 | | </TooltipTrigger> |
| 1790 | | <TooltipPopup className="max-w-96 wrap-anywhere font-mono" side="bottom"> |
| 1791 | | {checkoutCommand} |
| 1792 | | </TooltipPopup> |
| 1793 | | </Tooltip> |
| 1794 | | ) : null} |
| 1806 | <PullRequestCopyableCode |
| 1807 | key={detail.headBranch} |
| 1808 | value={detail.headBranch} |
| 1809 | target="branch name" |
| 1810 | copyLabel="Copy pull request branch" |
| 1811 | copiedLabel="Branch name copied" |
| 1812 | /> |
| 1795 | 1813 | </span> |
| 1796 | 1814 | <span className="ml-auto inline-flex shrink-0 items-center justify-end gap-2"> |
| 1797 | 1815 | <span className="inline-flex items-center gap-1.5 tabular-nums"> |