安装与引入
+ +# 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'+