Files
MarkLite/DESIGN.md
T
thzxx ee5f35177e release: v0.6.0 — 内置解析器迁移 / Toast 全面接入 / Sqlark 深度集成
- 渲染管线迁移至 MetonaEditor 内置解析器,移除 unified/rehype 全家桶(9 个依赖)
- 修复相对路径图片修复的目录前缀碰撞与路径解析 bug
- ConfirmDialog/useConfirm/LoadingSpinner/useDocStats 移除,改用 MeToast.confirm/loading/promise
- 新增状态栏(字数/行数/阅读时间/光标位置)、Zen 模式、数据备份导出导入
- Sqlark: 版本化迁移(addMigration/migrateTo)、subscribe 表变更、备份 exportAll/importTable
- 数据库损坏自愈:异常退出残留残缺 SSTable 导致打开失败时自动重建
- 大纲导航改用 scrollToLine 官方 API
- 修复 rollup 平台包互删(postinstall 自动补齐)+ 集成测试(fake-indexeddb)
2026-08-09 16:50:06 +08:00

22 KiB
Raw Blame History

MarkLite v0.4.3 — 架构设计文档

1. 项目概述

MarkLite 是一款轻量级的 Windows 本地 Markdown 编辑器桌面应用程序。基于 Electron + React + TypeScript 构建,采用 MetonaEditor v0.4.0 编辑器(三模式视图 + 内置解析器 + 插件系统)、Zustand 状态管理、MetonaSqlarkAriaEngine)持久化。

1.1 核心原则

  1. 类型安全 — 全量 TypeScript,所有 IPC 通信、状态、接口均有类型定义
  2. 模块化 — 源文件按职责分层:主进程 / 预加载 / 渲染进程(组件 / stores / hooks / lib / db / types
  3. 安全隔离 — contextIsolation + nodeIntegration:false + CSP + 内置解析器 XSS 防护
  4. 可测试性 — 业务逻辑(lib/)与 UIcomponents/)解耦

2. 技术架构

2.1 技术栈

组件 技术 版本 说明
桌面框架 Electron v28 跨平台桌面应用框架
前端框架 React v18 函数组件 + Hooks
类型系统 TypeScript v5.6 全量类型安全
编辑器 MetonaEditor v0.4.0 零依赖 Markdown 编辑器,三模式视图 + 插件系统
状态管理 Zustand v5 轻量级状态管理
持久化 MetonaSqlark (IndexedDB) v0.4.1 标签页状态 / 用户设置 / 最近文件(AriaEngine
Markdown 解析 MetonaEditor 内置解析器 v0.4.0 零依赖,GFM + 脚注 + 数学公式 + mermaid
代码高亮 MetonaEditor 内置高亮器 v0.4.0 零依赖,16 种语言
Toast @metona-team/metona-toast v0.5.0 通知提示组件
构建工具 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        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 内置解析器 渲染 MarkdownescapeHTML + 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 — 标签页状态

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 — 编辑器状态

interface EditorState {
  viewMode: 'editor' | 'preview' | 'source'  // 视图模式
  darkMode: boolean                // 暗色主题
  externallyModified: { filePath: string } | null  // 外部修改检测状态
  loadingStates: Record<string, boolean>           // 全局加载状态
}

// 模块级 getter — 供 Sidebar/OutlinePanel 访问 MetonaEditor 实例
function getMetonaEditor(): MarkdownEditor | null
function setMetonaEditorGetter(fn: () => MarkdownEditor | null): void

3.4 sidebarStore — 侧边栏状态

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

4. 数据持久化 — IndexedDB

v0.5.0: 由 Dexie.js 迁移至 MetonaSqlarkAriaEngine:自研 LSM-Tree + WAL + MVCC 存储引擎,对标 SQLite)。 MetonaSqlark 的 create() 为异步,采用懒加载单例(getDb()), 表结构幂等创建(查表名后 defineTable),数据库名更换为 MarkLiteV2(旧 Dexie 数据已放弃)。

4.1 数据库 Schema

// schema.ts — 懒加载单例
let dbPromise: Promise<MetonaSqlark> | null = null
export function getDb(): Promise<MetonaSqlark> {
  if (!dbPromise) {
    dbPromise = MetonaSqlark.create({
      name: 'MarkLiteV2',
      mode: 'aria',             // AriaEngine: LSM-Tree + WAL + MVCC 快照隔离
      diskEngine: 'indexeddb',  // 底层存储后端(indexeddb | opfs | memory
      version: 1,
    }).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 停止监听目录变化

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 / warmCSS 变量驱动
国际化 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.highlight16 种语言)
  • Mermaid 图表:内置解析器原生输出 .me-mermaid 容器,mermaid.run() 直接渲染
Markdown 源码
    │
    ▼
parseMarkdownMetonaEditor 内置解析器)
    ├── GFM / 任务列表 / 表格 / 删除线
    ├── 脚注 / 数学公式 / 定义列表 / emoji
    ├── 引用链接 / 自动链接 / 上下标
    ├── mermaid → <div class="me-mermaid">…
    └── XSS 防护(escapeHTML + safeUrl
    │
    ▼
fixImageSrcsHTML 后处理:相对路径 → 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 文本
    │
    ▼
parseMarkdownMetonaEditor 内置解析器)
    ├── 块级:标题 / 列表 / 引用 / 代码块 / 表格 / 水平线 / 脚注 / 数学公式 / 定义列表
    ├── 行内:粗体 / 斜体 / 删除线 / 高亮 / 上下标 / 行内代码 / 链接 / 图片 / emoji
    ├── mermaid<div class="me-mermaid"><pre class="mermaid">…
    └── 安全:escapeHTML 转义 + safeUrl URL 过滤 + 属性级注入防护
    │
    ▼
fixImageSrcsmarkdown.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 开发模式

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 状态管理
@metona-team/metona-sqlark 0.4.1 前端关系型数据库(AriaEngine)
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 代码检查