feat(代码块): 添加 HTML 代码预览功能

- 在代码块工具栏添加预览按钮
- 支持 HTML/HTM 类型代码的实时预览
- 集成 HtmlPreviewModal 模态框组件
This commit is contained in:
gaoziman 2025-12-21 01:15:02 +08:00
parent 959fedf1d0
commit 1c114a764e

View File

@ -1,9 +1,10 @@
'use client'; 'use client';
import { useEffect, useRef, useState, useMemo } from 'react'; import { useEffect, useRef, useState, useMemo } from 'react';
import { Copy, Check } from 'lucide-react'; import { Copy, Check, Eye } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import Prism from 'prismjs'; import Prism from 'prismjs';
import { HtmlPreviewModal } from '@/components/ui/HtmlPreviewModal';
import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-typescript'; import 'prismjs/components/prism-typescript';
import 'prismjs/components/prism-jsx'; import 'prismjs/components/prism-jsx';
@ -52,10 +53,15 @@ export function CodeBlock({
className, className,
}: CodeBlockProps) { }: CodeBlockProps) {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [previewOpen, setPreviewOpen] = useState(false);
// 规范化语言名称 // 规范化语言名称
const normalizedLanguage = languageAliases[language.toLowerCase()] || language.toLowerCase(); const normalizedLanguage = languageAliases[language.toLowerCase()] || language.toLowerCase();
// 判断是否支持 HTML 预览
const isHtmlPreviewable = ['html', 'htm', 'markup'].includes(normalizedLanguage) ||
['html', 'htm'].includes(language.toLowerCase());
// 使用 useMemo 缓存高亮后的 HTML避免频繁重新高亮 // 使用 useMemo 缓存高亮后的 HTML避免频繁重新高亮
const highlightedCode = useMemo(() => { const highlightedCode = useMemo(() => {
const grammar = Prism.languages[normalizedLanguage]; const grammar = Prism.languages[normalizedLanguage];
@ -78,27 +84,41 @@ export function CodeBlock({
const lines = code.split('\n'); const lines = code.split('\n');
return ( return (
<div className={cn('relative group rounded-lg overflow-hidden my-4', className)}> <div className={cn('relative group rounded overflow-hidden my-4', className)}>
{/* 顶部工具栏 */} {/* 顶部工具栏 */}
<div className="flex items-center justify-between px-4 py-2 bg-[#2d2d2d] text-gray-400 text-sm"> <div className="flex items-center justify-between px-4 py-2 bg-[#2d2d2d] text-gray-400 text-sm">
<span className="font-mono">{language || 'code'}</span> <span className="font-mono">{language || 'code'}</span>
<button <div className="flex items-center gap-2">
onClick={handleCopy} {/* HTML 预览按钮 */}
className="inline-flex items-center gap-1.5 px-2 py-1 rounded hover:bg-white/10 transition-colors" {isHtmlPreviewable && (
title="Copy code" <button
> onClick={() => setPreviewOpen(true)}
{copied ? ( className="inline-flex items-center gap-1.5 px-2 py-1 rounded hover:bg-white/10 transition-colors text-blue-400 hover:text-blue-300"
<> title="预览 HTML"
<Check size={14} className="text-green-400" /> >
<span className="text-green-400">Copied!</span> <Eye size={14} />
</> <span></span>
) : ( </button>
<>
<Copy size={14} />
<span>Copy</span>
</>
)} )}
</button> {/* 复制按钮 */}
<button
onClick={handleCopy}
className="inline-flex items-center gap-1.5 px-2 py-1 rounded hover:bg-white/10 transition-colors"
title="Copy code"
>
{copied ? (
<>
<Check size={14} className="text-green-400" />
<span className="text-green-400">Copied!</span>
</>
) : (
<>
<Copy size={14} />
<span>Copy</span>
</>
)}
</button>
</div>
</div> </div>
{/* 代码区域 */} {/* 代码区域 */}
@ -130,6 +150,16 @@ export function CodeBlock({
/> />
</pre> </pre>
</div> </div>
{/* HTML 预览模态框 */}
{isHtmlPreviewable && (
<HtmlPreviewModal
htmlCode={code}
isOpen={previewOpen}
onClose={() => setPreviewOpen(false)}
language={language}
/>
)}
</div> </div>
); );
} }