# MarkLite v0.3.12 — 架构设计文档

## 1. 项目概述

MarkLite 是一款轻量级的 Windows 本地 Markdown 编辑器桌面应用程序。基于 Electron + React + TypeScript 构建，采用 Milkdown v7 WYSIWYG 编辑器 + 源码编辑模式、Zustand 状态管理、IndexedDB 持久化、unified/rehype Markdown 渲染管线。

### 1.1 核心原则

1. **类型安全** — 全量 TypeScript，所有 IPC 通信、状态、接口均有类型定义
2. **模块化** — 源文件按职责分层：主进程 / 预加载 / 渲染进程（组件 / stores / hooks / lib / db / types）
3. **安全隔离** — contextIsolation + nodeIntegration:false + CSP + rehype-sanitize
4. **可测试性** — 业务逻辑（lib/）与 UI（components/）解耦

## 2. 技术架构

### 2.1 技术栈

| 组件 | 技术 | 版本 | 说明 |
|------|------|------|------|
| 桌面框架 | Electron | v28 | 跨平台桌面应用框架 |
| 前端框架 | React | v18 | 函数组件 + Hooks |
| 类型系统 | TypeScript | v5.6 | 全量类型安全 |
| 编辑器 | Milkdown | v7 | WYSIWYG 编辑器 + 源码编辑模式 |
| 状态管理 | Zustand | v5 | 轻量级状态管理 |
| 持久化 | Dexie.js (IndexedDB) | v4 | 标签页状态 / 用户设置 / 最近文件 |
| Markdown 解析 | unified / remark / rehype | v11 | 插件化渲染管线 |
| 代码高亮 | rehype-highlight | v7 | 基于 highlight.js |
| 构建工具 | electron-vite | v3 | Electron + Vite，HMR 热更新 |
| 打包工具 | electron-builder | v25 | Windows NSIS 安装包 |
| 样式 | CSS Variables | — | 主题驱动，亮色/暗色 |

### 2.2 进程架构

```
┌──────────────────────────────────────────────────────────────┐
│               Main Process (src/main/)  5 文件               │
│  index.ts           入口：窗口创建、app 生命周期、单实例锁    │
│  ipc-handlers.ts    所有 ipcMain.handle 注册                  │
│  file-system.ts     文件读写、目录树构建、BOM 剥离            │
│  file-watcher.ts    fs.watch 封装（单文件 + 目录监听）        │
│  window-manager.ts  窗口创建、关闭拦截、单实例锁              │
└───────────────────────┬──────────────────────────────────────┘
                        │ contextBridge (安全隔离)
┌───────────────────────▼──────────────────────────────────────┐
│            Preload Script (src/preload/)  1 文件              │
│  index.ts           contextBridge 类型安全暴露                │
│  electronAPI        18 个方法/事件的类型安全接口               │
└───────────────────────┬──────────────────────────────────────┘
                        │
┌───────────────────────▼──────────────────────────────────────┐
│       Renderer Process (src/renderer/)  React 18             │
│                                                               │
│  components/ (23)  Toolbar · TabBar · Editor · EditorToolbar  │
│                    SourceEditor · Preview · Sidebar · FileTree│
│                    OutlinePanel · StatusBar · WelcomeScreen   │
│                    Toast · ConfirmDialog · ModifiedBanner     │
│                    SearchReplace · DropOverlay · ErrorBoundary│
│                    AboutDialog · LoadingSpinner · Icons       │
│                                                               │
│  stores/ (3)        tabStore · editorStore · sidebarStore     │
│  hooks/ (19)        useTheme · useSettings · useSettingsInit │
│                     useKeyboard · useDragDrop · useFileWatch  │
│                     useAutoSave · useIpcListeners ...          │
│  lib/ (4)           markdown · fileUtils · errorHandler       │
│                     constants                                  │
│  db/ (4)            schema · tabRepository · settingsRepo     │
│                     recentFilesRepository                     │
│  types/ (5)         tab · file · settings · ipc · index       │
│  styles/ (3)        variables · global · markdown-body        │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│               Shared (src/shared/)  3 文件                    │
│  ipc-channels.ts    IPC 通道名常量                            │
│  types.ts           共享类型定义                              │
│  constants.ts       共享常量 (版本号、文件大小限制等)          │
└──────────────────────────────────────────────────────────────┘
```

