import React, { useState, useCallback } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus, prism } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { useTheme } from '../../../contexts/ThemeContext'; import useRenderTracker from '../../../hooks/useRenderTracker'; interface CodeBlockProps { content: string; language?: string; } // Custom styles for dark mode const customDarkStyle = { ...vscDarkPlus, 'code[class*="language-"]': { ...vscDarkPlus['code[class*="language-"]'], background: 'transparent', backgroundColor: 'transparent', }, 'pre[class*="language-"]': { ...vscDarkPlus['pre[class*="language-"]'], background: 'transparent', backgroundColor: 'transparent', }, }; // Custom styles for light mode const customLightStyle = { ...prism, 'code[class*="language-"]': { ...prism['code[class*="language-"]'], background: 'transparent', backgroundColor: 'transparent', }, 'pre[class*="language-"]': { ...prism['pre[class*="language-"]'], background: 'transparent', backgroundColor: 'transparent', }, }; // Modern API fails on insecure origins; this helper ensures we only attempt it when allowed. const copyWithNavigatorClipboard = async (text: string): Promise => { if ( typeof navigator === 'undefined' || !navigator.clipboard?.writeText || (typeof window !== 'undefined' && window.isSecureContext === false) ) { return false; } try { await navigator.clipboard.writeText(text); return true; } catch { return false; } }; // Legacy fallback using a hidden textarea + execCommand so HTTP dashboards still work. const copyWithFallback = (text: string): boolean => { if ( typeof document === 'undefined' || !document.body || typeof document.execCommand !== 'function' ) { return false; } const textarea = document.createElement('textarea'); textarea.value = text; textarea.setAttribute('readonly', ''); textarea.style.position = 'fixed'; textarea.style.top = '-9999px'; textarea.style.opacity = '0'; document.body.appendChild(textarea); const selection = document.getSelection(); const originalRange = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; textarea.focus(); textarea.select(); textarea.setSelectionRange(0, textarea.value.length); let copied = false; try { copied = document.execCommand('copy'); } catch { copied = false; } document.body.removeChild(textarea); if (originalRange && selection) { selection.removeAllRanges(); selection.addRange(originalRange); } return copied; }; const CodeBlock: React.FC = ({ content, language}) => { const { effectiveTheme } = useTheme(); const [isCopied, setIsCopied] = useState(false); useRenderTracker('CodeBlock', { content, language, // isComplete, // isCopied, effectiveTheme, }); // Initialize variables language = language || 'text'; const handleCopyClick = useCallback(async () => { const copied = (await copyWithNavigatorClipboard(content)) || copyWithFallback(content); if (copied) { setIsCopied(true); setTimeout(() => setIsCopied(false), 3000); } else { console.error('Failed to copy text: unsupported environment'); } }, [content]); return (
{language}
{content}
); } export default React.memo(CodeBlock);