- 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
689 lines
18 KiB
Markdown
689 lines
18 KiB
Markdown
# MetonaEditor
|
||
|
||
> TypeScript 重构 · 零运行时依赖 · 轻量级桌面端 Markdown Editor 库
|
||
|
||
[](https://git.metona.cn/MetonaTeam/-/packages/npm/@metona-team%2Fmetona-editor)
|
||
[](./LICENSE)
|
||
[](./tests)
|
||
[](./tests)
|
||
[](./tsconfig.json)
|
||
|
||
## 特性
|
||
|
||
- **TypeScript 源码** — 全模块 TypeScript 严格模式,完整类型导出,IDE 智能提示
|
||
- **零运行时依赖** — 打包后单文件,无任何第三方库,UMD / ESM / CJS 三种格式
|
||
- **自研解析器** — CommonMark + GFM 扩展,96% 语句覆盖率,parseTokens / renderTokens 分离 API,registerBlockHandler 自定义块语法
|
||
- **插件系统 v2** — 拓扑排序依赖管理,6 个预设插件开箱即用,安装 / 卸载生命周期
|
||
- **三模式视图** — edit / split / preview,拖拽分隔条调整比例,双向滚动同步
|
||
- **主题系统** — light / dark / warm / auto,CSS 变量可定制,实例级隔离,主题继承,外部跟随
|
||
- **国际化** — zh-CN / en-US 完整翻译,实例级语言隔离,远程加载翻译包
|
||
- **编辑体验** — 行号装订线、智能 Enter、括号自动闭合、拖放文件、大纲面板、Zen 专注模式
|
||
- **安全** — HTML 转义,XSS 协议过滤(javascript / vbscript / file / data),sanitize 钩子
|
||
- **桌面端优先** — 纯电脑端设计,无移动端冗余代码
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [快速开始](#快速开始)
|
||
- [配置项](#配置项)
|
||
- [工具栏](#工具栏)
|
||
- [实例 API](#实例-api)
|
||
- [静态 API](#静态-api)
|
||
- [事件系统](#事件系统)
|
||
- [Markdown 解析器](#markdown-解析器)
|
||
- [插件系统](#插件系统)
|
||
- [主题系统](#主题系统)
|
||
- [国际化](#国际化)
|
||
- [构建与测试](#构建与测试)
|
||
- [License](#license)
|
||
|
||
---
|
||
|
||
## 快速开始
|
||
|
||
### npm 安装
|
||
|
||
```bash
|
||
npm install @metona-team/metona-editor
|
||
```
|
||
|
||
```typescript
|
||
import MeEditor from '@metona-team/metona-editor';
|
||
|
||
const editor = MeEditor.create('#editor', {
|
||
value: '# Hello World',
|
||
mode: 'split',
|
||
plugins: ['autoSave', 'searchReplace'],
|
||
});
|
||
```
|
||
|
||
### CDN 引入
|
||
|
||
```html
|
||
<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>
|
||
```
|
||
|
||
---
|
||
|
||
## 配置项
|
||
|
||
```typescript
|
||
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
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 工具栏
|
||
|
||
### 默认工具栏
|
||
|
||
```typescript
|
||
['bold','italic','strikethrough','code','|',
|
||
'h1','h2','h3','|',
|
||
'quote','ul','ol','|',
|
||
'link','image','table','hr','|',
|
||
'undo','redo','|',
|
||
'edit','split','preview','fullscreen']
|
||
```
|
||
|
||
### 自定义工具栏
|
||
|
||
```typescript
|
||
// 精简版
|
||
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
|
||
|
||
### 内容操作
|
||
|
||
```typescript
|
||
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
|
||
```
|
||
|
||
### 命令执行
|
||
|
||
```typescript
|
||
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
|
||
```
|
||
|
||
### 历史栈
|
||
|
||
```typescript
|
||
editor.undo(): this
|
||
editor.redo(): this
|
||
editor.canUndo(): boolean
|
||
editor.canRedo(): boolean
|
||
```
|
||
|
||
### 模式与全屏
|
||
|
||
```typescript
|
||
editor.setMode(mode: 'edit' | 'split' | 'preview'): this
|
||
editor.getMode(): EditMode
|
||
editor.toggleFullscreen(): this
|
||
editor.isFullscreen(): boolean
|
||
editor.exitFullscreen(): this
|
||
```
|
||
|
||
### 主题与语言
|
||
|
||
```typescript
|
||
editor.setTheme(theme: string): this
|
||
editor.getTheme(): string
|
||
editor.setLocale(locale: string): this
|
||
editor.getLocale(): string
|
||
editor.t(key: string, params?: object): string
|
||
```
|
||
|
||
### 统计与状态
|
||
|
||
```typescript
|
||
editor.getStats(): { characters, words, chineseChars, englishWords, lines, readingTime }
|
||
editor.getStatus(): { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }
|
||
```
|
||
|
||
### 启用 / 禁用 / 只读
|
||
|
||
```typescript
|
||
editor.enable(): this
|
||
editor.disable(): this
|
||
editor.isDisabled(): boolean
|
||
editor.setReadOnly(readOnly: boolean): this
|
||
editor.isReadOnly(): boolean
|
||
```
|
||
|
||
### 事件
|
||
|
||
```typescript
|
||
editor.on(name: string, fn: (...args: any[]) => void): () => void
|
||
editor.off(name: string, fn: (...args: any[]) => void): this
|
||
```
|
||
|
||
### 插件
|
||
|
||
```typescript
|
||
editor.use(plugin: string | PluginObject, options?: object): this
|
||
editor.unuse(name: string): this
|
||
editor.getPlugins(): PluginObject[]
|
||
editor.addToolbarButton(config: ToolbarButtonConfig): this
|
||
```
|
||
|
||
### 快捷键
|
||
|
||
```typescript
|
||
editor.registerShortcut(combo: string, handler: Function | string, description?: string): this
|
||
editor.unregisterShortcut(combo: string): this
|
||
editor.getShortcuts(): Shortcut[]
|
||
```
|
||
|
||
### 工具栏管理
|
||
|
||
```typescript
|
||
editor.configureToolbar(tools: string[]): this
|
||
editor.removeToolbarButton(action: string): this
|
||
```
|
||
|
||
### Zen / Word Wrap
|
||
|
||
```typescript
|
||
editor.toggleZen(): this
|
||
editor.isZen(): boolean
|
||
editor.toggleWordWrap(): this
|
||
editor.setWordWrap(on: boolean): this
|
||
editor.isWordWrap(): boolean
|
||
```
|
||
|
||
### Toast
|
||
|
||
```typescript
|
||
editor.toast(message: string, opts?: {
|
||
type?: 'success' | 'error' | 'warning' | 'info';
|
||
duration?: number;
|
||
animation?: 'fade' | 'slide' | 'scale';
|
||
}): this
|
||
```
|
||
|
||
### 销毁
|
||
|
||
```typescript
|
||
editor.destroy(): void
|
||
editor.isDestroyed(): boolean
|
||
```
|
||
|
||
---
|
||
|
||
## 静态 API
|
||
|
||
```typescript
|
||
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>` |
|
||
| 图片 | `` | `<img>` |
|
||
| 自动链接 | `<https://...>` | `<a>` |
|
||
| 数学公式 | `$E=mc^2$` `$$\int$$` | `<span>` / `<div>` |
|
||
| 脚注 | `text[^1]` | `<sup>` + 底部定义 |
|
||
| 定义列表 | `Term\n: def` | `<dl><dt><dd>` |
|
||
| Emoji | `:smile:` `:rocket:` | 😊 🚀(150+) |
|
||
| 反斜杠转义 | `\*` `\_` `\\` | 取消标点特殊含义 |
|
||
| HTML 注释 | `<!-- note -->` | 透传 |
|
||
| 实体引用保护 | `&` `©` | 不被二次转义 |
|
||
|
||
### 链接内格式
|
||
|
||
```typescript
|
||
parseMarkdown('[**粗体链接**](https://example.com)')
|
||
// => <a href="..."><strong>粗体链接</strong></a>
|
||
```
|
||
|
||
### parseTokens / renderTokens
|
||
|
||
```typescript
|
||
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
|
||
|
||
```typescript
|
||
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` 钩子异常自动回退为纯文本
|
||
|
||
---
|
||
|
||
## 插件系统
|
||
|
||
### 自定义插件
|
||
|
||
```typescript
|
||
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
|
||
|
||
```typescript
|
||
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') // 预设副本
|
||
```
|
||
|
||
---
|
||
|
||
## 主题系统
|
||
|
||
### 内置主题
|
||
|
||
```typescript
|
||
editor.setTheme('light') // 亮色
|
||
editor.setTheme('dark') // 暗色
|
||
editor.setTheme('warm') // 暖色
|
||
editor.setTheme('auto') // 跟随系统(默认)
|
||
```
|
||
|
||
### CSS 变量定制
|
||
|
||
```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;
|
||
}
|
||
```
|
||
|
||
### 自定义主题
|
||
|
||
```typescript
|
||
import { themeUtils } from '@metona-team/metona-editor';
|
||
|
||
themeUtils.registerTheme('ocean', {
|
||
extends: 'dark', // 继承 dark 主题
|
||
bg: '#0a1628',
|
||
accent: '#38bdf8',
|
||
});
|
||
themeUtils.switchTheme('ocean');
|
||
```
|
||
|
||
### 外部主题跟随
|
||
|
||
```typescript
|
||
// 从父容器继承
|
||
editor.getThemeContext().adopt();
|
||
|
||
// 跟随外部元素
|
||
editor.getThemeContext().syncWithElement(document.body);
|
||
```
|
||
|
||
---
|
||
|
||
## 国际化
|
||
|
||
### 切换语言
|
||
|
||
```typescript
|
||
editor.setLocale('en-US') // 实例级
|
||
MeEditor.setLocale('zh-CN') // 全局
|
||
```
|
||
|
||
### 添加语言
|
||
|
||
```typescript
|
||
import { i18nUtils } from '@metona-team/metona-editor';
|
||
|
||
i18nUtils.addTranslations('ja', {
|
||
bold: '太字',
|
||
italic: '斜体',
|
||
});
|
||
```
|
||
|
||
### 远程加载翻译包
|
||
|
||
```typescript
|
||
await i18nUtils.loadRemote('/locales/ja.json', 'ja');
|
||
```
|
||
|
||
### 格式化工具
|
||
|
||
```typescript
|
||
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(列表/引用延续) |
|
||
|
||
---
|
||
|
||
## 构建与测试
|
||
|
||
```bash
|
||
# 安装依赖
|
||
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](./LICENSE) © MetonaTeam
|