17 KiB
17 KiB
MarkLite v0.3.11 — 架构设计文档
1. 项目概述
MarkLite 是一款轻量级的 Windows 本地 Markdown 编辑器桌面应用程序。基于 Electron + React + TypeScript 构建,采用 Milkdown v7 WYSIWYG 编辑器 + 源码编辑模式、Zustand 状态管理、IndexedDB 持久化、unified/rehype Markdown 渲染管线。
1.1 核心原则
- 类型安全 — 全量 TypeScript,所有 IPC 通信、状态、接口均有类型定义
- 模块化 — 源文件按职责分层:主进程 / 预加载 / 渲染进程(组件 / stores / hooks / lib / db / types)
- 安全隔离 — contextIsolation + nodeIntegration:false + CSP + rehype-sanitize
- 可测试性 — 业务逻辑(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 — 标签页状态
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 — 编辑器状态
interface EditorState {
viewMode: 'editor' | 'preview' // 视图模式
darkMode: boolean // 暗色主题
}
3.4 sidebarStore — 侧边栏状态
interface SidebarState {
isVisible: boolean // 是否显示
rootPath: string | null // 当前打开的文件夹路径
tree: FileNode[] // 目录树数据
expandedDirs: Set<string> // 已展开的目录集合
sidebarWidth: number // 侧边栏宽度 (180~500)
}
4. 数据持久化 — IndexedDB
通过 Dexie.js 封装 IndexedDB:
4.1 数据库 Schema
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 开发模式
npm run dev # electron-vite dev(HMR 热更新)
9.2 生产构建
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 | 代码检查 |