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;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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,22 +137,45 @@ 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">
|
||||||
|
<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>
|
</svg>
|
||||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">ReadMD</h1>
|
|
||||||
</div>
|
</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
|
<ExtractButton
|
||||||
@ -166,26 +185,26 @@ export function Popup() {
|
|||||||
onModeChange={setMode}
|
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
|
<OptionsPanel
|
||||||
settings={settings}
|
settings={settings}
|
||||||
onSettingsChange={saveSettings}
|
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 && (
|
{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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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">
|
||||||
|
{/* 包含标题 */}
|
||||||
|
<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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={settings.includeTitle}
|
checked={settings.includeTitle}
|
||||||
onChange={(e) => onSettingsChange({ includeTitle: e.target.checked })}
|
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>
|
||||||
|
|
||||||
<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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={settings.includeUrl}
|
checked={settings.includeUrl}
|
||||||
onChange={(e) => onSettingsChange({ includeUrl: e.target.checked })}
|
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>
|
||||||
|
|
||||||
<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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={settings.simplifyMode}
|
checked={settings.simplifyMode}
|
||||||
onChange={(e) => onSettingsChange({ simplifyMode: e.target.checked })}
|
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>
|
</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>
|
||||||
|
<div className="relative">
|
||||||
<select
|
<select
|
||||||
value={settings.imageMode}
|
value={settings.imageMode}
|
||||||
onChange={(e) => onSettingsChange({ imageMode: e.target.value as UserSettings['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="url">保留 URL 链接</option>
|
||||||
<option value="base64">转为Base64</option>
|
<option value="base64">转为 Base64</option>
|
||||||
<option value="download">下载到本地</option>
|
<option value="download">下载到本地</option>
|
||||||
</select>
|
</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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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">
|
||||||
|
<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>
|
<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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user