Render rich issue attachment previews

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-06-01 17:40:00 +00:00
parent 96feaa331a
commit e86d000c7b
6 changed files with 640 additions and 132 deletions
+47
View File
@@ -0,0 +1,47 @@
import type { IssueAttachment } from "@paperclipai/shared";
import { isVideoContentType } from "./issue-output";
function normalizedContentType(attachment: Pick<IssueAttachment, "contentType">) {
return attachment.contentType.toLowerCase().split(";")[0]?.trim() ?? "";
}
export function attachmentFilename(attachment: Pick<IssueAttachment, "id" | "originalFilename">) {
return attachment.originalFilename ?? attachment.id;
}
export function attachmentOpenPath(
attachment: Pick<IssueAttachment, "contentPath" | "openPath">,
) {
return attachment.openPath ?? attachment.contentPath;
}
export function attachmentDownloadPath(
attachment: Pick<IssueAttachment, "contentPath" | "downloadPath">,
) {
return attachment.downloadPath ?? `${attachment.contentPath}?download=1`;
}
export function isImageAttachment(attachment: Pick<IssueAttachment, "contentType">) {
return normalizedContentType(attachment).startsWith("image/");
}
export function isVideoAttachment(attachment: Pick<IssueAttachment, "contentType">) {
return isVideoContentType(normalizedContentType(attachment));
}
export function isMarkdownAttachment(
attachment: Pick<IssueAttachment, "contentType" | "originalFilename">,
) {
const contentType = normalizedContentType(attachment);
if (
contentType === "text/markdown" ||
contentType === "text/x-markdown" ||
contentType === "application/markdown" ||
contentType === "application/x-markdown"
) {
return true;
}
const filename = (attachment.originalFilename ?? "").toLowerCase();
return filename.endsWith(".md") || filename.endsWith(".markdown");
}
+1
View File
@@ -67,6 +67,7 @@ export const queryKeys = {
? (["issues", "cost-summary", issueId, "exclude-root"] as const)
: (["issues", "cost-summary", issueId] as const),
attachments: (issueId: string) => ["issues", "attachments", issueId] as const,
attachmentPreview: (attachmentId: string) => ["issues", "attachment-preview", attachmentId] as const,
documents: (issueId: string) => ["issues", "documents", issueId] as const,
document: (issueId: string, key: string) => ["issues", "document", issueId, key] as const,
documentRevisions: (issueId: string, key: string) => ["issues", "document-revisions", issueId, key] as const,