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
2026-07-23 16:23:07 +08:00
2026-07-23 16:23:07 +08:00
2026-07-23 16:23:07 +08:00
2026-07-23 16:23:07 +08:00

MetonaEditor

TypeScript 重构 · 零运行时依赖 · 轻量级桌面端 Markdown Editor 库

version license tests coverage types

特性

  • 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 钩子
  • 桌面端优先 — 纯电脑端设计,无移动端冗余代码

目录


快速开始

npm 安装

npm install @metona-team/metona-editor
import MeEditor from '@metona-team/metona-editor';

const editor = MeEditor.create('#editor', {
  value: '# Hello World',
  mode: 'split',
  plugins: ['autoSave', 'searchReplace'],
});

CDN 引入

<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>

配置项

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
});

工具栏

默认工具栏

['bold','italic','strikethrough','code','|',
 'h1','h2','h3','|',
 'quote','ul','ol','|',
 'link','image','table','hr','|',
 'undo','redo','|',
 'edit','split','preview','fullscreen']

自定义工具栏

// 精简版
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

内容操作

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

命令执行

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

历史栈

editor.undo(): this
editor.redo(): this
editor.canUndo(): boolean
editor.canRedo(): boolean

模式与全屏

editor.setMode(mode: 'edit' | 'split' | 'preview'): this
editor.getMode(): EditMode
editor.toggleFullscreen(): this
editor.isFullscreen(): boolean
editor.exitFullscreen(): this

主题与语言

editor.setTheme(theme: string): this
editor.getTheme(): string
editor.setLocale(locale: string): this
editor.getLocale(): string
editor.t(key: string, params?: object): string

统计与状态

editor.getStats(): { characters, words, chineseChars, englishWords, lines, readingTime }
editor.getStatus(): { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }

启用 / 禁用 / 只读

editor.enable(): this
editor.disable(): this
editor.isDisabled(): boolean
editor.setReadOnly(readOnly: boolean): this
editor.isReadOnly(): boolean

事件

editor.on(name: string, fn: (...args: any[]) => void): () => void
editor.off(name: string, fn: (...args: any[]) => void): this

插件

editor.use(plugin: string | PluginObject, options?: object): this
editor.unuse(name: string): this
editor.getPlugins(): PluginObject[]
editor.addToolbarButton(config: ToolbarButtonConfig): this

快捷键

editor.registerShortcut(combo: string, handler: Function | string, description?: string): this
editor.unregisterShortcut(combo: string): this
editor.getShortcuts(): Shortcut[]

工具栏管理

editor.configureToolbar(tools: string[]): this
editor.removeToolbarButton(action: string): this

Zen / Word Wrap

editor.toggleZen(): this
editor.isZen(): boolean
editor.toggleWordWrap(): this
editor.setWordWrap(on: boolean): this
editor.isWordWrap(): boolean

Toast

editor.toast(message: string, opts?: {
  type?: 'success' | 'error' | 'warning' | 'info';
  duration?: number;
  animation?: 'fade' | 'slide' | 'scale';
}): this

销毁

editor.destroy(): void
editor.isDestroyed(): boolean

静态 API

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; 不被二次转义

链接内格式

parseMarkdown('[**粗体链接**](https://example.com)')
// => <a href="..."><strong>粗体链接</strong></a>

parseTokens / renderTokens

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

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 钩子异常自动回退为纯文本

插件系统

自定义插件

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

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')   // 预设副本

主题系统

内置主题

editor.setTheme('light')   // 亮色
editor.setTheme('dark')    // 暗色
editor.setTheme('warm')    // 暖色
editor.setTheme('auto')    // 跟随系统(默认)

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;
}

自定义主题

import { themeUtils } from '@metona-team/metona-editor';

themeUtils.registerTheme('ocean', {
  extends: 'dark',           // 继承 dark 主题
  bg: '#0a1628',
  accent: '#38bdf8',
});
themeUtils.switchTheme('ocean');

外部主题跟随

// 从父容器继承
editor.getThemeContext().adopt();

// 跟随外部元素
editor.getThemeContext().syncWithElement(document.body);

国际化

切换语言

editor.setLocale('en-US')    // 实例级
MeEditor.setLocale('zh-CN')  // 全局

添加语言

import { i18nUtils } from '@metona-team/metona-editor';

i18nUtils.addTranslations('ja', {
  bold: '太字',
  italic: '斜体',
});

远程加载翻译包

await i18nUtils.loadRemote('/locales/ja.json', 'ja');

格式化工具

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(列表/引用延续)

构建与测试

# 安装依赖
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 © MetonaTeam

S
Description
轻量、零依赖、精致美观的 Markdown Editor 库
https://editor.metona.cn
Readme MIT
984 KiB
Languages
TypeScript 81.3%
HTML 17.5%
JavaScript 0.7%
Shell 0.5%