# MarkLite v0.4.3 — 架构设计文档 ## 1. 项目概述 MarkLite 是一款轻量级的 Windows 本地 Markdown 编辑器桌面应用程序。基于 Electron + React + TypeScript 构建,采用 MetonaEditor v0.4.0 编辑器(三模式视图 + 内置解析器 + 插件系统)、Zustand 状态管理、MetonaSqlark(AriaEngine)持久化。 ### 1.1 核心原则 1. **类型安全** — 全量 TypeScript,所有 IPC 通信、状态、接口均有类型定义 2. **模块化** — 源文件按职责分层:主进程 / 预加载 / 渲染进程(组件 / stores / hooks / lib / db / types) 3. **安全隔离** — contextIsolation + nodeIntegration:false + CSP + 内置解析器 XSS 防护 4. **可测试性** — 业务逻辑(lib/)与 UI(components/)解耦 ## 2. 技术架构 ### 2.1 技术栈 | 组件 | 技术 | 版本 | 说明 | |------|------|------|------| | 桌面框架 | Electron | v28 | 跨平台桌面应用框架 | | 前端框架 | React | v18 | 函数组件 + Hooks | | 类型系统 | TypeScript | v5.6 | 全量类型安全 | | 编辑器 | MetonaEditor | v0.4.0 | 零依赖 Markdown 编辑器,三模式视图 + 插件系统 | | 状态管理 | Zustand | v5 | 轻量级状态管理 | | 持久化 | MetonaSqlark | v0.7.4 | 标签页状态 / 用户设置 / 最近文件(AriaEngine + KVStore,OPFS 落盘;v0.6.2 起不再使用 IndexedDB,旧库启动时自动删除) | | Markdown 解析 | MetonaEditor 内置解析器 | v0.4.0 | 零依赖,GFM + 脚注 + 数学公式 + mermaid | | 代码高亮 | MetonaEditor 内置高亮器 | v0.4.0 | 零依赖,16 种语言 | | Toast | @metona-team/metona-toast | v0.5.0 | 通知提示组件 | | 构建工具 | 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 22 个方法/事件的类型安全接口 │ └───────────────────────┬──────────────────────────────────────┘ │ ┌───────────────────────▼──────────────────────────────────────┐ │ Renderer Process (src/renderer/) React 18 │ │ │ │ components/ Toolbar · TabBar · Editor · Sidebar │ │ FileTree · OutlinePanel · WelcomeScreen │ │ ConfirmDialog · ModifiedBanner │ │ DropOverlay · ErrorBoundary · AboutDialog│ │ LoadingSpinner · Icons │ │ │ │ stores/ (4) tabStore · editorStore · sidebarStore │ │ autoSaveStore │ │ hooks/ (15) useTheme · useSettingsInit · useKeyboard │ │ useDragDrop · useFileWatch · useAutoSave │ │ useIpcListeners · useFileOperations ... │ │ lib/ (4) markdown · fileUtils · errorHandler │ │ toast · 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` | 仅暴露 18 个类型安全方法 + 4 个事件订阅 | | CSP | `default-src 'self'; script-src 'self'` | 阻断内联脚本、外部资源 | | HTML | `MetonaEditor 内置解析器` | 渲染 Markdown(escapeHTML + safeUrl XSS 防护) | | 链接 | 协议白名单 | 仅允许 `http:` / `https:` / `#` 锚点 | | 路径 | `validatePath()` | 防止路径遍历攻击 | ## 3. 状态管理架构 ### 3.1 Zustand Stores ``` ┌─────────────────────────────────────────────────────────┐ │ App.tsx (根组件) │ ├─────────┬──────────┬──────────┬──────────────────────────┤ │Toolbar │ TabBar │ Sidebar │ Editor (MetonaEditor) │ ├─────────┴──────────┴──────────┴──────────────────────────┤ │ Zustand Stores │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ │ tabStore │ │editorStore│ │sidebarStore│ │autoSaveStore│ │ │ │ - tabs │ │- viewMode│ │- tree │ │- isSaving │ │ │ │- activeId│ │- darkMode│ │- expanded │ │- enabled │ │ │ │ - mru │ │- extMod │ │- rootPath │ │ │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │ │ │ │ │ │ │ │ ┌────▼────────────▼────────────▼──────────────▼──────┐ │ │ │ IndexedDB (MetonaSqlark) │ │ │ │ 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(防抖 500ms) loadFromDB() // 从 IndexedDB 加载 } ``` ### 3.3 editorStore — 编辑器状态 ```typescript interface EditorState { viewMode: 'editor' | 'preview' | 'source' // 视图模式 darkMode: boolean // 暗色主题 externallyModified: { filePath: string } | null // 外部修改检测状态 loadingStates: Record // 全局加载状态 } // 模块级 getter — 供 Sidebar/OutlinePanel 访问 MetonaEditor 实例 function getMetonaEditor(): MarkdownEditor | null function setMetonaEditorGetter(fn: () => MarkdownEditor | null): void ``` ### 3.4 sidebarStore — 侧边栏状态 ```typescript interface SidebarState { isVisible: boolean // 是否显示 rootPath: string | null // 当前打开的文件夹路径 tree: FileNode[] // 目录树数据 expandedDirs: string[] // 已展开的目录集合 sidebarWidth: number // 侧边栏宽度 (180~500) } ``` ## 4. 数据持久化 — MetonaSqlark (KVStore) v0.5.0: 由 Dexie.js 迁移至 MetonaSqlark(AriaEngine:自研 LSM-Tree + WAL + MVCC 存储引擎,对标 SQLite)。 MetonaSqlark 的 create() 为异步,采用懒加载单例(getDb()), 表结构幂等创建(查表名后 defineTable),数据库名更换为 MarkLiteV2(旧 Dexie 数据已放弃)。 v0.6.2: sqlark 升级 0.7.4,存储后端改用自研 KVStore(内存索引 + 快照/日志,OPFS 落盘); 旧 IndexedDB 库在启动时自动删除,不做向下兼容。 ### 4.1 数据库 Schema ```typescript // schema.ts — 懒加载单例 let dbPromise: Promise | null = null export function getDb(): Promise { if (!dbPromise) { dbPromise = create({ name: 'MarkLiteV2', mode: 'aria', // AriaEngine: LSM-Tree + WAL + MVCC 快照隔离 diskEngine: 'kv', // v0.6.2: 自研 KVStore 后端(OPFS 落盘;jsdom 测试回退 memory) version: 0, // AriaEngine 忽略版本号 }).then(async (db) => { if (!(await db.getTableNames()).includes('tabSnapshots')) { await db.defineTable('tabSnapshots', { id: { type: 'string', primaryKey: true }, ... }) } // settings / recentFiles / activeTab 同理 return db }) } return dbPromise } ``` ### 4.2 数据模型 | Store | 字段 | 说明 | |-------|------|------| | `tabSnapshots` | id(PK), filePath, content, scrollTop, selectionStart, selectionEnd, isModified, updatedAt(index) | 标签页状态快照 | | `settings` | id(PK), themeMode, viewMode, sidebarCollapsed, sidebarWidth | 用户偏好设置 | | `recentFiles` | filePath(PK), lastOpened(index) | 最近打开文件列表(v0.5.0 改用 filePath 主键,sqlark 无自增) | | `activeTab` | id(PK), 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` | 停止监听目录变化 | | `dir:search` | `{ dirPath, query, caseSensitive?, useRegex? }` | `SearchInDirResult` | v0.6.2: 文件夹内多文件内容搜索 | ### 5.2 主进程 → 渲染进程(send) | 通道 | 数据 | 说明 | |------|------|------| | `file:openInTab` | `{ filePath, content }` | 在新标签中打开文件 | | `file:externallyModified` | `filePath` | 文件被外部修改 | | `window:confirmClose` | 无 | 请求确认关闭 | | `sidebar:dirChanged` | 无 | 目录结构变化 | ## 6. 编辑器架构 — MetonaEditor v0.1.14 ### 6.1 三模式视图 - **编辑模式 (edit)**:纯文本编辑器,显示 Markdown 源码 - **分屏模式 (split)**:左侧编辑、右侧实时预览,同步滚动 - **预览模式 (preview)**:仅显示渲染后的 HTML MetonaEditor 内置模式切换工具栏,与应用层的 viewMode store 双向同步,切换时自动持久化到 IndexedDB。 ### 6.2 内置功能 | 功能 | 说明 | |------|------| | 格式化工具栏 | bold / italic / strikethrough / underline / code / h1-h3 / quote / ul / ol / indent / outdent / link / image / table / hr | | 搜索替换 | Ctrl+F / Ctrl+H,支持正则、大小写敏感 | | 历史栈 | undo / redo,防抖合并,可配置上限 | | 主题 | light / dark / auto / warm,CSS 变量驱动 | | 国际化 | zh-CN / en-US 完整翻译 | | 全屏模式 | 编辑器全屏展示 | ### 6.3 插件体系 通过 `plugins` 配置数组安装 MetonaEditor 预设插件: | 插件 | 说明 | |------|------| | searchReplace | Ctrl+F 查找、Ctrl+H 替换面板 | | imagePaste | Ctrl+V 粘贴剪贴板图片,自动转 base64 | > 注:autoSave 插件仅支持 localStorage,而 MarkLite 需要文件系统保存(Electron IPC),因此使用自定义 useAutoSave hook。 ### 6.4 渲染管线集成 v0.6.0: 移除 unified/remark/rehype 自研管线,改用 MetonaEditor **内置解析器**(parseMarkdown), 通过 `render` 钩子接入,实现: - **相对路径图片解析**:内置解析器的 safeUrl 会过滤 `file:` 协议,因此渲染后做 HTML 后处理,将相对路径图片 src 转换为 `file://` 绝对路径(越界路径保持原样) - **XSS 防护**:内置 escapeHTML + safeUrl(过滤 javascript:/vbscript:/file:/data:)+ 属性转义 - **代码高亮**:内置零依赖高亮器(`highlight: MeEditor.highlight`,16 种语言) - **Mermaid 图表**:内置解析器原生输出 `.me-mermaid` 容器,`mermaid.run()` 直接渲染 ``` Markdown 源码 │ ▼ parseMarkdown(MetonaEditor 内置解析器) ├── GFM / 任务列表 / 表格 / 删除线 ├── 脚注 / 数学公式 / 定义列表 / emoji ├── 引用链接 / 自动链接 / 上下标 ├── mermaid →
… └── XSS 防护(escapeHTML + safeUrl) │ ▼ fixImageSrcs(HTML 后处理:相对路径 → file://) │ ▼ MetonaEditor 预览区渲染 ``` ### 6.5 主题切换 MetonaEditor 的 CSS 样式通过 wrapper 元素上的 inline `--md-*` CSS 变量驱动。主题切换流程: 1. `MeEditor.setTheme(dark/light)` — 更新 documentElement 全局变量 + localStorage 2. 手动覆写 `.me-wrapper` 上的 inline CSS 变量(`style.setProperty`) 3. 双向同步:应用工具栏暗色按钮 ⇄ 编辑器主题 ### 6.6 内容同步 - **编辑 → 存储**:`onChange` 回调 → `updateTabContent` + `setModified` - **标签切换**:`setValue(content, { silent: true })` 静默更新,避免重复触发 onChange - **滚动持久化**:切换标签时通过 DOM 查询 `textarea` / `.me-preview` 保存/恢复滚动位置 ## 7. Markdown 渲染管线 v0.6.0: 渲染完全由 MetonaEditor 内置解析器承担(零依赖),应用侧仅保留图片路径修复后处理: ``` Markdown 文本 │ ▼ parseMarkdown(MetonaEditor 内置解析器) ├── 块级:标题 / 列表 / 引用 / 代码块 / 表格 / 水平线 / 脚注 / 数学公式 / 定义列表 ├── 行内:粗体 / 斜体 / 删除线 / 高亮 / 上下标 / 行内代码 / 链接 / 图片 / emoji ├── mermaid:
…
    └── 安全:escapeHTML 转义 + safeUrl URL 过滤 + 属性级注入防护
    │
    ▼
fixImageSrcs(markdown.ts 后处理)
    └── 相对路径图片 src → file:// 绝对路径(越界 ../ 不处理)
    │
    ▼
MetonaEditor 预览区 / getHTML 导出
```

## 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 │  ┌─────────────────────────────────────────────┐              │
│   📁 src  │  │ B I S U  │ H1 H2 H3 │ " 1. 2. ≡ ⇥ ⇤ │  │              │
│   📄 file1│  │ 🔗 🖼 ⊞ — │ ↶ ↷ │ 📝 ⇔ 👁 ⊞           │  │              │
│   📄 file2│  ├─────────────────────────────────────────────┤              │
│           │  │ # Title              │  Title               │              │
│ 文档大纲   │  │                      │  ───────              │              │
│  · Title  │  │ content...           │  content...          │              │
├──────────┴──┴─────────────────────────────────────────────┴──────────────┤
│       (MetonaEditor 底栏: 字数/行数/阅读时间)                             │
└──────────────────────────────────────────────────────────────────────────┘
```

## 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 | 状态管理 |
| @metona-team/metona-sqlark | 0.7.4 | 前端关系型数据库(AriaEngine + KVStore,OPFS) |
| nanoid | ^5.0 | 唯一 ID 生成 |
| @metona-team/metona-editor | 0.4.0 | Markdown 编辑器(内置解析器 + 高亮) |
| @metona-team/metona-toast | 0.5.0 | Toast 通知组件 |
| mermaid | ^10.9 | Mermaid 图表渲染 |

### 开发依赖

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