mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 21:07:40 +01:00
113 lines
4.3 KiB
TypeScript
113 lines
4.3 KiB
TypeScript
import { useState } from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import type { IssueDocument } from "@paperclipai/shared";
|
|
import { FileQuestion, Loader2 } from "lucide-react";
|
|
import { ApiError } from "@/api/client";
|
|
import { issuesApi } from "@/api/issues";
|
|
import { DocumentAnnotationsCountChip, IssueDocumentAnnotations } from "@/components/IssueDocumentAnnotations";
|
|
import { MarkdownBody } from "@/components/MarkdownBody";
|
|
import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
|
import { queryKeys } from "@/lib/queryKeys";
|
|
import { documentDisplayTitle } from "@/lib/issue-artifacts";
|
|
import { useLocation } from "@/lib/router";
|
|
|
|
export function TaskDocumentPanel({
|
|
issueId,
|
|
documentKey,
|
|
initialDocument,
|
|
revisionId,
|
|
}: {
|
|
issueId: string;
|
|
documentKey: string;
|
|
initialDocument?: IssueDocument;
|
|
revisionId?: string;
|
|
}) {
|
|
const location = useLocation();
|
|
const [annotationPanelOpen, setAnnotationPanelOpen] = useState(false);
|
|
const query = useQuery<IssueDocument | null>({
|
|
queryKey: [...queryKeys.issues.documents(issueId), documentKey, ...(revisionId ? ["revision", revisionId] : [])],
|
|
queryFn: async () => {
|
|
try {
|
|
const current = await issuesApi.getDocument(issueId, documentKey);
|
|
if (!revisionId) return current;
|
|
const revision = await issuesApi.getDocumentRevision(issueId, documentKey, revisionId);
|
|
return { ...current, title: revision.title, body: revision.body, format: revision.format,
|
|
latestRevisionId: revision.id, latestRevisionNumber: revision.revisionNumber, updatedAt: revision.createdAt };
|
|
} catch (error) {
|
|
if (error instanceof ApiError && error.status === 404) return null;
|
|
throw error;
|
|
}
|
|
},
|
|
initialData: revisionId ? undefined : initialDocument,
|
|
});
|
|
|
|
const view = useQueryView(query);
|
|
|
|
if (query.isLoading || view.kind === "reconnecting") {
|
|
return (
|
|
<div className="flex items-center gap-2 py-8 text-sm text-muted-foreground" role="status">
|
|
<Loader2 className="size-4 animate-spin" aria-hidden />
|
|
Loading document…
|
|
</div>
|
|
);
|
|
}
|
|
if (view.kind === "error") {
|
|
return (
|
|
<div className="py-6">
|
|
<QueryErrorState error={query.error} action="load the document" onRetry={view.retry} retrying={view.isFetching} />
|
|
</div>
|
|
);
|
|
}
|
|
const document = query.data;
|
|
if (!document) {
|
|
return (
|
|
<div className="flex items-start gap-3 py-8 text-sm" role="status">
|
|
<FileQuestion className="mt-0.5 size-5 shrink-0 text-muted-foreground" aria-hidden />
|
|
<div className="space-y-1">
|
|
<p className="font-medium">{revisionId ? "Reviewed revision unavailable" : "Document no longer available"}</p>
|
|
<p className="text-muted-foreground">
|
|
{revisionId ? "The exact reviewed revision could not be found. The current version has not been substituted." : "Close this tab or choose another document."}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<article className="space-y-4">
|
|
<header className="space-y-1">
|
|
<h2 className="text-lg font-semibold">{documentDisplayTitle(document)}</h2>
|
|
<div className="flex flex-wrap items-center gap-1 text-xs text-muted-foreground">
|
|
<span>{`Revision ${document.latestRevisionNumber ?? 1}`}</span>
|
|
<span aria-hidden>·</span>
|
|
<span>{`Updated ${new Date(document.updatedAt).toLocaleString()}`}</span>
|
|
{!revisionId ? <DocumentAnnotationsCountChip
|
|
issueId={issueId}
|
|
docKey={document.key}
|
|
panelOpen={annotationPanelOpen}
|
|
onToggle={() => setAnnotationPanelOpen((open) => !open)}
|
|
/> : <span>· Reviewed version</span>}
|
|
</div>
|
|
</header>
|
|
{document.body.trim() ? (
|
|
<IssueDocumentAnnotations
|
|
issueId={issueId}
|
|
doc={document}
|
|
bodyMarkdown={document.body}
|
|
draftDirty={false}
|
|
draftConflicted={false}
|
|
historicalPreview={!!revisionId}
|
|
locationHash={location.hash}
|
|
panelOpen={annotationPanelOpen}
|
|
onPanelOpenChange={setAnnotationPanelOpen}
|
|
panelPlacement="popover"
|
|
>
|
|
<MarkdownBody>{document.body}</MarkdownBody>
|
|
</IssueDocumentAnnotations>
|
|
) : (
|
|
<p className="text-sm text-muted-foreground">Document is empty.</p>
|
|
)}
|
|
</article>
|
|
);
|
|
}
|