安装与引入
# npm 安装 npm install @metona-team/metona-editor # ES Module import MeEditor from '@metona-team/metona-editor'; const editor = MeEditor.create('#editor', { value: '# Hello World', mode: 'split', }); # 浏览器 UMD <script src="dist/metona-editor.js"></script> <script> const editor = MeEditor.create('#editor'); </script>
全部配置项
所有配置均为可选,以下是默认值:
MeEditor.create(container, { // 内容 value: '', // 初始 Markdown 文本 placeholder: '', // 占位符 // 视图 mode: 'split', // 'edit' | 'split' | 'preview' height: 400, // 数字为 px,字符串原样使用 toolbar: DEFAULT_TOOLBAR, // 工具栏配置,false 隐藏 wordCount: true, // 字数统计状态栏 // 行为 autofocus: false, // 自动聚焦 spellcheck: false, // 拼写检查 readOnly: false, // 只读模式 historyLimit: 100, // 历史栈上限 historyDebounce: 400, // 历史防抖延迟(ms) syncScroll: true, // 分屏模式同步滚动 tabSize: 2, // 制表符空格数(0 表示 \t) // 主题与国际化 theme: 'auto', // 'light' | 'dark' | 'auto' | 'warm' | 自定义 locale: 'zh-CN', // 'zh-CN' | 'en-US' | 自定义 // 自定义渲染 render: null, // (md, env) => html highlight: null, // (code, lang) => html sanitize: null, // (html) => safeHtml // 外观 className: '', // 容器额外 class style: {}, // 内联样式 // 插件与回调 plugins: [], // 实例级插件数组 onChange: null, // (value, editor) => void onInput: null, // (value, editor) => void onFocus: null, // (editor) => void onBlur: null, // (editor) => void onSave: null, // (value, editor) => void onModeChange: null, // (mode, editor) => void onFullscreen: null, // (fullscreen, editor) => void onCreate: null, // (editor) => void onDestroy: null, // (editor) => void });
工具栏配置
// 默认工具栏(完整按钮) MeEditor.create(container, { toolbar: true }); // 隐藏工具栏 MeEditor.create(container, { toolbar: false }); // 自定义工具栏 — '|' 为分隔符 MeEditor.create(container, { toolbar: ['bold', 'italic', '|', 'h1', 'h2', '|', 'undo', 'redo'], });
可用动作:
bolditalicstrikethroughunderlinecode h1h2h3quoteulol indentoutdenthr linkimagetable undoredo editsplitpreviewfullscreen
实例 API
内容操作
editor.getValue(); // 获取 Markdown 文本 editor.setValue(md, { silent }); // 设置内容,silent 不触发 change editor.getHTML(); // 获取渲染后的 HTML editor.refresh(); // 强制刷新预览(内容未变时) editor.insert(text, { replace }); // 光标处插入 / 替换选区 editor.wrap(before, after); // 选区包裹 editor.focus(); // 聚焦 editor.blur(); // 失焦
命令执行
editor.exec(action, ...args); // 执行命令,返回 this 链式调用 editor.exec('bold').exec('h1'); // 链式调用
| action | 效果 | 快捷键 |
|---|---|---|
bold | 加粗 **text** | Ctrl+B |
italic | 斜体 *text* | Ctrl+I |
strikethrough | 删除线 ~~text~~ | — |
underline | 下划线 <u>text</u> | Ctrl+U |
code | 行内代码 `text` | Ctrl+E |
h1/h2/h3 | 标题 # / ## / ### | Ctrl+1/2/3 |
quote | 引用 > | Ctrl+Q |
ul/ol | 无序/有序列表 | — |
indent/outdent | 缩进/反缩进 | Tab/Shift+Tab |
hr | 水平线 --- | — |
link | 插入链接 [text](url) | Ctrl+K |
image | 插入图片  | — |
table | 插入表格 | — |
undo/redo | 撤销/重做 | Ctrl+Z / Ctrl+Y |
edit/split/preview | 切换模式 | — |
fullscreen | 切换全屏 | — |
历史栈
editor.undo(); // 撤销 editor.redo(); // 重做 editor.canUndo(); // 是否可撤销 editor.canRedo(); // 是否可重做
模式与全屏
editor.setMode('split'); // 设置模式:edit | split | preview editor.getMode(); // 获取当前模式 editor.toggleFullscreen(); // 切换全屏 editor.exitFullscreen(); // 退出全屏 editor.isFullscreen(); // 是否全屏
统计与状态
editor.getStats(); // => { characters, words, chineseChars, englishWords, lines, readingTime } editor.getStatus(); // => { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }
启用/禁用/只读
editor.enable(); // 启用 editor.disable(); // 禁用 editor.isDisabled(); // 是否禁用 editor.setReadOnly(true); // 只读模式 editor.isReadOnly(); // 是否只读
事件系统
| 事件名 | 触发时机 | 回调参数 |
|---|---|---|
input | textarea 原生 input | (value, editor) |
change | 内容变化 | (value, editor) |
focus | 聚焦 | (editor) |
blur | 失焦 | (editor) |
save | Ctrl+S | (value, editor) |
modeChange | 模式切换 | (mode, editor) |
fullscreen | 全屏切换 | (fullscreen, editor) |
destroy | 销毁 | () |
autosave | autoSave 保存 | ({ key, value }) |
beforeRender | 渲染前 | (editor) |
afterRender | 渲染后 | (editor) |
全局钩子(所有实例共享)
| 钩子 | 触发时机 |
|---|---|
beforeCreate | 构造函数初始化前 |
afterCreate | 构造函数初始化完成 |
beforeRender | 每次渲染前(全局) |
afterRender | 每次渲染后(全局) |
beforeDestroy | destroy 前 |
afterDestroy | destroy 后 |
插件管理
editor.use(plugin, options); // 安装插件 editor.getPlugins(); // 获取已安装插件列表 editor.addToolbarButton(config); // 追加工具栏按钮
生命周期
editor.destroy(); // 销毁实例 editor.isDestroyed(); // 是否已销毁
静态 API
import MeEditor from '@metona-team/metona-editor'; // 工厂函数 MeEditor.create(container, options); // 全局默认插件 MeEditor.use(presetPlugins.autoSave, { delay: 2000 }); MeEditor.use('searchReplace'); MeEditor.use(customPlugin); // 全局事件钩子 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, safeUrl, slugify } from '@metona-team/metona-editor'; parseMarkdown('# Hello'); // => '<h1 id="hello">Hello</h1>' safeUrl('javascript:alert(1)'); // => '' slugify('Hello World'); // => 'hello-world'
解析器语法参考
| 语法 | 示例 | 输出 |
|---|---|---|
| 标题 | # H1 ~ ###### H6 | <h1> ~ <h6> |
| 段落 | 纯文本 | <p> |
| 粗体 | **bold** __bold__ | <strong> |
| 斜体 | *italic* _italic_ | <em> |
| 删除线 | ~~text~~ | <del> |
| 高亮标记 | ==text== | <mark> |
| 上标 | x^2^ | <sup> |
| 下标 | H~2~O | <sub> |
| 行内代码 | `code` | <code> |
| 代码块 | ```lang | <pre><code> |
| 引用 | > quote | <blockquote> |
| 无序列表 | - item | <ul><li> |
| 有序列表 | 1. item | <ol><li> |
| 任务列表 | - [x] done | <li class="me-task-item"> |
| 水平线 | --- *** ___ | <hr> |
| 表格 | | a | b | | <table>(支持 :---: 列对齐) |
| 链接 | [text](url) | <a> |
| 图片 |  | <img> |
| 自动链接 | <https://...> | <a> |
| Emoji | :smile: :rocket: | 😊 🚀(80+ 常用) |
XSS 防护
所有文本经 HTML 转义。URL 自动过滤 javascript: vbscript: file: 及非图片 data: 协议。
插件约定
const myPlugin = { name: 'myPlugin', description: '我的自定义插件', install(editor, options) { // this 指向插件对象本身 this._timer = null; editor.on('change', this._onChange); }, destroy(editor) { if (this._timer) clearTimeout(this._timer); editor.off('change', this._onChange); }, };
预设插件
autoSave — 自动保存草稿
editor.use(presetPlugins.autoSave, { key: 'me-draft-' + editor.id, // localStorage key delay: 1000, // 防抖延迟(ms) }); editor.restoreDraft(); // 恢复草稿 editor.clearDraft(); // 清除草稿 editor.getDraftKey(); // 获取存储 key
exportTool — 导出文件
editor.use(presetPlugins.exportTool); editor.exportMarkdown('my-doc.md'); editor.exportHTML('my-doc.html', { title: '文档标题', css: 'body { font-family: sans-serif; }', lang: 'zh-CN', });
searchReplace — 查找替换
editor.use(presetPlugins.searchReplace); // Ctrl+F → 查找 Ctrl+H → 替换 // Enter → 下一个 Shift+Enter → 上一个 Escape → 关闭
imagePaste — 粘贴图片转 base64
editor.use(presetPlugins.imagePaste); // 在编辑区 Ctrl+V 粘贴剪贴板图片 // 自动插入 
主题系统
// 切换主题 MeEditor.setTheme('light'); // 亮色 MeEditor.setTheme('dark'); // 暗色 MeEditor.setTheme('warm'); // 暖色 MeEditor.setTheme('auto'); // 自动(默认) // 注册自定义主题 import { themeUtils } from '@metona-team/metona-editor'; themeUtils.registerTheme('ocean', { bg: '#001122', text: '#aabbcc', accent: '#00ddff', }); themeUtils.applyTheme('ocean');
CSS 变量:主题变量限定在 .me-wrapper 元素上,不污染全局样式。支持 @media print 打印样式。
国际化
// 切换语言 MeEditor.setLocale('en-US'); MeEditor.setLocale('zh-CN'); // 翻译函数 import { i18nUtils } from '@metona-team/metona-editor'; i18nUtils.t('bold'); // => '粗体'(zh-CN) i18nUtils.t('bold'); // => 'Bold'(en-US) // 添加语言 i18nUtils.addTranslations('ja', { bold: '太字', italic: '斜体' }); // 格式化 i18nUtils.formatNumber(1234567); // => '1,234,567' i18nUtils.formatCurrency(99.99, 'USD'); // => '$99.99' i18nUtils.formatDate('2024-01-15'); // => '1/15/2024'