"use client"; import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { X, Download, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; interface FilePreviewModalProps { name: string; onClose: () => void; onDownload: (name: string) => Promise; getFileContent: (name: string) => Promise<{ blob: Blob; contentType: string }>; } const TEXT_EXTENSIONS = new Set([ "txt", "md", "markdown", "json", "xml", "html", "htm", "css", "js", "ts", "jsx", "tsx", "py", "rb", "java", "c", "cpp", "h", "hpp", "go", "rs", "sh", "bash", "zsh", "yaml", "yml", "toml", "ini", "cfg", "conf", "env", "log", "csv", "sql", "graphql", "vue", "svelte", "astro", "php", "pl", "swift", "kt", "scala", "r", "lua", "vim", ]); function getFileType(name: string): "text" | "pdf" | "audio" | "video" | "markdown" | "unknown" { const ext = name.split(".").pop()?.toLowerCase() || ""; const baseName = name.toLowerCase(); if (ext === "md" || ext === "markdown") return "markdown"; if (ext === "pdf") return "pdf"; if (["mp3", "wav", "ogg", "flac", "aac", "m4a", "wma", "opus"].includes(ext)) return "audio"; if (["mp4", "webm", "ogv", "mov", "avi", "mkv", "m4v"].includes(ext)) return "video"; if (TEXT_EXTENSIONS.has(ext) || ["dockerfile", "makefile", "readme", "license", "changelog"].includes(baseName)) return "text"; return "unknown"; } function SimpleMarkdown({ content }: { content: string }) { const lines = content.split("\n"); const elements: React.ReactNode[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Headers if (line.startsWith("### ")) { elements.push(

{processInline(line.slice(4))}

); } else if (line.startsWith("## ")) { elements.push(

{processInline(line.slice(3))}

); } else if (line.startsWith("# ")) { elements.push(

{processInline(line.slice(2))}

); } else if (line.startsWith("---") || line.startsWith("***")) { elements.push(
); } else if (line.startsWith("- ") || line.startsWith("* ")) { elements.push(
  • {processInline(line.slice(2))}
  • ); } else if (/^\d+\. /.test(line)) { elements.push(
  • {processInline(line.replace(/^\d+\. /, ""))}
  • ); } else if (line.startsWith("> ")) { elements.push(
    {processInline(line.slice(2))}
    ); } else if (line.startsWith("```")) { // Code block - collect until closing ``` const codeLines: string[] = []; i++; while (i < lines.length && !lines[i].startsWith("```")) { codeLines.push(lines[i]); i++; } elements.push(
              {codeLines.join("\n")}
            
    ); } else if (line.trim() === "") { elements.push(
    ); } else { elements.push(

    {processInline(line)}

    ); } } return
    {elements}
    ; } function processInline(text: string): React.ReactNode { // Process bold, italic, code inline const parts: React.ReactNode[] = []; let remaining = text; let key = 0; while (remaining.length > 0) { // Bold const boldMatch = remaining.match(/\*\*(.+?)\*\*/); // Inline code const codeMatch = remaining.match(/`([^`]+)`/); // Italic const italicMatch = remaining.match(/(? (a!.match.index ?? 0) - (b!.match.index ?? 0)); if (matches.length === 0) { parts.push(remaining); break; } const first = matches[0]!; const idx = first.match.index ?? 0; if (idx > 0) { parts.push(remaining.slice(0, idx)); } if (first.type === "bold") { parts.push({first.match[1]}); } else if (first.type === "code") { parts.push({first.match[1]}); } else { parts.push({first.match[1]}); } remaining = remaining.slice(idx + first.match[0].length); } return parts.length === 1 ? parts[0] : <>{parts}; } export function FilePreviewModal({ name, onClose, onDownload, getFileContent }: FilePreviewModalProps) { const t = useTranslations("files"); const [content, setContent] = useState(null); const [objectUrl, setObjectUrl] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const fileType = getFileType(name); useEffect(() => { let cancelled = false; async function load() { try { const { blob, contentType } = await getFileContent(name); if (cancelled) return; if (fileType === "text" || fileType === "markdown") { const text = await blob.text(); if (!cancelled) setContent(text); } else { const url = URL.createObjectURL(blob); if (!cancelled) setObjectUrl(url); } } catch { if (!cancelled) setError(true); } finally { if (!cancelled) setLoading(false); } } load(); return () => { cancelled = true; if (objectUrl) URL.revokeObjectURL(objectUrl); }; }, [name]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [onClose]); return (
    e.stopPropagation()}>

    {name}

    e.stopPropagation()}> {loading && (
    )} {error && (

    {t("preview_error")}

    )} {!loading && !error && (fileType === "text") && content !== null && (
                {content}
              
    )} {!loading && !error && fileType === "markdown" && content !== null && (
    )} {!loading && !error && fileType === "pdf" && objectUrl && (