Metona CN OpenSource Team
中国-四川-成都

@metona-team/metona-editor (0.2.5)

Published 2026-08-09 11:06:19 +08:00 by thzxx in MetonaTeam/MetonaEditor

Installation

@metona-team:registry=https://git.metona.cn/api/packages/MetonaTeam/npm/
npm install @metona-team/metona-editor@0.2.5
"@metona-team/metona-editor": "0.2.5"

About this package

Type-safe, lightweight, zero-dependency Markdown Editor. Desktop-first. React-free. Single-file bundle.

MetonaEditor

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

version license tests coverage types

特性

  • TypeScript 源码 — 全模块 TypeScript 严格模式,完整类型导出,IDE 智能提示
  • 零运行时依赖 — 打包后单文件,无任何第三方库,UMD / ESM / CJS 三种格式
  • 自研解析器 — CommonMark + GFM 扩展,99% 行覆盖率,parseTokens / renderTokens 分离 API,registerBlockHandler 自定义块语法
  • 插件系统 v2 — 拓扑排序依赖管理,6 个预设插件开箱即用,安装 / 卸载生命周期
  • 三模式视图 — edit / split / preview,拖拽分隔条调整比例,双向滚动同步
  • 主题系统 — light / dark / warm / auto,CSS 变量可定制,实例级隔离,主题继承,外部跟随
  • 国际化 — zh-CN / en-US / ja / ko / fr / de 六种语言完整翻译,实例级语言隔离,远程加载翻译包
  • 编辑体验 — 行号装订线、智能 Enter、括号自动闭合、拖放文件、大纲面板、Zen 专注模式、右键上下文菜单
  • 内置语法高亮 — 零依赖轻量高亮器,js/ts/python/bash/css/html 等 13 种语言,MeEditor.highlight 即插即用
  • 安全 — HTML 转义,XSS 协议过滤(javascript / vbscript / file / data),属性级注入防护,sanitize 钩子
  • 桌面端优先 — 纯电脑端设计,无移动端冗余代码
  • 引用链接 — 支持 [text][ref] / ![alt][ref] 引用式链接和图片,含 title 属性
  • RTL 支持 — 完整的从右到左布局适配(阿拉伯语、希伯来语等)
  • 正则搜索 — 查找替换面板支持正则表达式模式
  • 分隔条记忆 — 分屏比例自动保存到 localStorage

目录


快速开始

方式一:npm 安装(推荐)

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

const editor = MeEditor.create('#editor', {
  value: '# Hello World',
  mode: 'split',
  theme: 'auto',
  plugins: ['autoSave', 'searchReplace'],
});
// CommonJS
const MeEditor = require('@metona-team/metona-editor');

const editor = MeEditor.create('#editor', { mode: 'split' });

方式二:CDN 引入

<!-- jsDelivr CDN (推荐) -->
<script src="https://cdn.jsdelivr.net/npm/@metona-team/metona-editor@0.2.5/dist/metona-editor.min.js"></script>

<!-- unpkg CDN -->
<script src="https://unpkg.com/@metona-team/metona-editor@0.2.5/dist/metona-editor.min.js"></script>

<!-- Gitea 源 -->
<script src="https://git.metona.cn/MetonaTeam/MetonaEditor/raw/branch/master/dist/metona-editor.js"></script>

<div id="editor"></div>
<script>
  const editor = MeEditor.create('#editor', { mode: 'split' });
</script>

方式三:本地文件

# 下载产物文件到项目中
# dist/metona-editor.js     → UMD (浏览器)
# dist/metona-editor.min.js → UMD 压缩
# dist/metona-editor.mjs → ES Module
# dist/metona-editor.cjs → CommonJS
<!-- 浏览器直接引入本地文件 -->
<script src="./dist/metona-editor.min.js"></script>
// Node.js ESM
import MeEditor from './dist/metona-editor.mjs';
// Node.js CJS
const MeEditor = require('./dist/metona-editor.cjs');

配置项

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=不限)
  floatingToolbar: true, // 选中文本浮动格式栏

  // 主题与语言
  theme: 'auto',        // 'light' | 'dark' | 'warm' | 'auto'
  locale: 'zh-CN',      // 'zh-CN' | 'en-US' | 'ja' | 'ko' | 'fr' | 'de'

  // 渲染钩子
  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.replaceAll(search: string, replace: string, caseSensitive?: boolean): number
