import * as React from "react"; import { useMemo, useState } from "react"; import * as RouterDom from "react-router-dom"; import type { Issue } from "@paperclipai/shared"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { timeAgo } from "@/lib/timeAgo"; import { createIssueDetailPath, withIssueDetailHeaderSeed } from "@/lib/issueDetailBreadcrumb"; import { fetchIssueDetail, getCachedIssueDetail, ISSUE_DETAIL_STALE_TIME_MS, prefetchIssueDetail, } from "@/lib/issueDetailCache"; import { queryKeys } from "@/lib/queryKeys"; import { cn } from "@/lib/utils"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { StatusIcon } from "@/components/StatusIcon"; function summarizeIssueDescription(description: string | null | undefined) { if (!description) return null; const summary = description .replace(/!\[[^\]]*]\([^)]+\)/g, " ") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/[#>*_`~-]+/g, " ") .replace(/\s+/g, " ") .trim(); if (!summary) return null; return summary.length > 180 ? `${summary.slice(0, 177).trimEnd()}...` : summary; } export function IssueQuicklookCard({ issue, linkTo, linkState, compact = false, }: { issue: Issue; linkTo: RouterDom.To; linkState?: unknown; compact?: boolean; }) { const description = useMemo(() => summarizeIssueDescription(issue.description), [issue.description]); return (
{issue.title}
{issue.identifier ?? issue.id.slice(0, 8)} · {issue.status.replace(/_/g, " ")} · {timeAgo(new Date(issue.updatedAt))}
{description ? (

{description}

) : null}
); } export const IssueLinkQuicklook = React.forwardRef< HTMLAnchorElement, React.ComponentProps & { issuePathId: string; disableIssueQuicklook?: boolean; issuePrefetch?: Issue | null; } >(function IssueLinkQuicklookImpl( { issuePathId, to, children, className, state, disableIssueQuicklook = false, issuePrefetch = null, onClick, onClickCapture, onMouseEnter, onFocus, onTouchStart, ...props }, ref, ) { const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const prefetchedState = issuePrefetch ? withIssueDetailHeaderSeed(state, issuePrefetch) : state; const cachedIssue = getCachedIssueDetail(queryClient, issuePathId, issuePrefetch ?? undefined); const { data, isLoading } = useQuery({ queryKey: queryKeys.issues.detail(issuePathId), queryFn: () => fetchIssueDetail(queryClient, issuePathId), enabled: open, initialData: () => cachedIssue, staleTime: ISSUE_DETAIL_STALE_TIME_MS, }); const detailPath = createIssueDetailPath(issuePathId); const handlePrefetch = React.useCallback(() => { void prefetchIssueDetail(queryClient, issuePathId, { issue: issuePrefetch }); }, [issuePathId, issuePrefetch, queryClient]); const link = ( { handlePrefetch(); onMouseEnter?.(event); }} onFocus={(event) => { handlePrefetch(); onFocus?.(event); }} onTouchStart={(event) => { handlePrefetch(); onTouchStart?.(event); }} onClickCapture={(event) => { handlePrefetch(); onClickCapture?.(event); }} onClick={(event) => { setOpen(false); onClick?.(event); }} {...props} > {children} ); if (disableIssueQuicklook) { return link; } return ( { handlePrefetch(); setOpen(true); }} onMouseLeave={() => setOpen(false)} > {link} setOpen(true)} onMouseLeave={() => setOpen(false)} onOpenAutoFocus={(event) => event.preventDefault()} > {data ? ( ) : (
{!isLoading ? (

Unable to load issue preview.

) : null}
)} ); });