refactor: 重新设计Popup页面,简洁现代美观

- 采用Clean Modernity设计哲学
- 优化视觉层次和间距
- 使用indigo/violet渐变主题色
- 简化模式切换按钮
- 美化复选框和下拉选择框
- 添加平滑动画效果
- 自定义滚动条样式
- 更新docs/popup-design-philosophy.md

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ddshi 2026-01-23 15:47:38 +08:00
parent f83795eeb2
commit 6045cbe6ad
6 changed files with 282 additions and 128 deletions

View File

@ -0,0 +1,15 @@
# Design Philosophy: Clean Modernity
## 视觉哲学宣言
**简约而不简单**。现代设计的本质不是添加,而是减法。每一个元素的存在都应有其意义,每一处空白都是设计的呼吸。去除一切不必要的装饰,让功能本身成为美学。
**清晰的视觉层次**。用户视线应自然流动,从最重要的元素流向次要元素。通过尺寸、颜色、间距和对比度建立层次,无需多余标注。信息自明,用户无需思考。
**克制的色彩运用**。以单色或双色调为主,点缀恰到好处的强调色。色彩用于引导注意力和表达状态,而非装饰。柔和的灰度背景承载内容,明亮的强调色突出行动。
**微妙的圆角与阴影**。圆润的边角传递友好与亲和,恰到好处的阴影创造深度与层次。避免生硬的直角和夸张的投影,追求自然的光影效果。
**精致的细节把控**。即使在16像素的图标中每一个像素都经过考量。按钮的点击区域、字体的行高、元素的对齐——这些看不见的细节决定了整体的品质感。博物馆级的工艺标准意味着卓越。
**留白的艺术**。空间本身就是一种元素。足够的留白让内容得以呼吸,让界面看起来从容不迫。拥挤的界面传达焦虑,宽敞的界面传递自信。

View File

