Files
MarkLite/DESIGN.md
T
thzxx b65e34e288 v0.3.10: 全面优化增强 — 17项Bug修复/稳定性/功能改进
P0 致命Bug修复:
- A1: openFolderDialog 类型/运行时崩溃(文件夹打开功能完全失效)
- A2: SourceEditor 受控textarea手动改DOM反模式
- A3: tabStore.updateTabContent 内容相同时误标isModified

死代码清理:
- B1: 删除menu:*/save/as/viewMode 三个永不触发的IPC通道

健壮性修复:
- E1: rehypeFixImages 路径规范化+防越界加固
- E2: getFileName 尾斜杠返回正确文件名
- E3: EditorToolbar 行内代码按钮改用toggleInlineCodeCommand
- E4: ErrorBoundary 内联样式抽为CSS类

功能增强:
- D1: 标签页拖拽排序(tabStore.moveTab + TabBar DnD + CSS)
- D2: Milkdown自动配对括号/引号(ProseMirror插件)
- D3: 状态栏自动保存开关可点击
- D4: 文档大纲活跃标题高亮(useActiveHeading hook)
- D5: 关闭窗口前flush防抖数据(flushSaveToDB)
- D6: 搜索替换支持正则表达式

类型/架构:
- C2: preload类型集中定义(ElectronAPI契约)
- __pycache__ typo修复

文档/版本:
- README/DESIGN同步为Milkdown + v0.3.10
- 项目结构树/技术栈/快捷键表更新

验证: typecheck(仅7预存), lint, test 96/96, vite build
2026-06-23 10:47:45 +08:00

17 KiB
Raw Blame History

MarkLite v0.3.10 — 架构设计文档

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/)与 UIcomponents/)解耦

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 + ViteHMR 热更新
打包工具 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 devHMR 热更新)

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 代码检查