refactor: 重新设计Popup页面,简洁现代美观
- 采用Clean Modernity设计哲学 - 优化视觉层次和间距 - 使用indigo/violet渐变主题色 - 简化模式切换按钮 - 美化复选框和下拉选择框 - 添加平滑动画效果 - 自定义滚动条样式 - 更新docs/popup-design-philosophy.md Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f83795eeb2
commit
6045cbe6ad
15
docs/popup-design-philosophy.md
Normal file
15
docs/popup-design-philosophy.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Design Philosophy: Clean Modernity
|
||||
|
||||
## 视觉哲学宣言
|
||||
|
||||
**简约而不简单**。现代设计的本质不是添加,而是减法。每一个元素的存在都应有其意义,每一处空白都是设计的呼吸。去除一切不必要的装饰,让功能本身成为美学。
|
||||
|
||||
**清晰的视觉层次**。用户视线应自然流动,从最重要的元素流向次要元素。通过尺寸、颜色、间距和对比度建立层次,无需多余标注。信息自明,用户无需思考。
|
||||
|
||||
**克制的色彩运用**。以单色或双色调为主,点缀恰到好处的强调色。色彩用于引导注意力和表达状态,而非装饰。柔和的灰度背景承载内容,明亮的强调色突出行动。
|
||||
|
||||
**微妙的圆角与阴影**。圆润的边角传递友好与亲和,恰到好处的阴影创造深度与层次。避免生硬的直角和夸张的投影,追求自然的光影效果。
|
||||
|
||||
**精致的细节把控**。即使在16像素的图标中,每一个像素都经过考量。按钮的点击区域、字体的行高、元素的对齐——这些看不见的细节决定了整体的品质感。博物馆级的工艺标准意味着卓越。
|
||||
|
||||
**留白的艺术**。空间本身就是一种元素。足够的留白让内容得以呼吸,让界面看起来从容不迫。拥挤的界面传达焦虑,宽敞的界面传递自信。
|
||||
@ -11,3 +11,55 @@ body {
|
||||
padding: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -68,7 +68,6 @@ export function Popup() {
|
||||
});
|
||||
await copyToClipboard(markdown);
|
||||
showStatus('success', '已复制到剪贴板!');
|
||||
// 1秒后关闭弹窗
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 1000);
|
||||
@ -89,8 +88,6 @@ export function Popup() {
|
||||
|
||||
const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// 使用chrome.downloads API
|
||||
const filename = `${extractedContent.title.replace(/[<>:"/\\|?*]/g, '_').substring(0, 100)}.md`;
|
||||
await chrome.downloads.download({
|
||||
url: url,
|
||||
@ -100,7 +97,6 @@ export function Popup() {
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
showStatus('success', '开始下载...');
|
||||
// 1秒后关闭弹窗
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 1000);
|
||||
@ -141,22 +137,45 @@ export function Popup() {
|
||||
|
||||
if (settingsLoading) {
|
||||
return (
|
||||
<div className="w-80 p-4 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
<div className="w-80 h-64 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-indigo-500 border-t-transparent"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-80 p-4 bg-white dark:bg-gray-900">
|
||||
{/* 标题 */}
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<svg className="w-8 h-8 text-green-500" viewBox="0 0 24 24" fill="currentColor">
|
||||
<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"/>
|
||||
<path d="M8 12h8v2H8zm0 4h8v2H8z"/>
|
||||
<div className="w-80 bg-gray-50 dark:bg-gray-900">
|
||||
{/* 头部 */}
|
||||
<div className="px-5 py-5 bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center gap-3">
|
||||
<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">
|
||||
<svg className="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
|
||||
<path d="M14 2v6h6"/>
|
||||
<path d="M8 13h8"/>
|
||||
<path d="M8 17h8"/>
|
||||
</svg>
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">ReadMD</h1>
|
||||
</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 className="p-5 space-y-4">
|
||||
{/* 状态消息 */}
|
||||
{status.message && (
|
||||
<div className={`p-3 rounded-lg text-sm font-medium transition-all duration-300 ${
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* 提取按钮 */}
|
||||
<ExtractButton
|
||||
@ -166,26 +185,26 @@ export function Popup() {
|
||||
onModeChange={setMode}
|
||||
/>
|
||||
|
||||
{/* 状态消息 */}
|
||||
{status.message && (
|
||||
<div className={`mt-3 p-2 rounded-lg text-sm ${
|
||||
status.type === 'success' ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' :
|
||||
status.type === 'error' ? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400' :
|
||||
status.type === 'loading' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400' :
|
||||
'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>
|
||||
|
||||
{/* 页脚 */}
|
||||
<div className="px-5 py-4 bg-white dark:bg-gray-800 border-t border-gray-100 dark:border-gray-700">
|
||||
<button
|
||||
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 && (
|
||||
@ -196,20 +215,6 @@ export function Popup() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,23 +9,23 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
|
||||
return (
|
||||
<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
|
||||
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'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
|
||||
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
自动提取
|
||||
</button>
|
||||
<button
|
||||
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'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
|
||||
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
|
||||
: '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
|
||||
onClick={onExtract}
|
||||
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
|
||||
? 'bg-gray-400 cursor-not-allowed'
|
||||
: 'bg-green-500 hover:bg-green-600 active:scale-95'
|
||||
? 'bg-gray-300 dark:bg-gray-600 cursor-not-allowed'
|
||||
: '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 ? (
|
||||
@ -51,7 +51,12 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
|
||||
提取中...
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@ -7,52 +7,104 @@ interface OptionsPanelProps {
|
||||
|
||||
export function OptionsPanel({ settings, onSettingsChange }: OptionsPanelProps) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">导出选项</h4>
|
||||
<div className="space-y-4">
|
||||
<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">
|
||||
{/* 包含标题 */}
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
|
||||
settings.includeTitle
|
||||
? 'border-indigo-500 bg-indigo-500'
|
||||
: 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
|
||||
}`}>
|
||||
{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="w-4 h-4 text-blue-500 rounded focus:ring-blue-500"
|
||||
className="hidden"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">包含标题</span>
|
||||
<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">
|
||||
{/* 包含原文链接 */}
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
|
||||
settings.includeUrl
|
||||
? 'border-indigo-500 bg-indigo-500'
|
||||
: 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
|
||||
}`}>
|
||||
{settings.includeUrl && (
|
||||
<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="w-4 h-4 text-blue-500 rounded focus:ring-blue-500"
|
||||
className="hidden"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">包含原文链接</span>
|
||||
<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">
|
||||
{/* 简化模式 */}
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
|
||||
settings.simplifyMode
|
||||
? 'border-indigo-500 bg-indigo-500'
|
||||
: 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
|
||||
}`}>
|
||||
{settings.simplifyMode && (
|
||||
<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="w-4 h-4 text-blue-500 rounded focus:ring-blue-500"
|
||||
className="hidden"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">简化模式(去除广告)</span>
|
||||
<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">
|
||||
<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>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={settings.imageMode}
|
||||
onChange={(e) => onSettingsChange({ imageMode: e.target.value as UserSettings['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"
|
||||
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="base64">转为 Base64</option>
|
||||
<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>
|
||||
);
|
||||
|
||||
@ -20,72 +20,97 @@ export function PreviewModal({ content, onClose, onCopy, onDownload }: PreviewMo
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<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="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-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">
|
||||
<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
|
||||
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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容预览 */}
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 mb-2">{content.title}</div>
|
||||
|
||||
<div className="p-5 max-h-72 overflow-auto">
|
||||
{/* 切换按钮 */}
|
||||
<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
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
|
||||
{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}
|
||||
</pre>
|
||||
) : (
|
||||
<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={{
|
||||
__html: content.markdown
|
||||
.replace(/^# .+$/m, '') // 移除标题
|
||||
.replace(/> .+$/gm, '') // 移除引用
|
||||
.replace(/```[\s\S]*?```/g, '[代码块]') // 简化代码块
|
||||
.replace(/\n/g, '<br/>') // 换行
|
||||
.replace(/^# .+$/m, '')
|
||||
.replace(/> .+$/gm, '')
|
||||
.replace(/```[\s\S]*?```/g, '[代码块]')
|
||||
.replace(/\n/g, '<br/>')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</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
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user