@ -11,3 +11,55 @@ body {
padding: 0; padding: 0;
width: 320px; width: 320px;
} }
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-fade-in {
animation: fadeIn 0.2s ease-out;
}
.animate-scale-in {
animation: scaleIn 0.2s ease-out;
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
.dark ::-webkit-scrollbar-thumb {
background: #4b5563;
}
.dark ::-webkit-scrollbar-thumb:hover {
background: #6b7280;
}

View File

@ -68,7 +68,6 @@ export function Popup() {
}); });
await copyToClipboard(markdown); await copyToClipboard(markdown);
showStatus('success', '已复制到剪贴板!'); showStatus('success', '已复制到剪贴板!');
// 1秒后关闭弹窗
setTimeout(() => { setTimeout(() => {
window.close(); window.close();
}, 1000); }, 1000);
@ -89,8 +88,6 @@ export function Popup() {
const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' }); const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
// 使用chrome.downloads API
const filename = `${extractedContent.title.replace(/[<>:"/\\|?*]/g, '_').substring(0, 100)}.md`; const filename = `${extractedContent.title.replace(/[<>:"/\\|?*]/g, '_').substring(0, 100)}.md`;
await chrome.downloads.download({ await chrome.downloads.download({
url: url, url: url,
@ -100,7 +97,6 @@ export function Popup() {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
showStatus('success', '开始下载...'); showStatus('success', '开始下载...');
// 1秒后关闭弹窗
setTimeout(() => { setTimeout(() => {
window.close(); window.close();
}, 1000); }, 1000);
@ -141,51 +137,74 @@ export function Popup() {
if (settingsLoading) { if (settingsLoading) {
return ( return (
<div className="w-80 p-4 flex items-center justify-center"> <div className="w-80 h-64 flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div> <div className="animate-spin rounded-full h-8 w-8 border-2 border-indigo-500 border-t-transparent"></div>
</div> </div>
); );
} }
return ( return (
<div className="w-80 p-4 bg-white dark:bg-gray-900"> <div className="w-80 bg-gray-50 dark:bg-gray-900">
{/* 标题 */} {/* 头部 */}
<div className="flex items-center gap-2 mb-4"> <div className="px-5 py-5 bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
<svg className="w-8 h-8 text-green-500" viewBox="0 0 24 24" fill="currentColor"> <div className="flex items-center gap-3">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 2l5 5h-5V4zM6 20V4h6v6h6v10H6z"/> <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center shadow-lg shadow-indigo-500/30">
<path d="M8 12h8v2H8zm0 4h8v2H8z"/> <svg className="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
</svg> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
<h1 className="text-xl font-bold text-gray-900 dark:text-white">ReadMD</h1> <path d="M14 2v6h6"/>
<path d="M8 13h8"/>
<path d="M8 17h8"/>
</svg>
</div>
<div>
<h1 className="text-lg font-semibold text-gray-900 dark:text-white">ReadMD</h1>
<p className="text-xs text-gray-500 dark:text-gray-400"> Markdown</p>
</div>
</div>
</div> </div>
{/* 提取按钮 */} {/* 主要内容区 */}
<ExtractButton <div className="p-5 space-y-4">
loading={status.type === 'loading'} {/* 状态消息 */}
onExtract={handleExtract} {status.message && (
mode={mode} <div className={`p-3 rounded-lg text-sm font-medium transition-all duration-300 ${
onModeChange={setMode} status.type === 'success' ? 'bg-emerald-50 text-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-400' :
/> status.type === 'error' ? 'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400' :
status.type === 'loading' ? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400' :
'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
}`}>
{status.message}
</div>
)}
{/* 状态消息 */} {/* 提取按钮 */}
{status.message && ( <ExtractButton
<div className={`mt-3 p-2 rounded-lg text-sm ${ loading={status.type === 'loading'}
status.type === 'success' ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' : onExtract={handleExtract}
status.type === 'error' ? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400' : mode={mode}
status.type === 'loading' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400' : onModeChange={setMode}
'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300' />
}`}>
{status.message}
</div>
)}
{/* 分隔线 */} {/* 选项面板 */}
<hr className="my-4 border-gray-200 dark:border-gray-700" /> <OptionsPanel
settings={settings}
onSettingsChange={saveSettings}
/>
</div>
{/* 选项面板 */} {/* 页脚 */}
<OptionsPanel <div className="px-5 py-4 bg-white dark:bg-gray-800 border-t border-gray-100 dark:border-gray-700">
settings={settings} <button
onSettingsChange={saveSettings} onClick={() => chrome.runtime.openOptionsPage()}
/> className="w-full py-2.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors flex items-center justify-center gap-2"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 2.0 1 83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</button>
</div>
{/* 预览弹窗 */} {/* 预览弹窗 */}
{showPreview && extractedContent && ( {showPreview && extractedContent && (
@ -196,20 +215,6 @@ export function Popup() {
onDownload={handleDownload} onDownload={handleDownload}
/> />
)} )}
{/* 页脚 */}
<div className="mt-4 text-center text-xs text-gray-500 dark:text-gray-400">
<a
href="#"
onClick={(e) => {
e.preventDefault();
chrome.runtime.openOptionsPage();
}}
className="hover:text-gray-700 dark:hover:text-gray-200"
>
</a>
</div>
</div> </div>
); );
} }

View File

@ -9,23 +9,23 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
return ( return (
<div className="space-y-3"> <div className="space-y-3">
{/* 提取模式切换 */} {/* 提取模式切换 */}
<div className="flex gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-lg"> <div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-800/50 rounded-lg">
<button <button
onClick={() => onModeChange('auto')} onClick={() => onModeChange('auto')}
className={`flex-1 py-2 px-3 text-sm rounded-md transition-colors ${ className={`flex-1 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
mode === 'auto' mode === 'auto'
? 'bg-blue-500 text-white' ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`} }`}
> >
</button> </button>
<button <button
onClick={() => onModeChange('selection')} onClick={() => onModeChange('selection')}
className={`flex-1 py-2 px-3 text-sm rounded-md transition-colors ${ className={`flex-1 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
mode === 'selection' mode === 'selection'
? 'bg-blue-500 text-white' ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`} }`}
> >
@ -36,10 +36,10 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
<button <button
onClick={onExtract} onClick={onExtract}
disabled={loading} disabled={loading}
className={`w-full py-3 px-4 rounded-lg font-medium text-white transition-all ${ className={`w-full py-3 px-4 rounded-xl font-medium text-white transition-all duration-200 ${
loading loading
? 'bg-gray-400 cursor-not-allowed' ? 'bg-gray-300 dark:bg-gray-600 cursor-not-allowed'
: 'bg-green-500 hover:bg-green-600 active:scale-95' : 'bg-gradient-to-r from-indigo-500 to-violet-600 hover:from-indigo-600 hover:to-violet-700 shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/40 active:scale-[0.98]'
}`} }`}
> >
{loading ? ( {loading ? (
@ -51,7 +51,12 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
... ...
</span> </span>
) : ( ) : (
'提取并导出Markdown' <span className="flex items-center justify-center gap-2">
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Markdown
</span>
)} )}
</button> </button>
</div> </div>

View File

@ -7,52 +7,104 @@ interface OptionsPanelProps {
export function OptionsPanel({ settings, onSettingsChange }: OptionsPanelProps) { export function OptionsPanel({ settings, onSettingsChange }: OptionsPanelProps) {
return ( return (
<div className="space-y-3"> <div className="space-y-4">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300"></h4> <h4 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider"></h4>
<label className="flex items-center gap-2 cursor-pointer"> <div className="space-y-3">
<input {/* 包含标题 */}
type="checkbox" <label className="flex items-center gap-3 cursor-pointer group">
checked={settings.includeTitle} <div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
onChange={(e) => onSettingsChange({ includeTitle: e.target.checked })} settings.includeTitle
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500" ? 'border-indigo-500 bg-indigo-500'
/> : 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
<span className="text-sm text-gray-700 dark:text-gray-300"></span> }`}>
</label> {settings.includeTitle && (
<svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<path d="M5 13l4 4L19 7"/>
</svg>
)}
</div>
<input
type="checkbox"
checked={settings.includeTitle}
onChange={(e) => onSettingsChange({ includeTitle: e.target.checked })}
className="hidden"
/>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors">
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer"> {/* 包含原文链接 */}
<input <label className="flex items-center gap-3 cursor-pointer group">
type="checkbox" <div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
checked={settings.includeUrl} settings.includeUrl
onChange={(e) => onSettingsChange({ includeUrl: e.target.checked })} ? 'border-indigo-500 bg-indigo-500'
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500" : 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
/> }`}>
<span className="text-sm text-gray-700 dark:text-gray-300"></span> {settings.includeUrl && (
</label> <svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<path d="M5 13l4 4L19 7"/>
</svg>
)}
</div>
<input
type="checkbox"
checked={settings.includeUrl}
onChange={(e) => onSettingsChange({ includeUrl: e.target.checked })}
className="hidden"
/>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors">
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer"> {/* 简化模式 */}
<input <label className="flex items-center gap-3 cursor-pointer group">
type="checkbox" <div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
checked={settings.simplifyMode} settings.simplifyMode
onChange={(e) => onSettingsChange({ simplifyMode: e.target.checked })} ? 'border-indigo-500 bg-indigo-500'
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500" : 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
/> }`}>
<span className="text-sm text-gray-700 dark:text-gray-300">广</span> {settings.simplifyMode && (
</label> <svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<path d="M5 13l4 4L19 7"/>
</svg>
)}
</div>
<input
type="checkbox"
checked={settings.simplifyMode}
onChange={(e) => onSettingsChange({ simplifyMode: e.target.checked })}
className="hidden"
/>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors">
</span>
</label>
</div>
{/* 图片处理 */}
<div className="pt-2"> <div className="pt-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> <label className="block text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
</label> </label>
<select <div className="relative">
value={settings.imageMode} <select
onChange={(e) => onSettingsChange({ imageMode: e.target.value as UserSettings['imageMode'] })} value={settings.imageMode}
className="w-full py-2 px-3 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" onChange={(e) => onSettingsChange({ imageMode: e.target.value as UserSettings['imageMode'] })}
> className="w-full py-2.5 px-3 text-sm appearance-none bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all duration-200 cursor-pointer"
<option value="url">URL链接</option> >
<option value="base64">Base64</option> <option value="url"> URL </option>
<option value="download"></option> <option value="base64"> Base64</option>
</select> <option value="download"></option>
</select>
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-gray-400">
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 9l-7 7-7-7"/>
</svg>
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@ -20,72 +20,97 @@ export function PreviewModal({ content, onClose, onCopy, onDownload }: PreviewMo
}); });
return ( return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4 animate-fade-in">
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-lg mx-4 max-h-[80vh] flex flex-col"> <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-lg overflow-hidden animate-scale-in">
{/* 标题栏 */} {/* 标题栏 */}
<div className="flex items-center justify-between p-4 border-b dark:border-gray-700"> <div className="flex items-center justify-between px-5 py-4 border-b border-gray-100 dark:border-gray-700">
<h3 className="font-semibold text-gray-900 dark:text-white"></h3> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center">
<svg className="w-4 h-4 text-indigo-600 dark:text-indigo-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
</div>
<div>
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<p className="text-xs text-gray-500 dark:text-gray-400 truncate max-w-[200px]">{content.title}</p>
</div>
</div>
<button <button
onClick={onClose} onClick={onClose}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" className="w-8 h-8 rounded-lg text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all flex items-center justify-center"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path d="M6 18L18 6M6 6l12 12"/>
</svg> </svg>
</button> </button>
</div> </div>
{/* 内容预览 */} {/* 内容预览 */}
<div className="flex-1 overflow-auto p-4"> <div className="p-5 max-h-72 overflow-auto">
<div className="text-sm text-gray-600 dark:text-gray-400 mb-2">{content.title}</div>
{/* 切换按钮 */} {/* 切换按钮 */}
<div className="flex gap-2 mb-3"> <div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-700/50 rounded-lg mb-4">
<button <button
onClick={() => setShowRaw(false)} onClick={() => setShowRaw(false)}
className={`px-3 py-1 text-xs rounded ${!showRaw ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700'}`} className={`flex-1 py-2 text-xs font-medium rounded-md transition-all duration-200 ${
!showRaw
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
> >
</button> </button>
<button <button
onClick={() => setShowRaw(true)} onClick={() => setShowRaw(true)}
className={`px-3 py-1 text-xs rounded ${showRaw ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700'}`} className={`flex-1 py-2 text-xs font-medium rounded-md transition-all duration-200 ${
showRaw
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
> >
Markdown
</button> </button>
</div> </div>
{showRaw ? ( {showRaw ? (
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-3 rounded overflow-auto max-h-64 whitespace-pre-wrap"> <pre className="text-xs bg-gray-50 dark:bg-gray-900 p-4 rounded-xl overflow-auto max-h-56 whitespace-pre-wrap font-mono text-gray-700 dark:text-gray-300">
{markdown} {markdown}
</pre> </pre>
) : ( ) : (
<div <div
className="prose prose-sm max-w-none dark:prose-invert overflow-auto max-h-64" className="prose prose-sm max-w-none dark:prose-invert overflow-auto max-h-56 text-gray-600 dark:text-gray-400"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: content.markdown __html: content.markdown
.replace(/^# .+$/m, '') // 移除标题 .replace(/^# .+$/m, '')
.replace(/> .+$/gm, '') // 移除引用 .replace(/> .+$/gm, '')
.replace(/```[\s\S]*?```/g, '[代码块]') // 简化代码块 .replace(/```[\s\S]*?```/g, '[代码块]')
.replace(/\n/g, '<br/>') // 换行 .replace(/\n/g, '<br/>')
}} }}
/> />
)} )}
</div> </div>
{/* 操作按钮 */} {/* 操作按钮 */}
<div className="p-4 border-t dark:border-gray-700 flex gap-2"> <div className="px-5 py-4 bg-gray-50 dark:bg-gray-800/50 border-t border-gray-100 dark:border-gray-700 flex gap-3">
<button <button
onClick={onCopy} onClick={onCopy}
className="flex-1 py-2 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg text-sm font-medium transition-colors" className="flex-1 py-2.5 px-4 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 rounded-xl text-sm font-medium border border-gray-200 dark:border-gray-600 transition-all duration-200 flex items-center justify-center gap-2"
> >
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
</button> </button>
<button <button
onClick={onDownload} onClick={onDownload}
className="flex-1 py-2 px-4 bg-green-500 hover:bg-green-600 text-white rounded-lg text-sm font-medium transition-colors" className="flex-1 py-2.5 px-4 bg-gradient-to-r from-indigo-500 to-violet-600 hover:from-indigo-600 hover:to-violet-700 text-white rounded-xl text-sm font-medium shadow-lg shadow-indigo-500/25 transition-all duration-200 flex items-center justify-center gap-2"
> >
MD文件 <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
</button> </button>
</div> </div>
</div> </div>