Compare commits

...
Author SHA1 Message Date
Marc Seitz 7f1c4d03ca feat: ensure filename when downloading file 2025-11-10 16:52:02 +04:00
7 changed files with 98 additions and 8 deletions
+5 -2
View File
@@ -32,7 +32,7 @@ import {
DocumentWithLinksAndLinkCountAndViewCount,
DocumentWithVersion,
} from "@/lib/types";
import { cn } from "@/lib/utils";
import { cn, ensureFileExtension } from "@/lib/utils";
import { supportsAdvancedExcelMode } from "@/lib/utils/get-content-type";
import { fileIcon } from "@/lib/utils/get-file-icon";
@@ -477,7 +477,10 @@ export default function DocumentHeader({
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = prismaDocument.name;
a.download = ensureFileExtension(prismaDocument.name, {
type: documentVersion.type || undefined,
contentType: documentVersion.contentType || undefined,
});
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
+5 -2
View File
@@ -6,7 +6,7 @@ import { Download, MoreVerticalIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { toast } from "sonner";
import { timeAgo } from "@/lib/utils";
import { ensureFileExtension, timeAgo } from "@/lib/utils";
import { cn } from "@/lib/utils";
import { fileIcon } from "@/lib/utils/get-file-icon";
import {
@@ -136,7 +136,10 @@ export default function DocumentCard({
disposition && disposition.match(/filename="(.+)"/);
link.download = filenameMatch
? decodeURIComponent(filenameMatch[1])
: document.name;
: ensureFileExtension(document.name, {
type: document.versions[0]?.type || undefined,
contentType: document.versions[0]?.contentType || undefined,
});
link.rel = "noopener noreferrer";
window.document.body.appendChild(link);
link.click();
@@ -91,6 +91,7 @@ export type DocumentVersion = {
hasPages: boolean;
isVertical: boolean;
updatedAt: Date;
contentType: string;
};
type DataroomDocument = {
@@ -6,6 +6,7 @@ import { Document, Page, pdfjs } from "react-pdf";
import { useSafePageViewTracker } from "@/lib/tracking/safe-page-view-tracker";
import { getTrackingOptions } from "@/lib/tracking/tracking-config";
import { ensureFileExtension } from "@/lib/utils";
import Nav from "@/components/view/nav";
@@ -231,7 +232,7 @@ export default function PDFViewer(props: any) {
//create <a/> to download the file
const a = document.createElement("a");
a.href = window.URL.createObjectURL(fileData);
a.download = props.name;
a.download = ensureFileExtension(props.name, { type: "pdf" });
document.body.appendChild(a);
a.click();
+2
View File
@@ -141,6 +141,7 @@ export async function fetchDataroomLinkData({
file: true,
isVertical: true,
updatedAt: true,
contentType: true,
},
take: 1,
},
@@ -323,6 +324,7 @@ export async function fetchDataroomDocumentLinkData({
hasPages: true,
file: true,
isVertical: true,
contentType: true,
},
take: 1,
},
+65
View File
@@ -15,6 +15,8 @@ import { ParsedUrlQuery } from "querystring";
import { toast } from "sonner";
import { twMerge } from "tailwind-merge";
import { getExtensionFromContentType } from "./utils/get-content-type";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
@@ -41,6 +43,69 @@ export function getFileNameWithPdfExtension(filename?: string): string {
return `${nameWithoutExt}.pdf`;
}
/**
* Ensures a filename has the correct file extension based on document type or contentType
* If the filename already has an extension, it is preserved.
* If not, the appropriate extension is added based on the provided type/contentType.
*/
export function ensureFileExtension(
filename?: string,
options?: {
type?: string;
contentType?: string;
},
): string {
if (!filename) return "document";
// Check if filename already has an extension
const hasExtension = /\.[^/.]+$/.test(filename);
if (hasExtension) {
return filename;
}
// If no extension, determine the appropriate one
let extension = "";
// First try to get extension from contentType if provided
if (options?.contentType) {
const ext = getExtensionFromContentType(options.contentType);
if (ext) {
extension = ext;
}
}
// If no extension from contentType, try using the document type
if (!extension && options?.type) {
switch (options.type) {
case "pdf":
extension = "pdf";
break;
case "image":
extension = "png"; // default image extension
break;
case "sheet":
extension = "xlsx";
break;
case "docs":
extension = "docx";
break;
case "slides":
extension = "pptx";
break;
case "video":
extension = "mp4";
break;
default:
// No extension determined
break;
}
}
// Return filename with extension if one was determined
return extension ? `${filename}.${extension}` : filename;
}
interface SWRError extends Error {
status: number;
}
+18 -3
View File
@@ -6,7 +6,7 @@ import { waitUntil } from "@vercel/functions";
import { getFile } from "@/lib/files/get-file";
import { notifyDocumentDownload } from "@/lib/integrations/slack/events";
import prisma from "@/lib/prisma";
import { getFileNameWithPdfExtension } from "@/lib/utils";
import { ensureFileExtension, getFileNameWithPdfExtension } from "@/lib/utils";
import { getIpAddress } from "@/lib/utils/ip";
export const config = {
@@ -281,7 +281,15 @@ export default async function handle(
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${encodeURIComponent(downloadDocuments[0].document!.name)}"`,
`attachment; filename="${encodeURIComponent(
ensureFileExtension(downloadDocuments[0].document!.name, {
type:
downloadDocuments[0].document!.versions[0].type || undefined,
contentType:
downloadDocuments[0].document!.versions[0].contentType ||
undefined,
}),
)}"`,
);
res.setHeader("Content-Length", Buffer.from(pdfBuffer).length);
res.setHeader("Cache-Control", "no-cache");
@@ -295,7 +303,14 @@ export default async function handle(
downloadDocuments[0].document!.versions[0].contentType ||
headResponse.headers.get("content-type") ||
"application/octet-stream";
const fileName = downloadDocuments[0].document!.name;
const fileName = ensureFileExtension(
downloadDocuments[0].document!.name,
{
type: downloadDocuments[0].document!.versions[0].type || undefined,
contentType:
downloadDocuments[0].document!.versions[0].contentType || undefined,
},
);
// For all other files, return direct download URL
return res.status(200).json({