# MarkLite v0.3.11 — 架构设计文档 ## 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 // 已展开的目录集合 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 | 代码检查 |