### 2.3 安全模型

| 层级 | 措施 | 说明 |
|------|------|------|
| Electron | `contextIsolation: true` | 渲染进程与主进程隔离 |
| Electron | `nodeIntegration: false` | 渲染进程无法访问 Node.js API |
| IPC | `contextBridge.exposeInMainWorld` | 仅暴露 22 个类型安全方法/事件 |
| CSP | `default-src 'self'; script-src 'self'` | 阻断内联脚本、外部资源 |
| HTML | `rehype-sanitize` | 渲染 Markdown 时过滤危险标签/属性 |
| 链接 | 协议白名单 | 仅允许 `http:` / `https:` / `#` 锚点 |
| 路径 | `validatePath()` | 防止路径遍历攻击 |

## 3. 状态管理架构

### 3.1 Zustand Stores

```
┌─────────────────────────────────────────────────────────┐
│                    App.tsx (根组件)                       │
├─────────┬──────────┬──────────┬──────────┬──────────────┤
│Toolbar  │ TabBar   │ Sidebar  │ Editor   │ Preview      │
│         │          │          │ (CM6)    │              │
├─────────┴──────────┴──────────┴──────────┴──────────────┤
│                    Zustand Stores                        │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐                │
│  │ tabStore │ │editorStore│ │sidebarStore│               │
│  │ - tabs   │ │- viewMode│ │- tree     │                │
│  │- activeId│ │- darkMode│ │- expanded │                │
│  │ - mru    │ │          │ │- rootPath │                │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘                │
│       │            │            │                        │
│  ┌────▼────────────▼────────────▼────────────────────┐  │
│  │              IndexedDB (Dexie.js)                  │  │
│  │  tabSnapshots │ settings │ recentFiles │ activeTab │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
```

### 3.2 tabStore — 标签页状态

```typescript
interface TabState {
  tabs: Tab[]                    // 所有标签页
  activeTabId: string | null     // 当前活动标签 ID
  mruStack: string[]             // MRU 标签栈（Ctrl+Tab 切换）

  createTab(filePath?, content?) // 创建标签（同文件不重复打开）
  closeTab(tabId)                // 关闭标签（自动切换到相邻标签）
  switchToTab(tabId)             // 切换标签
  updateTabContent(tabId, content) // 更新内容（标记修改）
  setModified(tabId, modified)   // 设置修改状态
  getActiveTab()                 // 获取当前标签
  updateTabScroll(tabId, scroll) // 更新滚动/光标位置
  saveToDB()                     // 保存到 IndexedDB
  loadFromDB()                   // 从 IndexedDB 加载
}
```

### 3.3 editorStore — 编辑器状态

```typescript
interface EditorState {
  viewMode: 'editor' | 'preview'  // 视图模式
  darkMode: boolean                // 暗色主题
}
```

### 3.4 sidebarStore — 侧边栏状态

```typescript
interface SidebarState {
  isVisible: boolean       // 是否显示
  rootPath: string | null  // 当前打开的文件夹路径
  tree: FileNode[]         // 目录树数据
  expandedDirs: Set<string> // 已展开的目录集合
  sidebarWidth: number     // 侧边栏宽度 (180~500)
}
```

## 4. 数据持久化 — IndexedDB

通过 Dexie.js 封装 IndexedDB：

### 4.1 数据库 Schema

