Files
MarkLite/DESIGN.md
T
thzxx 1dd22be0a8 release: v0.4.3 — 版本号更新与预览区文本选择修复
- 版本号从 0.4.2 升级至 0.4.3
- 修复预览区内容无法鼠标拖拽选择/复制的问题
- 根因:html/body 上的 user-select: none 级联到预览面板
- 修复:为 .me-preview 及其子元素恢复 user-select: text
2026-07-24 20:50:59 +08:00

20 KiB
Raw Blame History

MarkLite v0.4.3 — 架构设计文档

1. 项目概述

MarkLite 是一款轻量级的 Windows 本地 Markdown 编辑器桌面应用程序。基于 Electron + React + TypeScript 构建,采用 MetonaEditor v0.1.3 编辑器(三模式视图 + 插件系统)、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 全量类型安全
编辑器 MetonaEditor v0.1.3 零依赖 Markdown 编辑器,三模式视图 + 插件系统
状态管理 Zustand v5 轻量级状态管理
持久化 Dexie.js (IndexedDB) v4 标签页状态 / 用户设置 / 最近文件
Markdown 解析 unified / remark / rehype v11 插件化渲染管线(作为 MetonaEditor render 钩子)
代码高亮 rehype-highlight v7 基于 highlight.js
Toast @metona-team/metona-toast v2.0.1 通知提示组件
构建工具 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 rehype-sanitize 渲染 Markdown 时过滤危险标签/属性
链接 协议白名单 仅允许 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 (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(防抖 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

通过 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. 编辑器架构 — MetonaEditor v0.1.3

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 渲染管线集成

通过 MetonaEditor 的 render 钩子接入 unified/rehype 管线,实现:

  • 相对路径图片解析:将相对路径转换为 file:// 绝对路径
  • XSS 防护rehype-sanitize 过滤危险标签
  • 代码高亮rehype-highlight 语法高亮
  • 处理器缓存:LRU 缓存(最多 20 个),按文件路径分桶
Markdown 源码
    │
    ▼
unified 管线(renderMarkdownSync
    ├── remark-parse     解析为 MDAST
    ├── remark-gfm       GFM 扩展
    ├── remark-rehype    转换为 HAST
    ├── rehype-raw       解析内联 HTML
    ├── rehype-sanitize   安全过滤
    ├── rehype-fixImages  相对路径 → file://
    ├── rehype-highlight  代码高亮
    └── rehype-stringify  序列化为 HTML
    │
    ▼
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 渲染管线

Markdown 文本
    │
    ▼
remark-parse          解析为 MDAST
    │
    ▼
remark-gfm            扩展 GFM 语法
    │
    ▼
remark-rehype          转换为 HAST
    │
    ▼
rehype-raw             解析内联 HTML
    │
    ▼
rehype-sanitize         安全过滤
    │
    ▼
rehype-fixImages        相对路径图片转 file:// URL
    │
    ▼
rehype-highlight        代码语法高亮
    │
    ▼
rehype-stringify        序列化为 HTML
    │
    ▼
Renderer (MetonaEditor preview / Preview component)

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 状态管理
dexie ^4.0 IndexedDB 封装
nanoid ^5.0 唯一 ID 生成
@metona-team/metona-editor ^0.1.3 Markdown 编辑器(零依赖)
@metona-team/metona-toast ^2.0.1 Toast 通知组件
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 代码检查