From e9b6b51e9bdd3dc0b9585e8ccd9c97b8f7dd5fb2 Mon Sep 17 00:00:00 2001 From: Naman Rajpal Date: Sun, 11 Jan 2026 23:05:32 -0800 Subject: [PATCH 1/4] Adding markdown preview in FilePreview --- .../components/file-browser/FilePreview.tsx | 160 +++++++++++++++++- 1 file changed, 158 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/file-browser/FilePreview.tsx b/frontend/src/components/file-browser/FilePreview.tsx index 733a5bb..788fa78 100644 --- a/frontend/src/components/file-browser/FilePreview.tsx +++ b/frontend/src/components/file-browser/FilePreview.tsx @@ -1,9 +1,14 @@ import { useState, useCallback, useRef, useEffect, memo } from 'react' import { Button } from '@/components/ui/button' -import { Download, X, Edit3, Save, X as XIcon, WrapText } from 'lucide-react' +import { Download, X, Edit3, Save, X as XIcon, WrapText, Eye, Code } from 'lucide-react' import type { FileInfo } from '@/types/files' import { API_BASE_URL } from '@/config' import { VirtualizedTextView, type VirtualizedTextViewHandle } from '@/components/ui/virtualized-text-view' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import rehypeHighlight from 'rehype-highlight' +import rehypeRaw from 'rehype-raw' +import 'highlight.js/styles/github-dark.css' const API_BASE = API_BASE_URL @@ -20,18 +25,38 @@ interface FilePreviewProps { } export const FilePreview = memo(function FilePreview({ file, hideHeader = false, isMobileModal = false, onCloseModal, onFileSaved, initialLineNumber }: FilePreviewProps) { + const isMarkdownFile = file.name.endsWith('.md') || file.name.endsWith('.mdx') || file.mimeType === 'text/markdown' + const [viewMode, setViewMode] = useState<'preview' | 'edit'>('preview') const [editContent, setEditContent] = useState('') const [isSaving, setIsSaving] = useState(false) const [hasVirtualizedChanges, setHasVirtualizedChanges] = useState(false) const [highlightedLine, setHighlightedLine] = useState(initialLineNumber) const [lineWrap, setLineWrap] = useState(true) + const [markdownPreview, setMarkdownPreview] = useState(isMarkdownFile) + const [fullMarkdownContent, setFullMarkdownContent] = useState(null) + const [isLoadingFullContent, setIsLoadingFullContent] = useState(false) const virtualizedRef = useRef(null) const contentRef = useRef(null) const shouldVirtualize = file.size > VIRTUALIZATION_THRESHOLD_BYTES && !file.mimeType?.startsWith('image/') + useEffect(() => { + if (shouldVirtualize && isMarkdownFile && markdownPreview && !fullMarkdownContent) { + setIsLoadingFullContent(true) + fetch(`${API_BASE}/api/files/${file.path}?raw=true`) + .then(res => res.text()) + .then(content => { + setFullMarkdownContent(content) + setIsLoadingFullContent(false) + }) + .catch(() => { + setIsLoadingFullContent(false) + }) + } + }, [shouldVirtualize, isMarkdownFile, markdownPreview, fullMarkdownContent, file.path]) + useEffect(() => { @@ -165,6 +190,69 @@ export const FilePreview = memo(function FilePreview({ file, hideHeader = false, } if (shouldVirtualize && isTextFile) { + if (isMarkdownFile && markdownPreview && viewMode !== 'edit') { + if (isLoadingFullContent) { + return ( +
+
+
+ ) + } + if (fullMarkdownContent) { + return ( +
+ + {children} + + ) + } + return {children} + }, + pre({ children }) { + return ( +
+                        {children}
+                      
+ ) + }, + p({ children }) { + return

{children}

+ }, + strong({ children }) { + return {children} + }, + ul({ children }) { + return
    {children}
+ }, + ol({ children }) { + return
    {children}
+ }, + li({ children }) { + return
  • {children}
  • + }, + table({ children }) { + return ( +
    + {children}
    +
    + ) + } + }} + > + {fullMarkdownContent} +
    +
    + ) + } + } return ( ) } + + if (isMarkdownFile && markdownPreview) { + return ( +
    + + {children} + + ) + } + return {children} + }, + pre({ children }) { + return ( +
    +                        {children}
    +                      
    + ) + }, + p({ children }) { + return

    {children}

    + }, + strong({ children }) { + return {children} + }, + ul({ children }) { + return
      {children}
    + }, + ol({ children }) { + return
      {children}
    + }, + li({ children }) { + return
  • {children}
  • + }, + table({ children }) { + return ( +
    + {children}
    +
    + ) + } + }} + > + {textContent} +
    +
    + ) + } + const lines = textContent.split('\n') return (
    - {isTextFile && ( + {isMarkdownFile && viewMode !== 'edit' && ( + + )} + + {isTextFile && !markdownPreview && (