Files
MetonaEditor/README.md
T
thzxx 16464af0ae docs: rewrite README.md and site/ for v0.2.0
- README.md: full API reference, syntax table, plugin/theme/i18n docs
- site/index.html: v0.2.0 hero, live demo, feature cards, API overview
- site/docs.html: sidebar nav, full config, instance API tables, parser reference
- site/demo.html: all-syntax demo with TS code samples
2026-07-24 22:42:42 +08:00

689 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MetonaEditor
> TypeScript 重构 · 零运行时依赖 · 轻量级桌面端 Markdown Editor 库
[![version](https://img.shields.io/badge/version-0.2.0-blue)](https://git.metona.cn/MetonaTeam/-/packages/npm/@metona-team%2Fmetona-editor)
[![license](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
[![tests](https://img.shields.io/badge/tests-610%20passed-brightgreen)](./tests)
[![coverage](https://img.shields.io/badge/coverage-parser%2099%25-brightgreen)](./tests)
[![types](https://img.shields.io/badge/types-TypeScript%20strict-blue)](./tsconfig.json)
## 特性
- **TypeScript 源码** — 全模块 TypeScript 严格模式,完整类型导出,IDE 智能提示
- **零运行时依赖** — 打包后单文件,无任何第三方库,UMD / ESM / CJS 三种格式
- **自研解析器** — CommonMark + GFM 扩展,96% 语句覆盖率,parseTokens / renderTokens 分离 APIregisterBlockHandler 自定义块语法
- **插件系统 v2** — 拓扑排序依赖管理,6 个预设插件开箱即用,安装 / 卸载生命周期
- **三模式视图** — edit / split / preview,拖拽分隔条调整比例,双向滚动同步
- **主题系统** — light / dark / warm / autoCSS 变量可定制,实例级隔离,主题继承,外部跟随
- **国际化** — zh-CN / en-US 完整翻译,实例级语言隔离,远程加载翻译包
- **编辑体验** — 行号装订线、智能 Enter、括号自动闭合、拖放文件、大纲面板、Zen 专注模式
- **安全** — HTML 转义,XSS 协议过滤(javascript / vbscript / file / data),sanitize 钩子
- **桌面端优先** — 纯电脑端设计,无移动端冗余代码
---
## 目录
- [快速开始](#快速开始)
- [配置项](#配置项)
- [工具栏](#工具栏)
- [实例 API](#实例-api)
- [静态 API](#静态-api)
- [事件系统](#事件系统)
- [Markdown 解析器](#markdown-解析器)
- [插件系统](#插件系统)
- [主题系统](#主题系统)
- [国际化](#国际化)
- [构建与测试](#构建与测试)
- [License](#license)
---
## 快速开始
### npm 安装
```bash
npm install @metona-team/metona-editor
```
```typescript
import MeEditor from '@metona-team/metona-editor';
const editor = MeEditor.create('#editor', {
value: '# Hello World',
mode: 'split',
plugins: ['autoSave', 'searchReplace'],
});
```
### CDN 引入
```html
<div id="editor"></div>
<script src="https://git.metona.cn/MetonaTeam/MetonaEditor/raw/branch/master/dist/metona-editor.js"></script>
<script>
const editor = MeEditor.create('#editor', { mode: 'split' });
</script>
```
---
## 配置项
```typescript
MeEditor.create(container, {
// 内容
value: '', // 初始 Markdown 文本
placeholder: '', // 占位符
// 视图
mode: 'split', // 'edit' | 'split' | 'preview'
height: 400, // 数字为 px,字符串原样使用
toolbar: DEFAULT_TOOLBAR, // 工具栏按钮数组,false 隐藏
wordCount: true, // 状态栏字数统计
lineNumbers: true, // 行号装订线
outline: false, // 大纲面板
// 行为
autofocus: false, // 自动聚焦
spellcheck: false, // 拼写检查
readOnly: false, // 只读模式
tabSize: 2, // Tab 空格数(0 = \t
historyLimit: 100, // 历史栈上限
historyDebounce: 400, // 历史栈防抖(ms
syncScroll: true, // 分屏同步滚动
autoBrackets: true, // 括号自动闭合
zenMode: false, // Zen 专注模式
wordWrap: true, // 自动换行
maxLength: 0, // 最大字符数(0=不限)
// 主题与语言
theme: 'auto', // 'light' | 'dark' | 'warm' | 'auto'
locale: 'zh-CN', // 'zh-CN' | 'en-US'
// 渲染钩子
render: null, // (md: string, env: RenderEnv) => string
highlight: null, // (code: string, lang: string) => string
sanitize: null, // (html: string) => string
// 外观
className: '', // 容器额外 CSS class
style: {}, // 容器内联样式
// 插件
plugins: [], // 实例级插件列表
// 回调
onChange: null, // (value: string, editor: MarkdownEditor) => void
onInput: null, // (value: string, editor: MarkdownEditor) => void
onFocus: null, // (editor: MarkdownEditor) => void
onBlur: null, // (editor: MarkdownEditor) => void
onSave: null, // (value: string, editor: MarkdownEditor) => void
onModeChange: null, // (mode: EditMode, editor: MarkdownEditor) => void
onFullscreen: null, // (fullscreen: boolean, editor: MarkdownEditor) => void
onCreate: null, // (editor: MarkdownEditor) => void
onDestroy: null, // (editor: MarkdownEditor) => void
onLinkClick: null, // (href: string, text: string, editor: MarkdownEditor) => void
});
```
---
## 工具栏
### 默认工具栏
```typescript
['bold','italic','strikethrough','code','|',
'h1','h2','h3','|',
'quote','ul','ol','|',
'link','image','table','hr','|',
'undo','redo','|',
'edit','split','preview','fullscreen']
```
### 自定义工具栏
```typescript
// 精简版
MeEditor.create('#editor', {
toolbar: ['bold', 'italic', '|', 'h1', 'h2', '|', 'undo', 'redo'],
});
// 添加自定义按钮
editor.addToolbarButton({
action: 'timestamp',
title: '插入时间戳',
icon: '<svg>...</svg>',
onClick: (editor) => editor.insert(new Date().toISOString()),
});
```
---
## 实例 API
### 内容操作
```typescript
editor.getValue(): string
editor.setValue(md: string, opts?: { silent?: boolean }): this
editor.getHTML(): string
editor.refresh(): this // 强制重渲染(内容未变时)
editor.insert(text: string, opts?: { replace?: boolean }): this
editor.wrap(before: string, after?: string): this
```
### 命令执行
```typescript
editor.exec(action: string): this
// 支持 action
// bold italic strikethrough underline code
// h1 h2 h3 quote ul ol hr
// link image table
// indent outdent undo redo
// edit split preview fullscreen
// zen wordwrap
```
### 历史栈
```typescript
editor.undo(): this
editor.redo(): this
editor.canUndo(): boolean
editor.canRedo(): boolean
```
### 模式与全屏
```typescript
editor.setMode(mode: 'edit' | 'split' | 'preview'): this
editor.getMode(): EditMode
editor.toggleFullscreen(): this
editor.isFullscreen(): boolean
editor.exitFullscreen(): this
```
### 主题与语言
```typescript
editor.setTheme(theme: string): this
editor.getTheme(): string
editor.setLocale(locale: string): this
editor.getLocale(): string
editor.t(key: string, params?: object): string
```
### 统计与状态
```typescript
editor.getStats(): { characters, words, chineseChars, englishWords, lines, readingTime }
editor.getStatus(): { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }
```
### 启用 / 禁用 / 只读
```typescript
editor.enable(): this
editor.disable(): this
editor.isDisabled(): boolean
editor.setReadOnly(readOnly: boolean): this
editor.isReadOnly(): boolean
```
### 事件
```typescript
editor.on(name: string, fn: (...args: any[]) => void): () => void
editor.off(name: string, fn: (...args: any[]) => void): this
```
### 插件
```typescript
editor.use(plugin: string | PluginObject, options?: object): this
editor.unuse(name: string): this
editor.getPlugins(): PluginObject[]
editor.addToolbarButton(config: ToolbarButtonConfig): this
```
### 快捷键
```typescript
editor.registerShortcut(combo: string, handler: Function | string, description?: string): this
editor.unregisterShortcut(combo: string): this
editor.getShortcuts(): Shortcut[]
```
### 工具栏管理
```typescript
editor.configureToolbar(tools: string[]): this
editor.removeToolbarButton(action: string): this
```
### Zen / Word Wrap
```typescript
editor.toggleZen(): this
editor.isZen(): boolean
editor.toggleWordWrap(): this
editor.setWordWrap(on: boolean): this
editor.isWordWrap(): boolean
```
### Toast
```typescript
editor.toast(message: string, opts?: {
type?: 'success' | 'error' | 'warning' | 'info';
duration?: number;
animation?: 'fade' | 'slide' | 'scale';
}): this
```
### 销毁
```typescript
editor.destroy(): void
editor.isDestroyed(): boolean
```
---
## 静态 API
```typescript
import MeEditor from '@metona-team/metona-editor';
// 工厂函数
MeEditor.create(container, options)
// 全局默认插件
MeEditor.use(plugin, options)
MeEditor.use('autoSave', { delay: 2000 })
// 全局事件钩子
MeEditor.on('beforeCreate', (editor) => {})
MeEditor.off('beforeCreate', handler)
// 全局主题 / 语言
MeEditor.setTheme('dark')
MeEditor.setLocale('en-US')
// 状态查询
MeEditor.getStatus()
// => { version, theme, locale, globalPlugins, presetPlugins }
// 销毁全局资源
MeEditor.destroy()
// 解析器独立使用
import { parseMarkdown, parseTokens, renderTokens, safeUrl, slugify, registerBlockHandler } from '@metona-team/metona-editor';
```
---
## 事件系统
### 实例事件
| 事件 | 触发时机 | 参数 |
|------|---------|------|
| `input` | textarea 原生 input | `(value, editor)` |
| `change` | 内容变化 | `(value, editor)` |
| `focus` | 聚焦 | `(editor)` |
| `blur` | 失焦 | `(editor)` |
| `save` | Ctrl+S | `(editor)` |
| `modeChange` | 模式切换 | `(mode, editor)` |
| `fullscreen` | 全屏切换 | `(fullscreen, editor)` |
| `themeChange` | 实例主题变更 | `({ theme, resolved, config })` |
| `localeChange` | 实例语言变更 | `({ locale, direction })` |
| `zenChange` | Zen 模式切换 | `(zen, editor)` |
| `destroy` | 销毁 | `()` |
| `autosave` | autoSave 触发 | `({ key, value })` |
| `beforeRender` | 渲染前 | `(editor)` |
| `afterRender` | 渲染后 | `(editor)` |
| `linkClick` | 预览区链接点击 | `({ href, text })` |
| `fileOpened` | fileSystem 打开文件 | `({ name, handle })` |
| `fileSaved` | fileSystem 保存文件 | `({ handle })` |
### 全局钩子
| 钩子 | 触发时机 |
|------|---------|
| `beforeCreate` | 实例构造初始化前 |
| `afterCreate` | 实例构造初始化完成 |
| `beforeRender` | 每次渲染前(全局) |
| `afterRender` | 每次渲染后(全局) |
| `beforeDestroy` | destroy 前 |
| `afterDestroy` | destroy 后 |
---
## Markdown 解析器
### 支持语法
| 语法 | 示例 | 输出 |
|------|------|------|
| ATX 标题 | `# H1` ~ `###### H6` | `<h1>` ~ `<h6>` |
| Setext 标题 | `H1\n===` | `<h1>` / `<h2>` |
| 粗体 | `**bold**` `__bold__` | `<strong>` |
| 粗斜体 | `***text***` `___text___` | `<em><strong>` |
| 斜体 | `*italic*` `_italic_` | `<em>` |
| 删除线 | `~~text~~` | `<del>` |
| 高亮 | `==text==` | `<mark>` |
| 上标 | `x^2^` | `<sup>` |
| 下标 | `H~2~O` | `<sub>` |
| 行内代码 | `` `code` `` | `<code>` |
| 代码块 | ` ```lang ` | `<pre><code class="language-lang">` |
| 缩进代码块 | ` code` / `\tcode` | `<pre><code>` |
| 引用 | `> quote` | `<blockquote>` |
| 无序列表 | `- / * / + item` | `<ul><li>` |
| 有序列表 | `1. item` | `<ol><li>` |
| 嵌套列表 | 多级缩进 | 多级 `<ul>/<ol>` |
| 任务列表 | `- [x] done` | `<li class="me-task-item">` |
| 水平线 | `---` `***` `___` | `<hr>` |
| 表格 | `\| a \| b \|` | `<table>` + 列对齐 |
| 链接 | `[text](url 'title')` | `<a>` |
| 图片 | `![alt](url)` | `<img>` |
| 自动链接 | `<https://...>` | `<a>` |
| 数学公式 | `$E=mc^2$` `$$\int$$` | `<span>` / `<div>` |
| 脚注 | `text[^1]` | `<sup>` + 底部定义 |
| 定义列表 | `Term\n: def` | `<dl><dt><dd>` |
| Emoji | `:smile:` `:rocket:` | 😊 🚀(150+ |
| 反斜杠转义 | `\*` `\_` `\\` | 取消标点特殊含义 |
| HTML 注释 | `<!-- note -->` | 透传 |
| 实体引用保护 | `&amp;` `&#169;` | 不被二次转义 |
### 链接内格式
```typescript
parseMarkdown('[**粗体链接**](https://example.com)')
// => <a href="..."><strong>粗体链接</strong></a>
```
### parseTokens / renderTokens
```typescript
import { parseTokens, renderTokens } from '@metona-team/metona-editor';
const { tokens, footnotes } = parseTokens('# Hello\n\n**world**');
// tokens: [{ type: 'heading', level: 1, text: 'Hello' }, ...]
// 在渲染前变换 token
tokens.unshift({ type: 'hr' });
const html = renderTokens(tokens, {}, footnotes);
```
### registerBlockHandler
```typescript
import { registerBlockHandler } from '@metona-team/metona-editor';
registerBlockHandler({
name: 'customAlert',
priority: 8.5, // 越小越先尝试
test: (line) => line.match(/^:::(\\w+)/),
parse: (lines, i, match) => ({
token: { type: 'alert', alertType: match[1] },
newIndex: i + 2,
}),
});
```
### XSS 安全
- 所有文本经 `escapeHTML` 转义
- 链接 URL 过滤 `javascript:` / `vbscript:` / `file:` / 非图片 `data:`
- `data:image` 限制最大 500KB
- `sanitize` 钩子供外部净化(如 DOMPurify)
- `highlight` 钩子异常自动回退为纯文本
---
## 插件系统
### 自定义插件
```typescript
const myPlugin = {
name: 'myPlugin',
version: '1.0.0',
description: 'My custom plugin',
install(editor) {
// 安装逻辑
editor.on('change', this._onChange);
},
destroy(editor) {
// 清理逻辑
editor.off('change', this._onChange);
},
};
editor.use(myPlugin, { optionA: 'value' });
editor.unuse('myPlugin');
```
### 预设插件
| 插件 | 说明 | 用法 |
|------|------|------|
| `autoSave` | localStorage 自动保存草稿 | `editor.use('autoSave', { delay: 1000 })` |
| `exportTool` | 导出 .md / .html 文件 | `editor.use('exportTool'); editor.exportMarkdown()` |
| `searchReplace` | Ctrl+F 查找 / Ctrl+H 替换 | `editor.use('searchReplace')` |
| `imagePaste` | 粘贴剪贴板图片转 base64 | `editor.use('imagePaste')` |
| `shortcutHelp` | 按 ? 弹出快捷键面板 | `editor.use('shortcutHelp')` |
| `fileSystem` | File System Access API 读写磁盘 | `editor.use('fileSystem'); editor.openFile()` |
### PluginManager & pluginUtils
```typescript
import { PluginManager, pluginUtils } from '@metona-team/metona-editor';
const pm = new PluginManager();
pm.register('my', myPlugin);
pm.has('my'); // true
pm.destroy();
pluginUtils.validatePlugin(plugin) // { valid, errors }
pluginUtils.createPlugin({ ... }) // 工厂函数
pluginUtils.getPreset('autoSave') // 预设副本
```
---
## 主题系统
### 内置主题
```typescript
editor.setTheme('light') // 亮色
editor.setTheme('dark') // 暗色
editor.setTheme('warm') // 暖色
editor.setTheme('auto') // 跟随系统(默认)
```
### CSS 变量定制
```css
:root {
--md-bg: #ffffff;
--md-text: #1f2937;
--md-border: rgba(0, 0, 0, 0.08);
--md-shadow: 0 10px 36px -10px rgba(0,0,0,0.18);
--md-toolbar-bg: rgba(248, 249, 250, 0.92);
--md-textarea-bg: #ffffff;
--md-preview-bg: #ffffff;
--md-code-bg: rgba(243, 244, 246, 1);
--md-code-text: #1f2937;
--md-accent: #3b82f6;
--md-muted: #6b7280;
--md-radius: 10px;
--md-font: -apple-system, "Segoe UI", "PingFang SC", sans-serif;
--md-mono: "SF Mono", "Consolas", monospace;
}
```
### 自定义主题
```typescript
import { themeUtils } from '@metona-team/metona-editor';
themeUtils.registerTheme('ocean', {
extends: 'dark', // 继承 dark 主题
bg: '#0a1628',
accent: '#38bdf8',
});
themeUtils.switchTheme('ocean');
```
### 外部主题跟随
```typescript
// 从父容器继承
editor.getThemeContext().adopt();
// 跟随外部元素
editor.getThemeContext().syncWithElement(document.body);
```
---
## 国际化
### 切换语言
```typescript
editor.setLocale('en-US') // 实例级
MeEditor.setLocale('zh-CN') // 全局
```
### 添加语言
```typescript
import { i18nUtils } from '@metona-team/metona-editor';
i18nUtils.addTranslations('ja', {
bold: '太字',
italic: '斜体',
});
```
### 远程加载翻译包
```typescript
await i18nUtils.loadRemote('/locales/ja.json', 'ja');
```
### 格式化工具
```typescript
i18nUtils.formatNumber(1234567) // '1,234,567'
i18nUtils.formatCurrency(99.99, 'USD') // '$99.99'
i18nUtils.formatDate('2024-01-15') // 本地化日期
```
---
## 快捷键
| 快捷键 | 功能 |
|--------|------|
| `Ctrl+B` | 粗体 |
| `Ctrl+I` | 斜体 |
| `Ctrl+U` | 下划线 |
| `Ctrl+E` | 行内代码 |
| `Ctrl+K` | 插入链接 |
| `Ctrl+1/2/3` | 标题 H1/H2/H3 |
| `Ctrl+Q` | 引用 |
| `Ctrl+Z` | 撤销 |
| `Ctrl+Y` / `Ctrl+Shift+Z` | 重做 |
| `Ctrl+S` | 保存 |
| `Ctrl+F` | 查找 |
| `Ctrl+H` | 查找替换 |
| `Tab` / `Shift+Tab` | 缩进 / 反缩进 |
| `?` | 快捷键帮助 |
| `Enter` | 智能 Enter(列表/引用延续) |
---
## 构建与测试
```bash
# 安装依赖
npm install
# 开发模式(热更新 + 示例站点)
npm run dev
# 构建产物(dist/
npm run build
# 类型检查
npm run typecheck
# 运行测试
npm test
# 测试覆盖率
npm run test -- --coverage
# 代码格式化
npm run format
```
### 构建产物
```
dist/
├── metona-editor.js UMD(浏览器直接引入)
├── metona-editor.min.js UMD 压缩版(CDN
├── metona-editor.esm.js ES Module
├── metona-editor.cjs.js CommonJS
└── metona-editor.d.ts TypeScript 类型声明
```
### 源码结构
```
src/
├── index.ts 入口 / 全局API
├── core.ts MarkdownEditor 类
├── parser.ts 自研 Markdown 解析器
├── plugins.ts 插件系统 & 6 个预设
├── themes.ts 主题系统
├── i18n.ts 国际化
├── styles.ts CSS-in-JS
├── constants.ts 常量 / 类型定义
├── utils.ts 工具函数
├── animations.ts 动画元数据
├── icons.ts 工具栏 SVG 图标
└── locales.ts 中英文翻译数据
```
### 技术栈
| 项目 | 技术 |
|------|------|
| 语言 | TypeScript 5 (strict) |
| 构建 | Rollup 3 |
| 测试 | Jest 29 + jsdom |
| 类型生成 | rollup-plugin-dts |
| 零运行时依赖 | ✅ |
---
## License
[MIT](./LICENSE) © MetonaTeam