Files
MetonaEditor/README.md
T
thzxx e5f99aee83 fix: 复查修复 5 处问题 — URL 双重转义/zen 泄漏/高亮环境不一致等
- parser: href/src/alt 改最小属性转义(escapeAttr 会把已实体化的 &
  再转义成 &,导致链接 URL 双重转义错误);新增 URL/alt 防回归测试
- core: _pushHistory 增加 maxLength 兜底截断(exec/Tab/Smart Enter/括号
  自动闭合等程序化路径此前绕过 maxlength 限制)
- core: destroy 时清理 Zen 模式的 document mousemove 监听(此前泄漏)
- highlight: 自写环境无关转义(仅 & < >),修复 Node 环境下字符串高亮
  失效、浏览器与 SSR 输出不一致的问题
- floating-toolbar/context-menu: toggleFloatingToolbar/registerContextMenu
  恢复链式 return this(拆分时丢失)
- plugins: 补齐 3 个硬编码文案的 i18n(regex/shortcuts/imageTooLarge),
  6 种语言同步
- 测试 768 → 780
2026-08-09 09:26:20 +08:00

796 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MetonaEditor
> TypeScript 重构 · 零运行时依赖 · 轻量级桌面端 Markdown Editor 库
[![version](https://img.shields.io/badge/version-0.2.5-blue)](https://git.metona.cn/MetonaTeam/-/packages/npm/@metona-team%2Fmetona-editor)
[![license](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
[![tests](https://img.shields.io/badge/tests-768%20passed-brightgreen)](./tests)
[![coverage](https://img.shields.io/badge/coverage-parser%2099%25-brightgreen)](./tests)
[![types](https://img.shields.io/badge/types-TypeScript%20strict-blue)](./tsconfig.json)
## 特性
- **TypeScript 源码** — 全模块 TypeScript 严格模式,完整类型导出,IDE 智能提示
- **零运行时依赖** — 打包后单文件,无任何第三方库,UMD / ESM / CJS 三种格式
- **自研解析器** — CommonMark + GFM 扩展,96% 语句覆盖率,parseTokens / renderTokens 分离 APIregisterBlockHandler 自定义块语法
- **插件系统 v2** — 拓扑排序依赖管理,6 个预设插件开箱即用,安装 / 卸载生命周期
- **三模式视图** — edit / split / preview,拖拽分隔条调整比例,双向滚动同步
- **主题系统** — light / dark / warm / autoCSS 变量可定制,实例级隔离,主题继承,外部跟随
- **国际化** — zh-CN / en-US / ja / ko 完整翻译,实例级语言隔离,远程加载翻译包
- **编辑体验** — 行号装订线、智能 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
---
## 目录
- [快速开始](#快速开始)
- [配置项](#配置项)
- [工具栏](#工具栏)
- [实例 API](#实例-api)
- [静态 API](#静态-api)
- [事件系统](#事件系统)
- [Markdown 解析器](#markdown-解析器)
- [插件系统](#插件系统)
- [主题系统](#主题系统)
- [国际化](#国际化)
- [构建与测试](#构建与测试)
- [License](#license)
---
## 快速开始
### 方式一:npm 安装(推荐)
```bash
npm install @metona-team/metona-editor
```
```typescript
// 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'],
});
```
```javascript
// CommonJS
const MeEditor = require('@metona-team/metona-editor');
const editor = MeEditor.create('#editor', { mode: 'split' });
```
### 方式二:CDN 引入
```html
<!-- 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>
```
### 方式三:本地文件
```bash
# 下载产物文件到项目中
# dist/metona-editor.js → UMD (浏览器)
# dist/metona-editor.min.js → UMD 压缩
# dist/metona-editor.mjs → ES Module
# dist/metona-editor.cjs → CommonJS
```
```html
<!-- 浏览器直接引入本地文件 -->
<script src="./dist/metona-editor.min.js"></script>
```
```javascript
// Node.js ESM
import MeEditor from './dist/metona-editor.mjs';
// Node.js CJS
const MeEditor = require('./dist/metona-editor.cjs');
```
---
## 配置项
```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=不限)
floatingToolbar: true, // 选中文本浮动格式栏
// 主题与语言
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
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 到剪贴板
```
### 光标与选区
```typescript
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
```
### 浮动工具栏
```typescript
editor.toggleFloatingToolbar(): this // 开关选中文本格式栏
editor.isFloatingToolbar(): boolean
```
### 命令执行
```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
```
### Copy API
```typescript
editor.copyAsMarkdown(): this // 复制 Markdown 源码到剪贴板
editor.copyAsHTML(): this // 复制渲染后的 HTML 到剪贴板
```
### 销毁
```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';
// 内置语法高亮
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 })` |
### 全局钩子
| 钩子 | 触发时机 |
|------|---------|
| `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;` | 不被二次转义 |
### 链接内格式
```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') // 预设副本
```
---
## 语法高亮
v0.2.5 起内置零依赖轻量高亮器(js / ts / tsx / jsx / python / bash / css / html / json / yaml / markdown / java / go / rust)。
```typescript
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`
---
## 主题系统
### 内置主题
```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.mjs ES Module
├── metona-editor.cjs 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