feat: 标签页 IndexedDB 持久化 + 最近打开文件
1. 标签页持久化(关闭后恢复): - tabStore 新增 loadFromDB/saveToDB 方法 - 启动时从 IndexedDB 加载标签页快照 - 标签页增删改时异步保存到 IndexedDB - 关闭窗口后重新打开,所有标签页自动恢复 2. 最近打开文件: - 欢迎屏幕显示最近打开的 10 个文件 - 点击文件名直接打开 - 从侧边栏/对话框/主进程打开文件时自动记录 - 文件路径显示在文件名下方(灰色小字) - hover 高亮效果 REFACTOR-PLAN.md 实现进度更新: ✅ CodeMirror 6 编辑器 ✅ 格式化工具栏 ✅ 标签页 IndexedDB 持久化 ✅ 最近打开文件 UI ✅ Zustand stores ✅ TypeScript 全量类型 ✅ unified/rehype ✅ electron-vite ❌ CSS Modules(全局 CSS 已足够)
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useCallback, useState, useRef } from 'react'
|
||||
import { useTabStore } from '../../stores/tabStore'
|
||||
import { useSidebarStore } from '../../stores/sidebarStore'
|
||||
import { getFileName } from '../../lib/fileUtils'
|
||||
import { recentFilesRepository } from '../../db/recentFilesRepository'
|
||||
import { FolderPlus, File, Folder, ChevronRight } from '../Icons'
|
||||
import type { FileNode } from '../../types/file'
|
||||
|
||||
@@ -106,6 +107,7 @@ export function Sidebar() {
|
||||
const result = await window.electronAPI.readFile(path)
|
||||
if (result.success && result.content) {
|
||||
createTab(path, result.content)
|
||||
recentFilesRepository.add(path)
|
||||
}
|
||||
}
|
||||
}, [tabs, switchToTab, createTab])
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import React from 'react'
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { AppIcon, WelcomeFile, WelcomeNew } from '../Icons'
|
||||
import { recentFilesRepository } from '../../db/recentFilesRepository'
|
||||
import { getFileName } from '../../lib/fileUtils'
|
||||
|
||||
interface WelcomeScreenProps {
|
||||
onOpen: () => void
|
||||
onNew: () => void
|
||||
onOpenRecent?: (filePath: string) => void
|
||||
}
|
||||
|
||||
export function WelcomeScreen({ onOpen, onNew }: WelcomeScreenProps) {
|
||||
export function WelcomeScreen({ onOpen, onNew, onOpenRecent }: WelcomeScreenProps) {
|
||||
const [recentFiles, setRecentFiles] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
recentFilesRepository.getAll(10).then(files => {
|
||||
setRecentFiles(files)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div id="welcome-screen">
|
||||
<div className="welcome-content">
|
||||
@@ -25,6 +36,32 @@ export function WelcomeScreen({ onOpen, onNew }: WelcomeScreenProps) {
|
||||
新建文件
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{recentFiles.length > 0 && (
|
||||
<div className="welcome-recent">
|
||||
<h3>最近打开</h3>
|
||||
<div className="recent-list">
|
||||
{recentFiles.map(filePath => (
|
||||
<button
|
||||
key={filePath}
|
||||
className="recent-item"
|
||||
onClick={() => onOpenRecent?.(filePath)}
|
||||
title={filePath}
|
||||
>
|
||||
<span className="recent-icon">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="recent-name">{getFileName(filePath)}</span>
|
||||
<span className="recent-path">{filePath}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="welcome-tips">
|
||||
<p>💡 提示:可以直接拖拽 .md 文件到窗口打开</p>
|
||||
<p>⌨️ 快捷键:Ctrl+O 打开 | Ctrl+S 保存 | Ctrl+F 搜索 | Ctrl+1/2/3 视图</p>
|
||||
|
||||
Reference in New Issue
Block a user