editor.replaceAllRegex(pattern: RegExp, replace: string): number
editor.lineCount(): number
editor.getLine(line: number): string
editor.copyAsMarkdown(): this                // 复制源码到剪贴板
editor.copyAsHTML(): this                    // 复制渲染 HTML 到剪贴板

光标与选区

editor.getSelectedText(): string
editor.getCursorPosition(): { line: number; column: number }
editor.setCursorPosition(line: number, column?: number): this
editor.scrollToLine(line: number): this
editor.selectLine(line: number): this
editor.selectAll(): this

浮动工具栏

editor.toggleFloatingToolbar(): this  // 开关选中文本格式栏
editor.isFloatingToolbar(): boolean

命令执行

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

Copy API

editor.copyAsMarkdown(): this   // 复制 Markdown 源码到剪贴板
editor.copyAsHTML(): this       // 复制渲染后的 HTML 到剪贴板

销毁

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

// 内置语法高亮
import { highlight, registerLanguage, getSupportedLanguages } 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 })
beforeChange 内容变更前 (oldValue, newValue, editor)
afterChange 内容变更后 (newValue, editor)
copy 复制到剪贴板 ({ type })
selectionChange 选区变化 ({ start, end, text })
cursorMove 光标移动 ({ line, column })

全局钩子

钩子 触发时机
beforeCreate 实例构造初始化前
afterCreate 实例构造初始化完成
beforeRender 每次渲染前(全局)
afterRender 每次渲染后(全局)
beforeDestroy destroy 前
afterDestroy destroy 后
beforeChange 内容变更前(全局)
afterChange 内容变更后(全局)

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>
引用链接 [text][ref] + [ref]: url <a>
引用图片 ![alt][ref] + [ref]: 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:
  • 属性级注入防护:href / src / alt / title / language-* 引号与换行均被转义
  • 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 文件,支持 PDF(打印) editor.use('exportTool'); editor.exportMarkdown()
searchReplace Ctrl+F 查找 / Ctrl+H 替换,支持正则、大小写、全字匹配 editor.use('searchReplace')
imagePaste 粘贴剪贴板图片转 base64,可限尺寸 editor.use('imagePaste', { maxSizeKB: 500 })
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')   // 预设副本

语法高亮

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 覆盖定制。


主题系统

内置主题

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 build && npm run bench

# 代码格式化
npm run format

构建产物

dist/
├── metona-editor.js         UMD(浏览器直接引入)
├── metona-editor.min.js     UMD 压缩版(CDN)
├── metona-editor.mjs     ES Module
├── metona-editor.cjs     CommonJS
└── metona-editor.d.ts       TypeScript 类型声明

源码结构

src/
├── index.ts             入口 / 全局API
├── core.ts              MarkdownEditor 类(构造 / 事件 / 历史 / 模式)
├── commands.ts          编辑命令实现(包裹 / 前缀 / 插入 / 表格格式化)
├── floating-toolbar.ts  选中文本浮动格式栏
├── context-menu.ts      右键上下文菜单
├── outline.ts           大纲面板
├── parser.ts            自研 Markdown 解析器
├── highlight.ts         内置轻量语法高亮器
├── 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

Dependencies

Development Dependencies

ID Version
@babel/core ^7.22.0
@babel/plugin-transform-modules-commonjs ^7.22.0
@babel/preset-env ^7.22.0
@babel/preset-typescript ^7.22.0
@rollup/plugin-commonjs ^25.0.0
@rollup/plugin-node-resolve ^15.0.0
@rollup/plugin-terser ^0.4.0
@rollup/plugin-typescript ^11.1.0
@types/jest ^29.5.0
@typescript-eslint/eslint-plugin ^8.65.0
@typescript-eslint/parser ^8.65.0
babel-jest ^29.5.0
eslint ^8.40.0
jest ^29.5.0
jest-environment-jsdom ^29.5.0
prettier ^2.8.0
rollup ^3.20.0
rollup-plugin-dts ^5.3.0
rollup-plugin-livereload ^2.0.5
rollup-plugin-serve ^2.0.1
tslib ^2.6.0
typescript ^5.0.0

Keywords

markdown editor wysiwyg ui component lightweight zero-dependency typescript
Details
npm
2026-08-09 11:06:19 +08:00
1
thzxx
MIT
382 KiB
Assets (1)
Versions (26) View all
0.4.3 2026-08-20
0.4.2 2026-08-19
0.4.1 2026-08-09
0.4.0 2026-08-09
0.2.5 2026-08-09