```typescript
const db = new Dexie('MarkLite')

db.version(1).stores({
  tabSnapshots: 'id, filePath, updatedAt',  // 标签页快照
  settings: 'id',                            // 用户设置
  recentFiles: '++id, filePath, lastOpened', // 最近打开文件
  activeTab: 'id'                            // 当前活动标签
})
```

### 4.2 数据模型

| Store | 字段 | 说明 |
|-------|------|------|
| `tabSnapshots` | id, filePath, content, scrollTop, selectionStart, selectionEnd, isModified, updatedAt | 标签页状态快照 |
| `settings` | id, darkMode, viewMode, sidebarCollapsed, sidebarWidth | 用户偏好设置 |
| `recentFiles` | ++id, filePath, lastOpened | 最近打开文件列表 |
| `activeTab` | id, activeTabId | 当前活动标签 ID |

## 5. IPC 通信设计

### 5.1 渲染进程 → 主进程（invoke）

| 通道 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `dialog:openFile` | 无 | `OpenFileResponse` | 打开文件对话框 |
| `file:read` | `filePath` | `ReadFileResult` | 读取文件内容 |
| `file:save` | `{ filePath, content }` | `SaveFileResult` | 保存文件 |
| `file:saveAs` | `{ content }` | `SaveFileResult` | 另存为 |
| `file:getCurrentPath` | 无 | `string \| null` | 获取当前文件路径 |
| `file:stats` | `filePath` | `FileStatsResult` | 获取文件元信息 |
| `file:reload` | 无 | `ReloadFileResult` | 重新加载当前文件 |
| `tab:switched` | `filePath \| null` | `void` | 通知主进程切换活动文件 |
| `window:forceClose` | 无 | `void` | 强制关闭窗口 |
| `window:cancelClose` | 无 | `void` | 取消关闭 |
| `dir:readTree` | `dirPath` | `ReadDirTreeResult` | 递归读取目录树 |
| `dir:openDialog` | 无 | `string \| null` | 打开文件夹选择对话框 |
| `dir:watch` | `dirPath` | `void` | 监听目录变化 |
| `dir:unwatch` | 无 | `void` | 停止监听目录变化 |

### 5.2 主进程 → 渲染进程（send）

| 通道 | 数据 | 说明 |
|------|------|------|
| `file:openInTab` | `{ filePath, content }` | 在新标签中打开文件 |
| `file:externallyModified` | `filePath` | 文件被外部修改 |
| `window:confirmClose` | 无 | 请求确认关闭 |
| `sidebar:dirChanged` | 无 | 目录结构变化 |

## 6. 编辑器架构 — Milkdown v7 (WYSIWYG) + SourceEditor (textarea)

### 6.1 双编辑模式

- **编辑模式 (Milkdown)**：基于 ProseMirror 的 WYSIWYG Markdown 编辑器，支持格式化工具栏（粗体/斜体/删除线/标题/列表/引用/代码块/链接/图片/分割线）、搜索替换面板（含正则支持）、自动配对括号/引号、undo/redo
- **源码模式 (SourceEditor)**：原生 textarea 控制 Markdown 原文，支持 Tab 缩进、Ctrl+B/I 快捷键

两种模式共享同一 tabStore 数据源，可随时切换。

### 6.2 插件体系

| 插件 | 说明 |
|------|------|
| commonmark | 基础 Markdown 语法 |
| gfm | GitHub Flavored Markdown（表格/任务列表/删除线等） |
| history | undo/redo |
| listener | 内容变更监听 |
| indent | Tab 缩进 |
| trailing | 尾随换行 |
| clipboard | 剪贴板增强 |
| searchPlugin (自研) | 搜索高亮装饰 |
| autoPairPlugin (自研) | 自动配对括号/引号 |

### 6.3 滚动与选区持久化

切换标签时自动保存/恢复：
- 滚动位置 (`scrollTop`)
- 光标选区 (`selectionStart`, `selectionEnd`)

## 7. Markdown 渲染管线

