安装与引入
# npm 安装 npm install @metona-team/metona-editor // TypeScript / 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: '', placeholder: '', id: '', // 实例 id(默认自动生成,隔离存储键) // 视图 mode: 'split', height: 400, toolbar: DEFAULT_TOOLBAR, wordCount: true, lineNumbers: true, outline: false, // 行为 autofocus: false, spellcheck: false, readOnly: false, tabSize: 2, historyLimit: 100, historyDebounce: 400, syncScroll: true, autoBrackets: true, zenMode: false, wordWrap: true, maxLength: 0, // 最大字符数(0=不限) floatingToolbar: true, // 选中文本浮动格式栏 // 主题 / 语言 theme: 'auto', locale: 'zh-CN', // 钩子 render: null, highlight: null, sanitize: null, // 外观 className: '', style: {}, // 插件 / 回调 plugins: [], onChange: null, onInput: null, onFocus: null, onBlur: null, onSave: null, onModeChange: null, onFullscreen: null, onCreate: null, onDestroy: null, onLinkClick: null, });
工具栏
// 默认完整工具栏 MeEditor.create(container, { toolbar: true }); // 隐藏 MeEditor.create(container, { toolbar: false }); // 自定义 | 为分隔符 MeEditor.create(container, { toolbar: ['bold', 'italic', '|', 'undo', 'redo'] });
可用动作:
bolditalicstrikethroughunderlinecode h1h2h3quoteulol indentoutdenthr linkimagetable undoredo editsplitpreviewfullscreen
实例 API
内容操作
editor.getValue(); // => string editor.setValue(md, { silent }); // => this editor.getHTML(); // => string editor.refresh(); // 强制重渲染 editor.insert(text, { replace }); // => this editor.wrap(before, after); // => this editor.replaceAll(search, replace, caseSensitive) // => 替换数 editor.replaceAllRegex(pattern, replace) // => 替换数 editor.lineCount(); getLine(n); editor.copyAsMarkdown(); copyAsHTML(); // 剪贴板 editor.focus(); blur();
光标与选区
editor.getSelectedText(); // => string editor.getCursorPosition(); // => { line, column } editor.setCursorPosition(line, column); // => this editor.scrollToLine(line); // => this editor.selectLine(line); // => this editor.selectAll(); // => this
伴随事件 selectionChange({ start, end, text })与 cursorMove({ line, column })。
命令执行
editor.exec(action) // => this, 支持链式
| 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 | > text | Ctrl+Q |
ul/ol | - / 1. 列表 | — |
link | [text](url) | Ctrl+K |
image |  | — |
table | 插入表格 | — |
formatTable v0.2.2 | 光标所在表格按列宽对齐 | — |
hr | ---水平线 | — |
indent/outdent | 缩进/反缩进 | Tab / Shift+Tab |
undo/redo | 撤销/重做 | Ctrl+Z / Y |
edit/split/preview | 切换模式 | — |
fullscreen | 全屏 | — |
zen | Zen 模式 | — |
wordwrap | 自动换行 | — |
历史栈
editor.undo(); editor.redo(); editor.canUndo(); editor.canRedo();
模式 / 全屏 / Zen / 浮动工具栏
editor.setMode('split'); editor.getMode(); editor.toggleFullscreen(); isFullscreen(); exitFullscreen(); editor.toggleZen(); isZen(); // 或配置 zenMode: true 启动即进入 editor.toggleWordWrap(); setWordWrap(true); isWordWrap(); editor.toggleFloatingToolbar(); isFloatingToolbar(); // 选中文本格式栏
统计与状态
editor.getStats(); // => { characters, words, chineseChars, englishWords, lines, readingTime } editor.getStatus(); // => { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }
启用 / 禁用 / 只读
editor.enable(); disable(); isDisabled(); editor.setReadOnly(true); isReadOnly();
事件系统
| 事件 | 触发时机 | 参数 |
|---|---|---|
input | textarea 输入 | (value, editor) |
change | 内容变化 | (value, editor) |
beforeChange / afterChange | 内容变更前后 | (oldValue, newValue, editor) |
focus / blur | 聚焦/失焦 | (editor) |
save | Ctrl+S | (editor) |
modeChange | 模式切换 | (mode, editor) |
fullscreen | 全屏切换 | (fullscreen, editor) |
themeChange | 主题变更 | ({ theme, resolved, config }) |
localeChange | 语言变更 | ({ locale, direction }) |
zenChange | Zen 切换 | (zen, editor) |
selectionChange | 选区变化 | ({ start, end, text }) |
cursorMove | 光标移动 | ({ line, column }) |
destroy | 销毁 | () |
beforeRender / afterRender | 渲染前后 | (editor) |
autosave | autoSave 触发 | ({ key, value }) |
linkClick | 预览链接点击 | ({ href, text }) |
copy | 复制到剪贴板 | ({ type }) |
fileOpened / fileSaved | fileSystem 读写 | ({ name, handle }) |
全局钩子
| 钩子 | 触发时机 |
|---|---|
beforeCreate / afterCreate | 实例构造前后 |
beforeRender / afterRender | 全局渲染前后 |
beforeDestroy / afterDestroy | 销毁前后 |
插件管理
editor.use(plugin, options); // 安装 editor.unuse(name); // 卸载 editor.getPlugins(); // 已安装列表 editor.addToolbarButton(config); // 追加按钮
快捷键管理
editor.registerShortcut('Ctrl+Shift+T', handler, 'desc'); editor.unregisterShortcut('Ctrl+Shift+T'); editor.getShortcuts();
生命周期 / Toast
editor.destroy(); isDestroyed(); editor.toast(msg, { type, duration, animation });
toast 动画支持 fade / slide / scale / bounce / flip / rotate / zoom 七种,duration: 0 不自动消失。
静态 API
import MeEditor from '@metona-team/metona-editor'; // 工厂函数 MeEditor.create(container, options); // 全局默认插件 MeEditor.use('autoSave', { delay: 2000 }); MeEditor.use(customPlugin); // 全局钩子 MeEditor.on('beforeCreate', (editor) => {}); MeEditor.off('beforeCreate', handler); // 全局主题 / 语言 MeEditor.setTheme('dark'); MeEditor.setLocale('en-US'); // 解析器独立使用 import { parseMarkdown, parseTokens, renderTokens } from '@metona-team/metona-editor'; // 内置语法高亮 import { highlight, registerLanguage } from '@metona-team/metona-editor';
内置语法高亮
v0.2.5 起内置零依赖轻量高亮器,支持 js / ts / tsx / jsx / python / bash / css / html / json / yaml / markdown / java / go / rust(含 shell / md 别名共 16 个语言标识)。
import MeEditor from '@metona-team/metona-editor'; // 方式一:作为 highlight 钩子(代码块自动高亮) MeEditor.create('#editor', { highlight: MeEditor.highlight }); // 方式二:独立调用 const html = MeEditor.highlight(code, 'typescript'); // 注册自定义语言 MeEditor.registerLanguage('myLang', { keywords: ['kw'], builtins: [] });
高亮输出使用 me-hl-keyword / me-hl-string / me-hl-comment / me-hl-number / me-hl-builtin / me-hl-function 类。内置浅色 / 深色自适应配色,可通过 CSS 覆盖定制:
/* 自定义配色(覆盖内置默认) */ .me-preview .me-hl-keyword{ color: #a855f7 } .me-preview .me-hl-string{ color: #22c55e }
解析器语法参考
| 语法 | 示例 | 输出 |
|---|---|---|
| 标题 | # H1 ~ ###### H6 | <h1> ~ <h6> |
| Setext 标题 | H1\n=== | <h1> / <h2> |
| 粗体 | **bold** __bold__ | <strong> |
| 粗斜体 v0.1.15 | ***text*** ___text___ | <em><strong> |
| 斜体 | *italic* _italic_ | <em> |
| 删除线 / 高亮 | ~~del~~ ==mark== | <del> / <mark> |
| 下划线 v0.3.0 | <u>text</u>(裸标签透传) | <u> |
| 上标 / 下标 | x^2^ H~2~O | <sup> / <sub> |
| 行内代码 | `code` | <code> |
| 代码块 | ```lang | <pre><code> |
| 缩进代码块 | code | <pre><code> |
| 引用 | > quote | <blockquote> |
| 无序列表 | - * + item | <ul><li> |
| 有序列表 | 1. item | <ol><li> |
| 嵌套列表 | 多级缩进 | 多级 <ul>/<ol> |
| 任务列表 | - [x] done | <li class="me-task-item"> |
| 水平线 | --- *** ___ | <hr> |
| 表格 | | a | b | | <table> + 列对齐 |
| 表格格式化 v0.2.2 | exec('formatTable') | 列宽对齐 |
| 链接 v0.1.15 | [**t**](url) | 文本内支持行内格式 |
| 图片 |  | <img> |
| 引用链接 v0.2.2 | [text][ref] + [ref]: url | <a> 引用式 |
| 引用图片 v0.2.2 | ![alt][ref] + [ref]: url | <img> 引用式 |
| 自动链接 | <https://...> | <a> |
| 数学公式 | $E=mc^2$ | <span> / <div> |
| 脚注 | text[^1] | <sup> + 底部 |
| 定义列表 | Term\n: def | <dl><dt><dd> |
| Emoji | :smile: :rocket: | 😊 🚀(150+) |
| 反斜杠转义 v0.1.15 | \* \_ | 取消标点特殊含义 |
XSS 防护
所有文本经 escapeHTML 转义,URL 过滤 javascript: / vbscript: / file: / 非图片 data:,属性级注入防护(href / src / alt / title / 脚注 id 引号转义),白名单裸标签 <u> 透传,提供 sanitize 钩子。
解析器 API
parseTokens / renderTokens
import { parseTokens, renderTokens } from '@metona-team/metona-editor'; const { tokens, footnotes } = parseTokens('# Hello\n\n**world**'); tokens.unshift({ type: 'hr' }); // 渲染前变换 const html = renderTokens(tokens, env, footnotes);
registerBlockHandler
import { registerBlockHandler } from '@metona-team/metona-editor'; registerBlockHandler({ name: 'custom', priority: 8.5, test: (line) => line.match(/^:::/), parse: (lines, i, match) => ({ token: { type: 'alert' }, newIndex: j }) });
插件约定
const myPlugin = { name: 'myPlugin', description: '我的插件', install(editor) { editor.on('change', this._onChange); }, destroy(editor) { editor.off('change', this._onChange); }, };
预设插件
autoSave
editor.use('autoSave', { key: 'draft-key', delay: 1000 }); editor.restoreDraft(); editor.clearDraft();
exportTool
editor.use('exportTool'); editor.exportMarkdown('doc.md'); editor.exportHTML('doc.html'); editor.exportPDF();
searchReplace
editor.use('searchReplace'); // Ctrl+F 查找 / Ctrl+H 替换,支持正则 .* 、大小写 Aa、全字 ab 开关
imagePaste
editor.use('imagePaste', { maxSizeKB: 500 }); // 粘贴图片转 base64,超限警告
shortcutHelp
editor.use('shortcutHelp'); // 按 ? 弹出快捷键面板
fileSystem
editor.use('fileSystem'); editor.openFile(); editor.saveFile(); editor.saveFileAs();
主题系统
// 实例级 editor.setTheme('dark'); editor.getTheme(); // 注册自定义 import { themeUtils } from '@metona-team/metona-editor'; themeUtils.registerTheme('ocean', { extends: 'dark', accent: '#00ddff' }); // 外部跟随 editor.getThemeContext().adopt(); // 从父容器继承 editor.getThemeContext().syncWithElement(document.body()); // 跟随外部元素 editor.getThemeContext().dispose(); // 手动断开跟随(destroy 时自动断开) // CSS 变量导出 themeUtils.exportCSSVars(); themeUtils.getCSSVariable('accent');
国际化
// 切换 editor.setLocale('en-US'); MeEditor.setLocale('zh-CN'); // 添加翻译 import { i18nUtils } from '@metona-team/metona-editor'; i18nUtils.addTranslations('ja', { bold: '太字' }); // 远程加载 await i18nUtils.loadRemote('/locales/ja.json', 'ja'); // 格式化 i18nUtils.formatNumber(1234567); i18nUtils.formatDate('2024-01-15');