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:
thzxx
2026-05-27 23:21:31 +08:00
parent 1cc4206b32
commit 23304dca1d
5 changed files with 201 additions and 14 deletions
@@ -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>