```
Markdown 文本
    │
    ▼
remark-parse          解析为 MDAST
    │
    ▼
remark-gfm            扩展 GFM 语法
    │
    ▼
remark-rehype          转换为 HAST
    │
    ▼
rehype-raw             解析内联 HTML
    │
    ▼
rehype-sanitize         安全过滤
    │
    ▼
rehype-fixImages        相对路径图片转 file:// URL
    │
    ▼
rehype-highlight        代码语法高亮
    │
    ▼
rehype-stringify        序列化为 HTML
    │
    ▼
dangerouslySetInnerHTML  渲染到 DOM
```

## 8. UI 设计

### 8.1 色彩方案

#### 亮色主题

| 角色 | CSS 变量 | 色值 |
|------|----------|------|
| 主色调 | `--primary` | `#1a73e8` |
| 背景色 | `--bg` | `#ffffff` |
| 次级背景 | `--bg-secondary` | `#f8f9fa` |
| 文字色 | `--text` | `#333333` |
| 边框色 | `--border` | `#e1e4e8` |

#### 暗色主题

| 角色 | CSS 变量 | 色值 |
|------|----------|------|
| 主色调 | `--primary` | `#8ab4f8` |
| 背景色 | `--bg` | `#1e1e1e` |
| 次级背景 | `--bg-secondary` | `#252526` |
| 文字色 | `--text` | `#d4d4d4` |
| 边框色 | `--border` | `#3e3e3e` |

### 8.2 布局

```
┌──────────────────────────────────────────────────────────────────────────┐
│  MarkLite - filename.md                                         ─ □ ✕  │
├──────────────────────────────────────────────────────────────────────────┤
│  📁 打开 │ 💾 保存 │ ✏️ 编辑 │ 👁 预览 │            🌙 🔗 ℹ️       │
├──────────────────────────────────────────────────────────────────────────┤
│  [file1.md] [file2.md] [未命名]  [+]                                    │
├──────────┬─────────────────────────────────────────────────────────────┤
│ 资源管理器 │                                                              │
│ ▼ project │  1  # Title             │  Title                           │
│   📁 src  │  2                     │  ───────                          │
│   📄 file1│  3  content...         │  content...                       │
│   📄 file2│                         │                                   │
├──────────┴─────────────────────────────────────────────────────────────┤
│  filename.md  │  UTF-8  │  Markdown                                    │
└────────────────────────────────────────────────────────────────────────┘
```

## 9. 构建与发布

### 9.1 开发模式

```bash
npm run dev          # electron-vite dev（HMR 热更新）
```

### 9.2 生产构建

```bash
npm run build        # electron-vite build + electron-builder --win
npm run build:portable  # 便携版（免安装）
```

### 9.3 打包配置

- 输出格式：NSIS 安装包（.exe）
- 目标平台：Windows x64
- 应用图标：assets/icon.ico
- 文件关联：`.md` / `.markdown` / `.txt`
- 支持自定义安装目录、桌面/开始菜单快捷方式

## 10. 依赖清单

### 运行时依赖

| 包名 | 版本 | 用途 |
|------|------|------|
| react / react-dom | ^18.3 | UI 框架 |
| zustand | ^5.0 | 状态管理 |
| dexie | ^4.0 | IndexedDB 封装 |
| nanoid | ^5.0 | 唯一 ID 生成 |
| @codemirror/* | ^6.x | 代码编辑器 |
| unified / remark / rehype | ^11.0 | Markdown 渲染管线 |
| rehype-highlight | ^7.0 | 代码语法高亮 |

### 开发依赖

| 包名 | 版本 | 用途 |
|------|------|------|
| electron | ^28.0 | 桌面框架 |
| electron-builder | ^25.0 | 打包工具 |
| electron-vite | ^3.0 | 构建工具 |
| typescript | ^5.6 | 类型系统 |
| eslint | ^9.0 | 代码检查 |
