From 53ffd5fa9d8d981493eaaa6f46fc9678b3655488 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 19:05:47 +0000 Subject: [PATCH 1/2] feat: Add HTML preview button for code blocks Add a preview button that appears on HTML code blocks containing full HTML pages. When clicked, it opens a modal with an iframe to render the HTML securely. Changes: - Add Eye icon import from lucide-react for preview button - Add Modal component import for preview popup - Add state management for HTML preview modal - Add helper function to detect full HTML pages (checks for DOCTYPE, html tags, or head/body tags) - Add preview button in code block header (conditionally shown for HTML pages) - Add modal with iframe for secure HTML rendering using srcDoc - Use sandbox attribute on iframe for security (allow-scripts allow-same-origin) The preview button appears alongside existing code block actions (wrap, copy, collapse) and uses the existing Modal component with a larger max-width (max-w-6xl) for better viewing. --- frontend/components/Markdown.tsx | 60 +++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/frontend/components/Markdown.tsx b/frontend/components/Markdown.tsx index 9ee8e3d2..e97e2b8a 100644 --- a/frontend/components/Markdown.tsx +++ b/frontend/components/Markdown.tsx @@ -6,7 +6,8 @@ import rehypeHighlight from 'rehype-highlight'; import rehypeKatex from 'rehype-katex'; import 'katex/dist/katex.min.css'; import { useTheme } from '../contexts/ThemeContext'; -import { ClipboardCheck, Clipboard, ChevronDown, ChevronUp, WrapText, Brain } from 'lucide-react'; +import { ClipboardCheck, Clipboard, ChevronDown, ChevronUp, WrapText, Brain, Eye } from 'lucide-react'; +import { Modal } from './ui/Modal'; interface MarkdownProps { text: string; @@ -327,6 +328,19 @@ const MarkdownComponents: any = { const [copied, setCopied] = React.useState(false); const [isCollapsed, setIsCollapsed] = React.useState(false); const [isWrapped, setIsWrapped] = React.useState(false); + const [showHtmlPreview, setShowHtmlPreview] = React.useState(false); + + // Helper function to check if code is a full HTML page + const isFullHtmlPage = (code: string): boolean => { + if (!code) return false; + const trimmed = code.trim().toLowerCase(); + // Check for DOCTYPE, html tag, or both head and body tags + return ( + trimmed.includes('') || + (trimmed.includes('')) || + (trimmed.includes(' { try { @@ -362,6 +376,12 @@ const MarkdownComponents: any = { const codeClassName = childEl?.props?.className || ''; const languageMatch = codeClassName.match(/language-(\w+)/); const language = languageMatch ? languageMatch[1] : null; + + // Get code content for HTML detection + const codeContent = typeof childEl?.props?.children === 'string' + ? childEl.props.children + : ''; + const isHtml = language === 'html' && isFullHtmlPage(codeContent); const wrappedChild = React.isValidElement(childEl) ? React.cloneElement(childEl as React.ReactElement, { className: `${codeClassName} ${ @@ -371,10 +391,11 @@ const MarkdownComponents: any = { : childEl; return ( -
+      <>
+        
         {/* Header with language and copy button */}
         
{language && {language}} @@ -405,6 +426,17 @@ const MarkdownComponents: any = { )} {copied ? 'Copied' : 'Copy'} + {isHtml && ( + + )}
+ + {/* HTML Preview Modal */} + {isHtml && ( + setShowHtmlPreview(false)} + title="HTML Preview" + maxWidthClassName="max-w-6xl" + > +