release: v0.4.4 — 升级 MetonaEditor 0.2.4 & MetonaToast 0.2.1

依赖升级:
- @metona-team/metona-editor: 0.1.14 → 0.2.4
- @metona-team/metona-toast: 0.1.2 → 0.2.1(精确版本)

Editor.tsx 适配 v0.2.4:
- 移除 ExtendedEditorOptions 类型 hack(lineNumbers/autoBrackets 已原生支持)
- 本地声明 EditMode/ThemeName 类型(v0.2.4 不再命名导出)
- 适配 onModeChange 签名 (mode: EditMode → mode: string)
- 适配 onLinkClick 签名 ({href}) → (href, text, editor)
- 新增 syncScroll: true 同步滚动
- 新增 wordWrap: true 自动换行
- 新增 outline: false(保留自研 OutlinePanel)
- 新增 historyLimit: 100 / historyDebounce: 400
- 新增 shortcutHelp 预设插件(按 ? 弹出快捷键面板)
- 新增 onLinkClick 回调(通过 openExternal 安全打开链接)
- 新增 onFocus / onBlur 回调(预留扩展点)

toast.ts 适配 v0.2.1:
- 安装 keyboard 插件(ESC 关闭所有 Toast)
- 安装 accessibility 插件(屏幕阅读器朗读)
- 新增 width: 360 配置
This commit is contained in:
thzxx
2026-07-25 16:23:59 +08:00
parent 8bbbb7ff70
commit 7ff030f2d3
8 changed files with 531 additions and 940 deletions
+194 -440
View File
@@ -4,518 +4,272 @@
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📝</text></svg>"/>
<title>MetonaEditor v0.1.10 — 在线演示</title>
<title>MetonaEditor v0.2.4 — 全功能演示</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root {
--app-bg: #f5f6f8;
--app-text: #1a1a2e;
--app-card: #ffffff;
--app-border: #e5e7eb;
--app-accent: #3b82f6;
--app-accent2: #8b5cf6;
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{--app-bg:#f5f6f8;--app-text:#1a1a2e;--app-card:#fff;--app-border:#e5e7eb;--app-accent:#3b82f6;--app-accent2:#8b5cf6;--header-bg:linear-gradient(135deg,#1e293b 0%,#0f172a 100%);--header-text:#f1f5f9;--badge-bg:rgba(255,255,255,.12);--badge-text:#e2e8f0}
[data-md-theme=dark]{--app-bg:#0f1117;--app-text:#e2e8f0;--app-card:#1a1d27;--app-border:#2d3140;--header-bg:linear-gradient(135deg,#0a0d15 0%,#161923 100%)}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:var(--app-bg);color:var(--app-text);min-height:100vh;line-height:1.6;transition:background .3s,color .3s}
/* dark overrides */
--header-bg: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
--header-text: #f1f5f9;
--badge-bg: rgba(255,255,255,0.12);
--badge-text: #e2e8f0;
}
[data-md-theme="dark"] {
--app-bg: #0f1117;
--app-text: #e2e8f0;
--app-card: #1a1d27;
--app-border: #2d3140;
--header-bg: linear-gradient(135deg, #0a0d15 0%, #161923 100%);
}
.header{background:var(--header-bg);color:var(--header-text);padding:28px 24px 20px;text-align:center;position:relative;overflow:hidden}
.header::before{content:'';position:absolute;top:-60px;right:-60px;width:300px;height:300px;background:radial-gradient(circle,rgba(139,92,246,.3) 0%,transparent 70%);border-radius:50%}
.header h1{font-size:1.85rem;font-weight:800;letter-spacing:-.5px;position:relative}
.header h1 span.grad{background:linear-gradient(135deg,var(--app-accent),var(--app-accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.header .badges{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:12px;position:relative}
.header .badge{padding:4px 12px;border-radius:20px;font-size:13px;font-weight:500;background:var(--badge-bg);color:var(--badge-text);border:1px solid rgba(255,255,255,.1)}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--app-bg);
color: var(--app-text);
min-height: 100vh;
line-height: 1.6;
transition: background 0.3s, color 0.3s;
}
.toolbar-strip{display:flex;align-items:center;gap:8px;padding:8px 20px;background:var(--app-card);border-bottom:1px solid var(--app-border);flex-wrap:wrap;position:sticky;top:0;z-index:50;transition:background .3s}
.toolbar-strip label{font-size:12px;font-weight:500;color:var(--app-text);opacity:.7;white-space:nowrap}
.toolbar-strip select{padding:4px 8px;border:1px solid var(--app-border);border-radius:6px;font-size:12px;background:var(--app-bg);color:var(--app-text);cursor:pointer}
.toolbar-strip button{padding:5px 12px;border:1px solid var(--app-border);border-radius:6px;font-size:12px;font-weight:500;background:var(--app-card);color:var(--app-text);cursor:pointer;transition:all .15s;white-space:nowrap}
.toolbar-strip button:hover{background:var(--app-accent);color:#fff;border-color:var(--app-accent)}
.toolbar-strip button.on{background:var(--app-accent);color:#fff;border-color:var(--app-accent)}
.toolbar-strip .spacer{flex:1}
/* ====== Header ====== */
.header {
background: var(--header-bg);
color: var(--header-text);
padding: 32px 24px 24px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -60px; right: -60px;
width: 300px; height: 300px;
background: radial-gradient(circle, rgba(139,92,246,0.3) 0%, transparent 70%);
border-radius: 50%;
}
.header h1 {
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.5px;
position: relative;
}
.header h1 span.grad {
background: linear-gradient(135deg, var(--app-accent), var(--app-accent2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header .badges {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
margin-top: 14px;
position: relative;
}
.header .badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
background: var(--badge-bg);
color: var(--badge-text);
border: 1px solid rgba(255,255,255,0.1);
}
.editor-section{max-width:1280px;margin:20px auto;padding:0 20px}
.editor-card{border-radius:12px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.06);background:var(--app-card);border:1px solid var(--app-border);transition:.3s}
.editor-card:hover{box-shadow:0 8px 36px rgba(0,0,0,.1)}
/* ====== Toolbar Strip ====== */
.toolbar-strip {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 24px;
background: var(--app-card);
border-bottom: 1px solid var(--app-border);
flex-wrap: wrap;
position: sticky;
top: 0;
z-index: 50;
transition: background 0.3s;
}
.toolbar-strip label {
font-size: 13px;
font-weight: 500;
color: var(--app-text);
opacity: 0.7;
white-space: nowrap;
}
.toolbar-strip select {
padding: 5px 10px;
border: 1px solid var(--app-border);
border-radius: 6px;
font-size: 13px;
background: var(--app-bg);
color: var(--app-text);
cursor: pointer;
}
.toolbar-strip button {
padding: 6px 14px;
border: 1px solid var(--app-border);
border-radius: 6px;
font-size: 13px;
font-weight: 500;
background: var(--app-card);
color: var(--app-text);
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.toolbar-strip button:hover { background: var(--app-accent); color: #fff; border-color: var(--app-accent); }
.toolbar-strip button.accent { background: var(--app-accent); color: #fff; border-color: var(--app-accent); }
.toolbar-strip .spacer { flex: 1; }
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;max-width:1280px;margin:20px auto;padding:0 20px}
.info-card{background:var(--app-card);border:1px solid var(--app-border);border-radius:10px;padding:16px 18px;transition:.3s}
.info-card:hover{box-shadow:0 4px 16px rgba(0,0,0,.08)}
.info-card .icon{font-size:22px;margin-bottom:6px}
.info-card h4{font-size:13px;font-weight:650;margin-bottom:3px}
.info-card p{font-size:12px;color:var(--app-text);opacity:.65;margin:0}
.toggle-group { display: flex; gap: 2px; }
.toggle-group button {
border-radius: 0;
margin: 0;
}
.toggle-group button:first-child { border-radius: 6px 0 0 6px; }
.toggle-group button:last-child { border-radius: 0 6px 6px 0; }
.toggle-group button.on { background: var(--app-accent); color: #fff; border-color: var(--app-accent); }
/* ====== Editor Container ====== */
.editor-section {
max-width: 1200px;
margin: 24px auto;
padding: 0 24px;
}
.editor-card {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 24px rgba(0,0,0,0.06);
background: var(--app-card);
border: 1px solid var(--app-border);
transition: box-shadow 0.3s, background 0.3s;
}
.editor-card:hover { box-shadow: 0 8px 36px rgba(0,0,0,0.1); }
/* ====== Info Cards ====== */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
max-width: 1200px;
margin: 24px auto;
padding: 0 24px;
}
.info-card {
background: var(--app-card);
border: 1px solid var(--app-border);
border-radius: 10px;
padding: 18px 20px;
transition: box-shadow 0.3s;
}
.info-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
.info-card .icon { font-size: 24px; margin-bottom: 8px; }
.info-card h4 { font-size: 14px; font-weight: 650; margin-bottom: 4px; }
.info-card p { font-size: 12px; color: var(--app-text); opacity: 0.65; margin: 0; }
/* ====== Footer ====== */
.footer {
text-align: center;
padding: 32px 24px;
font-size: 13px;
color: var(--app-text);
opacity: 0.5;
}
.footer a { color: var(--app-accent); text-decoration: none; }
/* ====== Responsive ====== */
@media (max-width: 768px) {
.header h1 { font-size: 1.5rem; }
.toolbar-strip { padding: 8px 12px; gap: 6px; }
.toolbar-strip button { padding: 5px 10px; font-size: 12px; }
.info-grid { grid-template-columns: 1fr 1fr; }
}
.footer{text-align:center;padding:28px 24px;font-size:13px;color:var(--app-text);opacity:.5}
.footer a{color:var(--app-accent);text-decoration:none}
@media(max-width:768px){.header h1{font-size:1.4rem}.toolbar-strip{padding:6px 10px;gap:4px}.toolbar-strip button{padding:4px 8px;font-size:11px}.info-grid{grid-template-columns:1fr 1fr}}
</style>
</head>
<body>
<header class="header">
<h1><span class="grad">Metona</span>Editor</h1>
<h1><span class="grad">Metona</span>Editor v0.2.4</h1>
<div class="badges">
<span class="badge">v0.1.10</span>
<span class="badge">零依赖</span>
<span class="badge">532 tests</span>
<span class="badge">MIT</span>
<span class="badge">中文优先</span>
<span class="badge">TypeScript</span>
<span class="badge">运行时依赖</span>
<span class="badge">725 tests</span>
<span class="badge">6 个插件</span>
<span class="badge">桌面端优先</span>
</div>
</header>
<div class="toolbar-strip" id="controlBar">
<a href="index.html" style="text-decoration:none;font-size:18px;margin-right:8px;" title="返回首页">🏠</a>
<div class="toolbar-strip" id="bar">
<a href="index.html" style="text-decoration:none;font-size:18px;margin-right:4px" title="首页">🏠</a>
<label>主题</label>
<select id="themeSelect">
<option value="auto">🌓 跟随系统</option>
<option value="light">☀️ 亮色</option>
<option value="dark">🌙 暗色</option>
<option value="warm">🔥 暖色</option>
</select>
<select id="selTheme"><option value="auto">🌓 跟随系统</option><option value="light">☀️ 亮色</option><option value="dark">🌙 暗色</option><option value="warm">🔥 暖色</option></select>
<label>语言</label>
<select id="localeSelect">
<option value="zh-CN">中文</option>
<option value="en-US">English</option>
</select>
<select id="selLocale"><option value="zh-CN">中文</option><option value="en-US">English</option><option value="ja">日本語</option><option value="ko">한국어</option><option value="fr">Français</option><option value="de">Deutsch</option></select>
<label>模式</label>
<div class="toggle-group" id="modeBtns">
<button data-mode="edit">编辑</button>
<button data-mode="split" class="on">分屏</button>
<button data-mode="preview">预览</button>
</div>
<button data-cmd="mode" data-arg="edit">编辑</button>
<button data-cmd="mode" data-arg="split" class="on">分屏</button>
<button data-cmd="mode" data-arg="preview">预览</button>
<div class="spacer"></div>
<label>行号</label>
<div class="toggle-group" id="lineNumBtns">
<button class="on"></button>
<button></button>
</div>
<label>大纲</label>
<div class="toggle-group" id="outlineBtns">
<button></button>
<button class="on"></button>
</div>
<button onclick="demoToast()" title="演示 Toast 通知">💬 Toast</button>
<button onclick="demoOutline()" title="生成大纲">📋 大纲</button>
<button data-cmd="zen" title="Zen 专注模式">🧘 Zen</button>
<button data-cmd="outline" title="大纲面板">📋 大纲</button>
<button data-cmd="wrap" title="切换换行">↩ 换行</button>
<button data-cmd="float" title="浮动工具栏" class="on">💬 浮动栏</button>
<button data-cmd="toast" title="Toast 通知">💬 Toast</button>
<button data-cmd="openFile" title="打开文件">📂 打开</button>
<button data-cmd="saveFile" title="保存文件">💾 保存</button>
</div>
<div class="editor-section">
<div class="editor-card">
<div id="editor"></div>
</div>
<div class="editor-card"><div id="editor"></div></div>
</div>
<div class="info-grid">
<div class="info-card">
<div class="icon">📝</div>
<h4>丰富语法</h4>
<p>数学公式、脚注、定义列表、嵌套列表、表格对齐、任务列表、Emoji</p>
</div>
<div class="info-card">
<div class="icon">⌨️</div>
<h4>快捷键</h4>
<p>Ctrl+B 粗体 · Ctrl+I 斜体 · Ctrl+S 保存 · Ctrl+F 搜索 · Ctrl+H 替换</p>
</div>
<div class="info-card">
<div class="icon">🎨</div>
<h4>4 套主题</h4>
<p>亮色 · 暗色 · 暖色 · 跟随系统,实例级隔离,外部主题跟随</p>
</div>
<div class="info-card">
<div class="icon">🔌</div>
<h4>4 个插件</h4>
<p>自动保存草稿 · 导出 MD/HTML · 搜索替换 · 图片粘贴</p>
</div>
<div class="info-card">
<div class="icon">🧩</div>
<h4>编辑增强</h4>
<p>行号装订线 · 智能 Enter · 括号自动闭合 · 拖放文件 · 大纲面板</p>
</div>
<div class="info-card">
<div class="icon">🛡️</div>
<h4>安全</h4>
<p>XSS 防护 · URL 协议过滤 · HTML 实体保护 · sanitize 钩子</p>
</div>
<div class="info-card"><div class="icon">🏗️</div><h4>TypeScript 全模块重构</h4><p>12 个 .ts 源文件,严格模式,完整类型导出</p></div>
<div class="info-card"><div class="icon">📝</div><h4>自研解析器 99% 行覆盖</h4><p>CommonMark + GFM:粗斜体、链接内格式、脚注、数学公式等</p></div>
<div class="info-card"><div class="icon">📂</div><h4>磁盘文件读写</h4><p>File System Access API 打开 / 保存真实 .md 文件</p></div>
<div class="info-card"><div class="icon">🔢</div><h4>行号装订线</h4><p>当前行高亮,与编辑区滚动同步</p></div>
<div class="info-card"><div class="icon">🧘</div><h4>Zen 专注模式</h4><p>工具栏自动隐藏,鼠标移到顶部滑入</p></div>
<div class="info-card"><div class="icon">⌨️</div><h4>快捷键面板</h4><p>按 ? 查看全部快捷键,Ctrl+F/H 搜索替换</p></div>
<div class="info-card"><div class="icon">📊</div><h4>Mermaid 图表</h4><p>```mermaid 代码块,加载 Mermaid.js 即可渲染</p></div>
<div class="info-card"><div class="icon">🔌</div><h4>6 个预设插件</h4><p>autoSave / exportTool / searchReplace / imagePaste / shortcutHelp / fileSystem</p></div>
</div>
<footer class="footer">
MetonaEditor v0.1.10 ·
<a href="index.html">首页</a> ·
<a href="docs.html">API 文档</a> ·
<a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> ·
MIT License
MetonaEditor v0.2.4 · TypeScript · <a href="index.html">首页</a> · <a href="docs.html">API 文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT
</footer>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script src="../dist/metona-editor.js"></script>
<script>
(function () {
const demoMd = [
'# 🚀 MetonaEditor 演示',
(function(){
var demoMd=[
'# 🚀 MetonaEditor v0.2.4 全功能演示',
'',
'> 轻量、零依赖、精致美观的 **Markdown Editor** 库。',
'> 单文件,开箱即用,中文优先。',
'> TypeScript 重构 · 零运行时依赖 · **桌面端** Markdown Editor 库。',
'> 全模块 TypeScript 严格模式,12 个源文件,684 个测试全部通过。',
'',
'## ✨ 核心特性',
'## ✨ v0.2.4 新特性',
'',
'- 零依赖 · 单文件打包 · UMD / ESM / CJS',
'- 三模式视图 · 分屏拖拽 · 滚动同步',
'- 行号装订线 · 当前行高亮',
'- 智能 Enter · 括号自动闭合',
'- 4 套主题 · 实例级隔离 · 外部跟随',
'- 插件系统 v2 · 依赖排序 · 异步加载',
'- 浮动格式工具栏:选中文本自动弹出 bold/italic/code/link',
'- 新增 `getSelectedText()` `getCursorPosition()` 等 10+ API',
'- `replaceAll` / `replaceAllRegex` 批量替换',
'- `selectionChange` / `cursorMove` 事件',
'- 德语(de)翻译 · 6 种语言支持',
'',
'## 📐 数学公式',
'## 📐 文本样式',
'',
'行内公式:爱因斯坦质能方程 $E = mc^2$',
'- **粗体** / __另一种粗体__',
'- ***粗斜体*** / ___另一种粗斜体___',
'- *斜体* / _另一种斜体_',
'- ~~删除线~~ / ==高亮标记==',
'- 链接内格式: [**粗体链接**](https://example.com)',
'- 化学:H~2~O / 数学:x^2^',
'- 代码:`const x = 42;`',
'- Emoji:smile: :rocket: :fire: :rainbow: :coffee:',
'',
'块级公式:',
'$$',
'\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}',
'$$',
'## 📊 数学公式 & 图表',
'',
'行内:$E = mc^2$',
'块级:$$\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$',
'',
'```mermaid',
'graph TD',
' A[TypeScript] --> B[Rollup]',
' B --> C[UMD/ESM/CJS]',
' C --> D[Browser]',
'```',
'',
'## 📋 表格与对齐',
'',
'| 版本 | 日期 | 测试 |',
'| :--- | :---: | ---: |',
'| v0.1.10 | 2026-07 | 532+ |',
'| v0.1.6 | 2026-07 | 521 |',
'| v0.1.5 | 2026-07 | 499 |',
'| 版本 | 日期 | 测试 | 主题 |',
'| :--- | :---: | ---: | --- |',
'| v0.2.4 | 2026-07 | 725 | 浮动工具栏+新API |',
'| v0.2.2 | 2026-07 | 694 | 法语+表格格式化 |',
'| v0.2.1 | 2026-07 | 684 | 引用链接+右键菜单 |',
'| v0.2.0 | 2026-07 | 610 | TypeScript 重构 |',
'| v0.1.15 | 2026-07 | 583 | 解析器增强 |',
'| v0.1.14 | 2026-07 | 535 | 语法高亮+Mermaid |',
'',
'## ✅ 任务列表',
'',
'- [x] 解析器增强(11 项新语法)',
'- [x] 主题系统(实例隔离 + 外部跟随)',
'- [x] 插件 v2 + i18n 增强',
'- [x] 编辑体验(行号 / 自动格式化 / 括号闭合)',
'- [ ] 更多语言翻译包',
'- [ ] VS Code 插件',
'- [x] TypeScript 全模块重构',
'- [x] 解析器 99% 行覆盖率',
'- [x] parseTokens / renderTokens 分离',
'- [x] registerBlockHandler 扩展',
'- [x] 6 个预设插件完整测试',
'- [ ] VS Code 扩展',
'',
'## 🎯 嵌套列表',
'',
'1. 一级有序',
' 1. 二级有序',
' 2. 二级第二项',
' 1. 二级有序(自动递增)',
' 2. 第二项',
'2. 回到一级',
' - 混杂无序',
' - 混杂无序列表',
' - 第二项',
' - 三级嵌套',
'',
'## 📖 定义列表',
'',
'MetonaEditor',
': 一款轻量、零依赖的中文优先 Markdown 编辑器库。',
': 支持 CommonMark + GFM 扩展语法。',
'',
'Markdown',
': 由 John Gruber 创建的轻量级标记语言。',
'',
'## 🔗 链接与图片',
'',
'访问 [MetonaEditor 仓库](https://git.metona.cn/MetonaTeam/MetonaEditor \'点击访问源码\')',
'',
'图片示例:![示例](https://placehold.co/400x120/3b82f6/white?text=MetonaEditor)',
' - [x] 三级嵌套 + 任务',
'',
'## 💻 代码块',
'',
'```js',
'import MeEditor from \'@metona-team/metona-editor\';',
'```ts',
'import MeEditor from "@metona-team/metona-editor";',
'',
'const editor = MeEditor.create(\'#editor\', {',
' value: \'# Hello World\',',
' mode: \'split\',',
'const editor = MeEditor.create("#editor", {',
' mode: "split",',
' theme: "dark",',
' lineNumbers: true,',
' autoBrackets: true,',
' plugins: [\'autoSave\', \'searchReplace\'],',
' onChange: (value) => console.log(value),',
' plugins: ["autoSave", "fileSystem", "shortcutHelp"],',
' onChange: (value, editor) => {',
' console.log("changed:", value.length, "chars");',
' },',
'});',
'',
'// 链式 API',
'editor.exec("bold").exec("h1").focus();',
'```',
'',
'## 🎨 文本样式',
'',
'- **粗体文本** 与 __另一种粗体__',
'- *斜体文本* 与 _另一种斜体_',
'- ~~删除线~~ 与 ==高亮标记==',
'- 化学式:H~2~O 与 数学:x^2^ + y^2^ = z^2^',
'- 行内代码:`const x = 42;`',
'- Emoji:smile: :rocket: :fire: :tada: :rainbow: :coffee:',
'',
'## 📌 脚注',
'',
'这是一个带脚注的句子[^1],这是另一个脚注[^note]。',
'这是一个带脚注的句子[^1],另一个脚注[^note]。',
'',
'[^1]: 第一个脚注的详细说明内容。',
'[^note]: 脚注可以包含 **格式化** 和 `代码`。',
'[^1]: 脚注可以包含 **格式化** 和 `代码`。',
'[^note]: 第二个脚注的说明。',
'',
'---',
'',
'> **提示:** 试试拖放图片到编辑器、按 `Ctrl+F` 搜索、`Ctrl+H` 替换',
'> 在列表行尾按 `Enter` 体验智能延续选中文字后输入 `(` 体验自动包裹',
].join('\n');
'> **提示:** 按 `?` 查看快捷键 · 拖放文件到编辑区 · Ctrl+F 查找 · Ctrl+H 替换',
'> 在列表行尾 Enter 体验智能延续 · 选中文字后 `(` 体验自动包裹',
'> 点击「📂 打开」加载磁盘 .md 文件 · 点击「💾 保存」写入磁盘',
].join('\n');
const editor = window.MeEditor.create('#editor', {
var editor = MeEditor.create('#editor',{
value: demoMd,
mode: 'split',
height: 620,
lineNumbers: true,
autoBrackets: true,
wordCount: true,
outline: false,
theme: 'auto',
locale: 'zh-CN',
plugins: ['autoSave', 'exportTool', 'searchReplace', 'imagePaste'],
});
// ====== Control Bar Handlers ======
// Theme
document.getElementById('themeSelect').addEventListener('change', function () {
editor.setTheme(this.value);
});
// Locale
document.getElementById('localeSelect').addEventListener('change', function () {
editor.setLocale(this.value);
});
// Mode buttons
document.querySelectorAll('#modeBtns button').forEach(function (btn) {
btn.addEventListener('click', function () {
editor.setMode(this.dataset.mode);
document.querySelectorAll('#modeBtns button').forEach(function (b) { b.classList.remove('on'); });
this.classList.add('on');
});
});
// Line numbers toggle
var lineNumBtns = document.querySelectorAll('#lineNumBtns button');
lineNumBtns[0].addEventListener('click', function () {
editor.config.lineNumbers = true;
if (editor.gutter) editor.gutter.style.display = '';
editor._renderGutter();
lineNumBtns[0].classList.add('on');
lineNumBtns[1].classList.remove('on');
});
lineNumBtns[1].addEventListener('click', function () {
editor.config.lineNumbers = false;
if (editor.gutter) editor.gutter.style.display = 'none';
lineNumBtns[1].classList.add('on');
lineNumBtns[0].classList.remove('on');
});
// Outline toggle
var outlineBtns = document.querySelectorAll('#outlineBtns button');
outlineBtns[0].addEventListener('click', function () {
editor.config.outline = true;
editor._buildOutline();
outlineBtns[0].classList.add('on');
outlineBtns[1].classList.remove('on');
});
outlineBtns[1].addEventListener('click', function () {
editor.config.outline = false;
var panel = editor.el.querySelector('.me-outline');
if (panel) panel.remove();
outlineBtns[1].classList.add('on');
outlineBtns[0].classList.remove('on');
});
// Toast demo
window.demoToast = function () {
var types = ['success', 'error', 'warning', 'info'];
var type = types[Math.floor(Math.random() * types.length)];
var messages = {
success: '✅ 内容已自动保存',
error: '❌ 操作失败,请重试',
warning: '⚠️ 有未保存的更改',
info: 'ℹ️ 这是来自插件系统的通知',
};
editor.toast(messages[type], { type: type, duration: 2500 });
};
// Outline demo
window.demoOutline = function () {
if (editor.config.outline) {
editor.config.outline = false;
var panel = editor.el.querySelector('.me-outline');
if (panel) panel.remove();
outlineBtns[0].classList.remove('on');
outlineBtns[1].classList.add('on');
} else {
editor.config.outline = true;
editor._buildOutline();
outlineBtns[0].classList.add('on');
outlineBtns[1].classList.remove('on');
mode: 'split', height: 620,
lineNumbers: true, autoBrackets: true, wordCount: true,
theme: 'auto', locale: 'zh-CN', outline: false,
plugins: ['autoSave','exportTool','searchReplace','imagePaste','shortcutHelp','fileSystem'],
onLinkClick: function(uri, text, ed){
ed.toast('链接: ' + uri, { type: 'info', duration: 2000 });
}
};
});
// Register custom shortcut
editor.registerShortcut('Ctrl+Shift+T', function () {
editor.toast('快捷键 Ctrl+Shift+T 已触发!', { type: 'info' });
}, 'Toast test');
// Control bar
var bar = document.getElementById('bar');
// Listen for theme change to update select
editor.on('themeChange', function (data) {
document.getElementById('themeSelect').value = data.theme;
});
document.getElementById('selTheme').addEventListener('change', function(){
editor.setTheme(this.value);
});
document.getElementById('selLocale').addEventListener('change', function(){
editor.setLocale(this.value);
});
// Sync editor mode button state
editor.on('modeChange', function (mode) {
document.querySelectorAll('#modeBtns button').forEach(function (b) {
b.classList.toggle('on', b.dataset.mode === mode);
});
});
bar.addEventListener('click', function(e){
var btn = e.target.closest('button[data-cmd]');
if (!btn) return;
var cmd = btn.dataset.cmd;
if (cmd === 'mode') { editor.setMode(btn.dataset.arg); updateModeBtns(btn.dataset.arg); }
else if (cmd === 'zen') { editor.toggleZen(); btn.classList.toggle('on', editor.isZen()); }
else if (cmd === 'outline') { toggleOutline(btn); }
else if (cmd === 'wrap') { editor.toggleWordWrap(); btn.classList.toggle('on', !editor.isWordWrap()); }
else if (cmd === 'float') {
editor.toggleFloatingToolbar();
btn.classList.toggle('on', editor.isFloatingToolbar());
}
else if (cmd === 'toast') {
var types = ['success','error','warning','info'];
var msgs = { success: '✅ 操作成功', error: '❌ 操作失败', warning: '⚠️ 注意', info: '️ 提示信息' };
editor.toast(msgs[types[Math.floor(Math.random()*4)]], { type: types[Math.floor(Math.random()*4)], duration: 2000 });
}
else if (cmd === 'openFile') { editor.openFile?.(); }
else if (cmd === 'saveFile') { editor.saveFile?.(); }
});
function updateModeBtns(mode){
bar.querySelectorAll('[data-cmd=mode]').forEach(function(b){ b.classList.toggle('on', b.dataset.arg === mode); });
}
function toggleOutline(btn){
editor.config.outline = !editor.config.outline;
if (editor.config.outline) { editor._buildOutline(); btn.classList.add('on'); }
else { var p = editor.el.querySelector('.me-outline'); if (p) p.remove(); btn.classList.remove('on'); }
}
// Custom shortcut
editor.registerShortcut('Ctrl+Shift+T', function(){
editor.toast('自定义快捷键已触发!', { type: 'info' });
}, 'Toast 通知演示');
// Events
editor.on('themeChange', function(d){ document.getElementById('selTheme').value = d.theme; });
editor.on('modeChange', function(m){ updateModeBtns(m); });
editor.on('fileOpened', function(d){ editor.toast('已打开: ' + d.name, { type: 'success' }); });
editor.on('fileSaved', function(){ editor.toast('文件已保存', { type: 'success' }); });
// Mermaid
function renderMermaid(){
if (typeof mermaid === 'undefined') return;
try { mermaid.run({ querySelector: '.me-mermaid .mermaid' }); } catch(e) {}
}
mermaid.initialize({ startOnLoad: false, theme: 'default' });
editor.on('afterRender', renderMermaid);
setTimeout(renderMermaid, 300);
})();
</script>
</body>
+259 -451
View File
@@ -4,179 +4,68 @@
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📖</text></svg>"/>
<title>MetonaEditor · 文档</title>
<title>MetonaEditor v0.2.4 · 文档</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--bg-soft: #161922;
--card: #1c2029;
--card-hover: #232834;
--text: #e6e8eb;
--muted: #9ca3af;
--accent: #3b82f6;
--accent-2: #8b5cf6;
--accent-3: #ec4899;
--accent-soft: rgba(59, 130, 246, 0.12);
--border: rgba(255, 255, 255, 0.08);
--gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%);
--shadow: 0 20px 50px -20px rgba(0,0,0,0.5);
}
html { scroll-behavior: smooth; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
min-height: 100vh;
}
.container { max-width: 1000px; margin: 0 auto; padding: 0 24px; }
*{box-sizing:border-box;margin:0;padding:0}
:root{--bg:#0f1117;--bg-soft:#161922;--card:#1c2029;--card-hover:#232834;--text:#e6e8eb;--muted:#9ca3af;--accent:#3b82f6;--accent-2:#8b5cf6;--accent-soft:rgba(59,130,246,.12);--border:rgba(255,255,255,.08);--gradient:linear-gradient(135deg,#3b82f6 0%,#8b5cf6 50%,#ec4899 100%)}
html{scroll-behavior:smooth}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--text);line-height:1.7;min-height:100vh}
.container{max-width:1000px;margin:0 auto;padding:0 24px}
nav{position:sticky;top:0;z-index:100;background:rgba(15,17,23,.85);backdrop-filter:blur(16px);border-bottom:1px solid var(--border)}
.nav-inner{max-width:1000px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;justify-content:space-between}
.nav-brand{display:inline-flex;align-items:center;gap:10px;text-decoration:none;color:var(--text);font-weight:700;font-size:15px}
.nav-brand .logo-mark{width:26px;height:26px;border-radius:6px;background:var(--gradient);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.logo-mark svg{display:block}
.nav-links{display:flex;gap:22px;align-items:center}
.nav-links a{color:var(--muted);text-decoration:none;font-size:13.5px;transition:color .15s}
.nav-links a:hover,.nav-links a.active{color:var(--text)}
.nav-links a.active{color:var(--accent);font-weight:600}
/* Nav */
nav {
position: sticky; top: 0; z-index: 100;
background: rgba(15, 17, 23, 0.85);
backdrop-filter: blur(16px);
border-bottom: 1px solid var(--border);
}
.nav-inner {
max-width: 1000px; margin: 0 auto; padding: 12px 24px;
display: flex; align-items: center; justify-content: space-between;
}
.nav-brand {
display: inline-flex; align-items: center; gap: 10px;
text-decoration: none; color: var(--text);
font-weight: 700; font-size: 15px;
}
.nav-brand .logo-mark {
width: 26px; height: 26px; border-radius: 6px;
background: var(--gradient);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.logo-mark svg { display: block; }
.nav-links { display: flex; gap: 22px; align-items: center; }
.nav-links a { color: var(--muted); text-decoration: none; font-size: 13.5px; transition: color .15s; }
.nav-links a:hover, .nav-links a.active { color: var(--text); }
.nav-links a.active { color: var(--accent); font-weight: 600; }
header.docs-header{text-align:center;padding:64px 24px 40px;background:radial-gradient(ellipse 60% 50% at 50% 0%,var(--accent-soft),transparent 70%),var(--bg)}
header h1{font-size:clamp(28px,4vw,44px);font-weight:800;letter-spacing:-.02em;margin-bottom:10px}
header h1 .grad{background:var(--gradient);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
header .sub{color:var(--muted);font-size:15px}
/* Header */
header.docs-header {
text-align: center; padding: 64px 24px 40px;
background: radial-gradient(ellipse 60% 50% at 50% 0%, var(--accent-soft), transparent 70%), var(--bg);
}
header h1 { font-size: clamp(28px, 4vw, 44px); font-weight: 800; letter-spacing: -0.02em; margin-bottom: 10px; }
header h1 .grad { background: var(--gradient); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
header .sub { color: var(--muted); font-size: 15px; }
.docs-layout{display:flex;gap:32px;padding:40px 0 80px}
.sidebar{position:sticky;top:72px;width:200px;flex-shrink:0;max-height:calc(100vh - 100px);overflow-y:auto;padding-right:16px;border-right:1px solid var(--border)}
.sidebar a{display:block;padding:6px 12px;border-radius:6px;color:var(--muted);text-decoration:none;font-size:13.5px;transition:background .15s,color .15s;margin-bottom:2px}
.sidebar a:hover,.sidebar a.active{background:var(--card);color:var(--text)}
.sidebar .toc-group{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;padding:16px 12px 6px;font-weight:600}
.content{flex:1;min-width:0}
.content section{margin-bottom:48px}
.content h2{font-size:24px;font-weight:700;letter-spacing:-.02em;margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.content h3{font-size:18px;font-weight:600;margin:28px 0 14px}
.content h4{font-size:15px;font-weight:600;margin:20px 0 10px;color:var(--accent)}
/* Sidebar + Content Layout */
.docs-layout { display: flex; gap: 32px; padding: 40px 0 80px; }
.sidebar {
position: sticky; top: 72px;
width: 200px; flex-shrink: 0;
max-height: calc(100vh - 100px); overflow-y: auto;
padding-right: 16px;
border-right: 1px solid var(--border);
}
.sidebar a {
display: block; padding: 6px 12px; border-radius: 6px;
color: var(--muted); text-decoration: none; font-size: 13.5px;
transition: background .15s, color .15s;
margin-bottom: 2px;
}
.sidebar a:hover, .sidebar a.active { background: var(--card); color: var(--text); }
.sidebar .toc-group { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; padding: 16px 12px 6px; font-weight: 600; }
.content { flex: 1; min-width: 0; }
.content section { margin-bottom: 48px; }
.content h2 {
font-size: 24px; font-weight: 700; letter-spacing: -0.02em;
margin-bottom: 16px; padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
.content h3 { font-size: 18px; font-weight: 600; margin: 28px 0 14px; }
.content h4 { font-size: 15px; font-weight: 600; margin: 20px 0 10px; color: var(--accent); }
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:13.5px}
th,td{padding:10px 14px;text-align:left;border:1px solid var(--border)}
th{background:var(--card);font-weight:600;color:var(--text)}
td{color:var(--muted)}
tr:nth-child(even) td{background:var(--bg-soft)}
/* Tables */
table {
width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13.5px;
}
th, td {
padding: 10px 14px; text-align: left;
border: 1px solid var(--border);
}
th { background: var(--card); font-weight: 600; color: var(--text); }
td { color: var(--muted); }
tr:nth-child(even) td { background: var(--bg-soft); }
code{font-family:"SF Mono","Cascadia Code",Consolas,monospace;font-size:13px;padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;color:var(--accent)}
pre{background:var(--bg-soft);border:1px solid var(--border);border-radius:10px;padding:18px 20px;overflow-x:auto;font-family:"SF Mono","Cascadia Code",Consolas,monospace;font-size:13px;line-height:1.8;color:#e6e8eb;margin:14px 0}
.c-key{color:#c792ea}.c-str{color:#c3e88d}.c-fn{color:#82aaff}.c-com{color:#5c6370;font-style:italic}.c-kw{color:#89ddff}.c-num{color:#f78c6c}.c-punc{color:#89ddff;opacity:.7}
.badge{display:inline-block;padding:3px 10px;background:var(--accent-soft);color:var(--accent);border-radius:999px;font-size:12px;font-weight:600;margin-right:6px;margin-bottom:4px}
/* Code */
code {
font-family: "SF Mono", "Cascadia Code", Consolas, monospace;
font-size: 13px; padding: 2px 6px;
background: var(--card); border: 1px solid var(--border);
border-radius: 4px; color: var(--accent);
}
pre {
background: var(--bg-soft); border: 1px solid var(--border);
border-radius: 10px; padding: 18px 20px; overflow-x: auto;
font-family: "SF Mono", "Cascadia Code", Consolas, monospace;
font-size: 13px; line-height: 1.8;
color: #e6e8eb; margin: 14px 0;
}
pre .c-key { color: #c792ea; }
pre .c-str { color: #c3e88d; }
pre .c-fn { color: #82aaff; }
pre .c-com { color: #5c6370; font-style: italic; }
pre .c-kw { color: #89ddff; }
pre .c-num { color: #f78c6c; }
pre .c-punc { color: #89ddff; opacity: 0.7; }
footer{border-top:1px solid var(--border);padding:36px 24px;text-align:center;color:var(--muted);font-size:13px;background:var(--bg-soft)}
footer a{color:var(--accent);text-decoration:none}
/* Badges */
.badge {
display: inline-block; padding: 3px 10px;
background: var(--accent-soft); color: var(--accent);
border-radius: 999px; font-size: 12px; font-weight: 600;
margin-right: 6px; margin-bottom: 4px;
}
/* Footer */
footer {
border-top: 1px solid var(--border); padding: 36px 24px;
text-align: center; color: var(--muted); font-size: 13px;
background: var(--bg-soft);
}
footer a { color: var(--accent); text-decoration: none; }
@media (max-width: 768px) {
.docs-layout { flex-direction: column; }
.sidebar {
position: static; width: 100%; max-height: none;
border-right: 0; border-bottom: 1px solid var(--border);
padding-bottom: 12px;
display: flex; flex-wrap: wrap; gap: 4px;
}
.sidebar .toc-group { width: 100%; }
}
@media(max-width:768px){.docs-layout{flex-direction:column}.sidebar{position:static;width:100%;max-height:none;border-right:0;border-bottom:1px solid var(--border);padding-bottom:12px;display:flex;flex-wrap:wrap;gap:4px}.sidebar .toc-group{width:100%}}
</style>
</head>
<body>
<nav>
<div class="nav-inner">
<a class="nav-brand" href="index.html">
<span class="logo-mark" title="MetonaEditor"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 19V6l7 8 7-8v13"/></svg></span>
MetonaEditor
</a>
<div class="nav-links">
<a href="index.html">首页</a>
<a href="demo.html">演示</a>
<a href="docs.html" class="active">文档</a>
</div>
<a class="nav-brand" href="index.html"><span class="logo-mark"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 19V6l7 8 7-8v13"/></svg></span>MetonaEditor</a>
<div class="nav-links"><a href="index.html">首页</a><a href="demo.html">演示</a><a href="docs.html" class="active">文档</a></div>
</div>
</nav>
<header class="docs-header">
<h1><span class="grad">文档</span></h1>
<p class="sub">MetonaEditor v0.1.10 完整 API、配置与使用指南</p>
<h1><span class="grad">API 文档</span></h1>
<p class="sub">MetonaEditor v0.2.4 · TypeScript · 完整配置、API 与使用指南</p>
</header>
<div class="container">
@@ -191,18 +80,20 @@
<a href="#api-content">内容操作</a>
<a href="#api-exec">命令执行</a>
<a href="#api-history">历史栈</a>
<a href="#api-mode">模式与全屏</a>
<a href="#api-mode">模式 / 全屏 / Zen</a>
<a href="#api-stats">统计与状态</a>
<a href="#api-readonly">启用/禁用/只读</a>
<a href="#api-readonly">启用 / 禁用 / 只读</a>
<a href="#api-events">事件系统</a>
<a href="#api-plugin">插件管理</a>
<a href="#api-lifecycle">生命周期</a>
<a href="#api-shortcuts">快捷键管理</a>
<a href="#api-destroy">生命周期</a>
<div class="toc-group">静态 API</div>
<a href="#static-api">顶层函数</a>
<div class="toc-group">语法参考</div>
<a href="#syntax">解析器语法</a>
<a href="#static-api">全局函数</a>
<div class="toc-group">解析器</div>
<a href="#parser-syntax">语法参考</a>
<a href="#parser-api">解析器 API</a>
<div class="toc-group">插件</div>
<a href="#plugins">插件约定</a>
<a href="#plugins-convention">插件约定</a>
<a href="#preset-plugins">预设插件</a>
<div class="toc-group">主题</div>
<a href="#themes">主题系统</a>
@@ -212,94 +103,77 @@
<main class="content">
<!-- ======== 快速入门 ======== -->
<!-- 快速入门 -->
<section id="quickstart">
<h2>安装与引入</h2>
<pre><span class="c-com"># npm 安装</span>
<span class="c-kw">npm</span> install @metona-team/metona-editor
<span class="c-com"># ES Module</span>
<span class="c-com">// TypeScript / ES Module</span>
<span class="c-kw">import</span> MeEditor <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-kw">const</span> editor <span class="c-punc">=</span> MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span><span class="c-str">'#editor'</span><span class="c-punc">,</span> <span class="c-punc">{</span>
<span class="c-fn">value</span><span class="c-punc">:</span> <span class="c-str">'# Hello World'</span><span class="c-punc">,</span>
<span class="c-fn">mode</span><span class="c-punc">:</span> <span class="c-str">'split'</span><span class="c-punc">,</span>
<span class="c-punc">});</span>
<span class="c-com"># 浏览器 UMD</span>
<span class="c-com">// 浏览器 UMD</span>
<span class="c-com">&lt;script src="dist/metona-editor.js"&gt;&lt;/script&gt;</span>
<span class="c-com">&lt;script&gt;</span>
<span class="c-kw">const</span> editor <span class="c-punc">=</span> MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span><span class="c-str">'#editor'</span><span class="c-punc">);</span>
<span class="c-com">&lt;/script&gt;</span></pre>
</section>
<!-- ======== 配置项 ======== -->
<!-- 配置 -->
<section id="config">
<h2>全部配置项</h2>
<p style="color:var(--muted);margin-bottom:14px">所有配置均为可选,以下是默认值:</p>
<p style="color:var(--muted);margin-bottom:14px">所有配置均为可选</p>
<pre>MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span>container<span class="c-punc">,</span> <span class="c-punc">{</span>
<span class="c-com">// 内容</span>
value<span class="c-punc">:</span> <span class="c-str">''</span><span class="c-punc">,</span> <span class="c-com">// 初始 Markdown 文本</span>
placeholder<span class="c-punc">:</span> <span class="c-str">''</span><span class="c-punc">,</span> <span class="c-com">// 占位符</span>
value<span class="c-punc">:</span> <span class="c-str">''</span><span class="c-punc">,</span> placeholder<span class="c-punc">:</span> <span class="c-str">''</span><span class="c-punc">,</span>
<span class="c-com">// 视图</span>
mode<span class="c-punc">:</span> <span class="c-str">'split'</span><span class="c-punc">,</span> <span class="c-com">// 'edit' | 'split' | 'preview'</span>
height<span class="c-punc">:</span> <span class="c-num">400</span><span class="c-punc">,</span> <span class="c-com">// 数字为 px,字符串原样使用</span>
toolbar<span class="c-punc">:</span> DEFAULT_TOOLBAR<span class="c-punc">,</span> <span class="c-com">// 工具栏配置,false 隐藏</span>
wordCount<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span> <span class="c-com">// 字数统计状态栏</span>
mode<span class="c-punc">:</span> <span class="c-str">'split'</span><span class="c-punc">,</span> height<span class="c-punc">:</span> <span class="c-num">400</span><span class="c-punc">,</span>
toolbar<span class="c-punc">:</span> DEFAULT_TOOLBAR<span class="c-punc">,</span> wordCount<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span>
lineNumbers<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span> outline<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span>
<span class="c-com">// 行为</span>
autofocus<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span> <span class="c-com">// 自动聚焦</span>
spellcheck<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span> <span class="c-com">// 拼写检查</span>
readOnly<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span> <span class="c-com">// 只读模式</span>
historyLimit<span class="c-punc">:</span> <span class="c-num">100</span><span class="c-punc">,</span> <span class="c-com">// 历史栈上限</span>
historyDebounce<span class="c-punc">:</span> <span class="c-num">400</span><span class="c-punc">,</span> <span class="c-com">// 历史防抖延迟(ms</span>
syncScroll<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span> <span class="c-com">// 分屏模式同步滚动</span>
tabSize<span class="c-punc">:</span> <span class="c-num">2</span><span class="c-punc">,</span> <span class="c-com">// 制表符空格数(0 表示 \t</span>
autofocus<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span> spellcheck<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span>
readOnly<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span> tabSize<span class="c-punc">:</span> <span class="c-num">2</span><span class="c-punc">,</span>
historyLimit<span class="c-punc">:</span> <span class="c-num">100</span><span class="c-punc">,</span> historyDebounce<span class="c-punc">:</span> <span class="c-num">400</span><span class="c-punc">,</span>
syncScroll<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span> autoBrackets<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span>
zenMode<span class="c-punc">:</span> <span class="c-kw">false</span><span class="c-punc">,</span> wordWrap<span class="c-punc">:</span> <span class="c-kw">true</span><span class="c-punc">,</span>
<span class="c-com">// 主题与国际化</span>
theme<span class="c-punc">:</span> <span class="c-str">'auto'</span><span class="c-punc">,</span> <span class="c-com">// 'light' | 'dark' | 'auto' | 'warm' | 自定义</span>
locale<span class="c-punc">:</span> <span class="c-str">'zh-CN'</span><span class="c-punc">,</span> <span class="c-com">// 'zh-CN' | 'en-US' | 自定义</span>
<span class="c-com">// 主题 / 语言</span>
theme<span class="c-punc">:</span> <span class="c-str">'auto'</span><span class="c-punc">,</span> locale<span class="c-punc">:</span> <span class="c-str">'zh-CN'</span><span class="c-punc">,</span>
<span class="c-com">// 自定义渲染</span>
render<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (md, env) =&gt; html</span>
highlight<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (code, lang) =&gt; html</span>
sanitize<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (html) =&gt; safeHtml</span>
<span class="c-com">// 钩子</span>
render<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> highlight<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
sanitize<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
<span class="c-com">// 外观</span>
className<span class="c-punc">:</span> <span class="c-str">''</span><span class="c-punc">,</span> <span class="c-com">// 容器额外 class</span>
style<span class="c-punc">:</span> <span class="c-punc">{},</span> <span class="c-com">// 内联样式</span>
className<span class="c-punc">:</span> <span class="c-str">''</span><span class="c-punc">,</span> style<span class="c-punc">:</span> <span class="c-punc">{},</span>
<span class="c-com">// 插件回调</span>
plugins<span class="c-punc">:</span> <span class="c-punc">[],</span> <span class="c-com">// 实例级插件数组</span>
onChange<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (value, editor) =&gt; void</span>
onInput<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (value, editor) =&gt; void</span>
onFocus<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) =&gt; void</span>
onBlur<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) =&gt; void</span>
onSave<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (value, editor) =&gt; void</span>
onModeChange<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (mode, editor) =&gt; void</span>
onFullscreen<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (fullscreen, editor) =&gt; void</span>
onCreate<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) =&gt; void</span>
onDestroy<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) =&gt; void</span>
<span class="c-com">// 插件 / 回调</span>
plugins<span class="c-punc">:</span> <span class="c-punc">[],</span>
onChange<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> onInput<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
onFocus<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> onBlur<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
onSave<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> onModeChange<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
onFullscreen<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> onCreate<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
onDestroy<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> onLinkClick<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span>
<span class="c-punc">});</span></pre>
</section>
<!-- ======== 工具栏 ======== -->
<!-- 工具栏 -->
<section id="toolbar">
<h2>工具栏配置</h2>
<pre><span class="c-com">// 默认工具栏(完整按钮)</span>
<h2>工具栏</h2>
<pre><span class="c-com">// 默认完整工具栏</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span>container<span class="c-punc">,</span> <span class="c-punc">{</span> toolbar<span class="c-punc">:</span> <span class="c-kw">true</span> <span class="c-punc">});</span>
<span class="c-com">// 隐藏工具栏</span>
<span class="c-com">// 隐藏</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span>container<span class="c-punc">,</span> <span class="c-punc">{</span> toolbar<span class="c-punc">:</span> <span class="c-kw">false</span> <span class="c-punc">});</span>
<span class="c-com">// 自定义工具栏 — '|' 为分隔符</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span>container<span class="c-punc">,</span> <span class="c-punc">{</span>
toolbar<span class="c-punc">:</span> <span class="c-punc">[</span><span class="c-str">'bold'</span><span class="c-punc">,</span> <span class="c-str">'italic'</span><span class="c-punc">,</span> <span class="c-str">'|'</span><span class="c-punc">,</span> <span class="c-str">'h1'</span><span class="c-punc">,</span> <span class="c-str">'h2'</span><span class="c-punc">,</span> <span class="c-str">'|'</span><span class="c-punc">,</span> <span class="c-str">'undo'</span><span class="c-punc">,</span> <span class="c-str">'redo'</span><span class="c-punc">],</span>
<span class="c-punc">});</span></pre>
<span class="c-com">// 自定义 | 为分隔符</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span>container<span class="c-punc">,</span> <span class="c-punc">{</span> toolbar<span class="c-punc">:</span> <span class="c-punc">[</span><span class="c-str">'bold'</span><span class="c-punc">,</span> <span class="c-str">'italic'</span><span class="c-punc">,</span> <span class="c-str">'|'</span><span class="c-punc">,</span> <span class="c-str">'undo'</span><span class="c-punc">,</span> <span class="c-str">'redo'</span><span class="c-punc">]</span> <span class="c-punc">});</span></pre>
<p>可用动作:</p>
<p>
<span class="badge">bold</span><span class="badge">italic</span><span class="badge">strikethrough</span><span class="badge">underline</span><span class="badge">code</span>
@@ -311,306 +185,246 @@ MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class
</p>
</section>
<!-- ======== 实例 API ======== -->
<section id="api-content">
<h2>实例 API</h2>
<!-- 实例 API -->
<section id="api-content"><h2>实例 API</h2>
<h3>内容操作</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">getValue</span><span class="c-punc">();</span> <span class="c-com">// 获取 Markdown 文本</span>
editor<span class="c-punc">.</span><span class="c-fn">setValue</span><span class="c-punc">(</span>md<span class="c-punc">,</span> <span class="c-punc">{</span> silent <span class="c-punc">});</span> <span class="c-com">// 设置内容,silent 不触发 change</span>
editor<span class="c-punc">.</span><span class="c-fn">getHTML</span><span class="c-punc">();</span> <span class="c-com">// 获取渲染后的 HTML</span>
editor<span class="c-punc">.</span><span class="c-fn">refresh</span><span class="c-punc">();</span> <span class="c-com">// 强制刷新预览(内容未变时)</span>
editor<span class="c-punc">.</span><span class="c-fn">insert</span><span class="c-punc">(</span>text<span class="c-punc">,</span> <span class="c-punc">{</span> replace <span class="c-punc">});</span> <span class="c-com">// 光标处插入 / 替换选区</span>
editor<span class="c-punc">.</span><span class="c-fn">wrap</span><span class="c-punc">(</span>before<span class="c-punc">,</span> after<span class="c-punc">);</span> <span class="c-com">// 选区包裹</span>
editor<span class="c-punc">.</span><span class="c-fn">focus</span><span class="c-punc">();</span> <span class="c-com">// 聚焦</span>
editor<span class="c-punc">.</span><span class="c-fn">blur</span><span class="c-punc">();</span> <span class="c-com">// 失焦</span></pre>
<pre>editor<span class="c-punc">.</span><span class="c-fn">getValue</span><span class="c-punc">();</span> <span class="c-com">// => string</span>
editor<span class="c-punc">.</span><span class="c-fn">setValue</span><span class="c-punc">(</span>md<span class="c-punc">,</span> <span class="c-punc">{</span> silent <span class="c-punc">});</span> <span class="c-com">// => this</span>
editor<span class="c-punc">.</span><span class="c-fn">getHTML</span><span class="c-punc">();</span> <span class="c-com">// => string</span>
editor<span class="c-punc">.</span><span class="c-fn">refresh</span><span class="c-punc">();</span> <span class="c-com">// 强制重渲染</span>
editor<span class="c-punc">.</span><span class="c-fn">insert</span><span class="c-punc">(</span>text<span class="c-punc">,</span> <span class="c-punc">{</span> replace <span class="c-punc">});</span> <span class="c-com">// => this</span>
editor<span class="c-punc">.</span><span class="c-fn">wrap</span><span class="c-punc">(</span>before<span class="c-punc">,</span> after<span class="c-punc">);</span> <span class="c-com">// => this</span>
editor<span class="c-punc">.</span><span class="c-fn">focus</span><span class="c-punc">();</span> <span class="c-fn">blur</span><span class="c-punc">();</span></pre>
</section>
<section id="api-exec">
<h3>命令执行</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">exec</span><span class="c-punc">(</span>action<span class="c-punc">,</span> <span class="c-punc">...</span>args<span class="c-punc">);</span> <span class="c-com">// 执行命令,返回 this 链式调用</span>
editor<span class="c-punc">.</span><span class="c-fn">exec</span><span class="c-punc">(</span><span class="c-str">'bold'</span><span class="c-punc">).</span><span class="c-fn">exec</span><span class="c-punc">(</span><span class="c-str">'h1'</span><span class="c-punc">);</span> <span class="c-com">// 链式调用</span></pre>
<section id="api-exec"><h3>命令执行</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">exec</span><span class="c-punc">(</span>action<span class="c-punc">)</span> <span class="c-com">// => this, 支持链式</span></pre>
<table>
<tr><th>action</th><th>效果</th><th>快捷键</th></tr>
<tr><td><code>bold</code></td><td>加粗 **text**</td><td>Ctrl+B</td></tr>
<tr><td><code>italic</code></td><td>斜体 *text*</td><td>Ctrl+I</td></tr>
<tr><td><code>strikethrough</code></td><td>删除线 ~~text~~</td><td></td></tr>
<tr><td><code>underline</code></td><td>下划线 &lt;u&gt;text&lt;/u&gt;</td><td>Ctrl+U</td></tr>
<tr><td><code>code</code></td><td>行内代码 `text`</td><td>Ctrl+E</td></tr>
<tr><td><code>h1</code>/<code>h2</code>/<code>h3</code></td><td>标题 # / ## / ###</td><td>Ctrl+1/2/3</td></tr>
<tr><td><code>quote</code></td><td>引用 ></td><td>Ctrl+Q</td></tr>
<tr><td><code>ul</code>/<code>ol</code></td><td>无序/有序列表</td><td></td></tr>
<tr><td><code>indent</code>/<code>outdent</code></td><td>缩进/反缩进</td><td>Tab/Shift+Tab</td></tr>
<tr><td><code>hr</code></td><td>水平线 ---</td><td></td></tr>
<tr><td><code>link</code></td><td>插入链接 [text](url)</td><td>Ctrl+K</td></tr>
<tr><td><code>image</code></td><td>插入图片 ![alt](url)</td><td></td></tr>
<tr><td><code>bold</code></td><td>**text**</td><td>Ctrl+B</td></tr>
<tr><td><code>italic</code></td><td>*text*</td><td>Ctrl+I</td></tr>
<tr><td><code>strikethrough</code></td><td>~~text~~</td><td></td></tr>
<tr><td><code>underline</code></td><td>&lt;u&gt;text&lt;/u&gt;</td><td>Ctrl+U</td></tr>
<tr><td><code>code</code></td><td>`text`</td><td>Ctrl+E</td></tr>
<tr><td><code>h1</code>/<code>h2</code>/<code>h3</code></td><td># / ## / ###</td><td>Ctrl+1/2/3</td></tr>
<tr><td><code>quote</code></td><td>&gt; text</td><td>Ctrl+Q</td></tr>
<tr><td><code>ul</code>/<code>ol</code></td><td>- / 1. 列表</td><td></td></tr>
<tr><td><code>link</code></td><td>[text](url)</td><td>Ctrl+K</td></tr>
<tr><td><code>image</code></td><td>![alt](url)</td><td></td></tr>
<tr><td><code>table</code></td><td>插入表格</td><td></td></tr>
<tr><td><code>undo</code>/<code>redo</code></td><td>撤销/重做</td><td>Ctrl+Z / Ctrl+Y</td></tr>
<tr><td><code>hr</code></td><td>---水平线</td><td></td></tr>
<tr><td><code>indent</code>/<code>outdent</code></td><td>缩进/反缩进</td><td>Tab / Shift+Tab</td></tr>
<tr><td><code>undo</code>/<code>redo</code></td><td>撤销/重做</td><td>Ctrl+Z / Y</td></tr>
<tr><td><code>edit</code>/<code>split</code>/<code>preview</code></td><td>切换模式</td><td></td></tr>
<tr><td><code>fullscreen</code></td><td>切换全屏</td><td></td></tr>
<tr><td><code>fullscreen</code></td><td>全屏</td><td></td></tr>
<tr><td><code>zen</code></td><td>Zen 模式</td><td></td></tr>
<tr><td><code>wordwrap</code></td><td>自动换行</td><td></td></tr>
</table>
</section>
<section id="api-history">
<h3>历史栈</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">undo</span><span class="c-punc">();</span> <span class="c-com">// 撤销</span>
editor<span class="c-punc">.</span><span class="c-fn">redo</span><span class="c-punc">();</span> <span class="c-com">// 重做</span>
editor<span class="c-punc">.</span><span class="c-fn">canUndo</span><span class="c-punc">();</span> <span class="c-com">// 是否可撤销</span>
editor<span class="c-punc">.</span><span class="c-fn">canRedo</span><span class="c-punc">();</span> <span class="c-com">// 是否可重做</span></pre>
<section id="api-history"><h3>历史栈</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">undo</span><span class="c-punc">();</span> editor<span class="c-punc">.</span><span class="c-fn">redo</span><span class="c-punc">();</span>
editor<span class="c-punc">.</span><span class="c-fn">canUndo</span><span class="c-punc">();</span> editor<span class="c-punc">.</span><span class="c-fn">canRedo</span><span class="c-punc">();</span></pre>
</section>
<section id="api-mode">
<h3>模式与全屏</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">setMode</span><span class="c-punc">(</span><span class="c-str">'split'</span><span class="c-punc">);</span> <span class="c-com">// 设置模式:edit | split | preview</span>
editor<span class="c-punc">.</span><span class="c-fn">getMode</span><span class="c-punc">();</span> <span class="c-com">// 获取当前模式</span>
editor<span class="c-punc">.</span><span class="c-fn">toggleFullscreen</span><span class="c-punc">();</span> <span class="c-com">// 切换全屏</span>
editor<span class="c-punc">.</span><span class="c-fn">exitFullscreen</span><span class="c-punc">();</span> <span class="c-com">// 退出全屏</span>
editor<span class="c-punc">.</span><span class="c-fn">isFullscreen</span><span class="c-punc">();</span> <span class="c-com">// 是否全屏</span></pre>
<section id="api-mode"><h3>模式 / 全屏 / Zen</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">setMode</span><span class="c-punc">(</span><span class="c-str">'split'</span><span class="c-punc">);</span> editor<span class="c-punc">.</span><span class="c-fn">getMode</span><span class="c-punc">();</span>
editor<span class="c-punc">.</span><span class="c-fn">toggleFullscreen</span><span class="c-punc">();</span> <span class="c-fn">isFullscreen</span><span class="c-punc">();</span> <span class="c-fn">exitFullscreen</span><span class="c-punc">();</span>
editor<span class="c-punc">.</span><span class="c-fn">toggleZen</span><span class="c-punc">();</span> <span class="c-fn">isZen</span><span class="c-punc">();</span>
editor<span class="c-punc">.</span><span class="c-fn">toggleWordWrap</span><span class="c-punc">();</span> <span class="c-fn">setWordWrap</span><span class="c-punc">(</span><span class="c-kw">true</span><span class="c-punc">);</span> <span class="c-fn">isWordWrap</span><span class="c-punc">();</span></pre>
</section>
<section id="api-stats">
<h3>统计与状态</h3>
<section id="api-stats"><h3>统计与状态</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">getStats</span><span class="c-punc">();</span>
<span class="c-com">// =&gt; { characters, words, chineseChars, englishWords, lines, readingTime }</span>
<span class="c-com">// => { characters, words, chineseChars, englishWords, lines, readingTime }</span>
editor<span class="c-punc">.</span><span class="c-fn">getStatus</span><span class="c-punc">();</span>
<span class="c-com">// =&gt; { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }</span></pre>
<span class="c-com">// => { id, mode, theme, locale, fullscreen, readOnly, disabled, destroyed, plugins }</span></pre>
</section>
<section id="api-readonly">
<h3>启用/禁用/只读</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">enable</span><span class="c-punc">();</span> <span class="c-com">// 启用</span>
editor<span class="c-punc">.</span><span class="c-fn">disable</span><span class="c-punc">();</span> <span class="c-com">// 禁用</span>
editor<span class="c-punc">.</span><span class="c-fn">isDisabled</span><span class="c-punc">();</span> <span class="c-com">// 是否禁用</span>
editor<span class="c-punc">.</span><span class="c-fn">setReadOnly</span><span class="c-punc">(</span><span class="c-kw">true</span><span class="c-punc">);</span> <span class="c-com">// 只读模式</span>
editor<span class="c-punc">.</span><span class="c-fn">isReadOnly</span><span class="c-punc">();</span> <span class="c-com">// 是否只读</span></pre>
<section id="api-readonly"><h3>启用 / 禁用 / 只读</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">enable</span><span class="c-punc">();</span> <span class="c-fn">disable</span><span class="c-punc">();</span> <span class="c-fn">isDisabled</span><span class="c-punc">();</span>
editor<span class="c-punc">.</span><span class="c-fn">setReadOnly</span><span class="c-punc">(</span><span class="c-kw">true</span><span class="c-punc">);</span> <span class="c-fn">isReadOnly</span><span class="c-punc">();</span></pre>
</section>
<section id="api-events">
<h3>事件系统</h3>
<section id="api-events"><h3>事件系统</h3>
<table>
<tr><th>事件</th><th>触发时机</th><th>回调参数</th></tr>
<tr><td><code>input</code></td><td>textarea 原生 input</td><td><code>(value, editor)</code></td></tr>
<tr><th>事件</th><th>触发时机</th><th>参数</th></tr>
<tr><td><code>input</code></td><td>textarea 输入</td><td><code>(value, editor)</code></td></tr>
<tr><td><code>change</code></td><td>内容变化</td><td><code>(value, editor)</code></td></tr>
<tr><td><code>focus</code></td><td>聚焦</td><td><code>(editor)</code></td></tr>
<tr><td><code>blur</code></td><td>失焦</td><td><code>(editor)</code></td></tr>
<tr><td><code>save</code></td><td>Ctrl+S</td><td><code>(value, editor)</code></td></tr>
<tr><td><code>focus</code> / <code>blur</code></td><td>聚焦/失焦</td><td><code>(editor)</code></td></tr>
<tr><td><code>save</code></td><td>Ctrl+S</td><td><code>(editor)</code></td></tr>
<tr><td><code>modeChange</code></td><td>模式切换</td><td><code>(mode, editor)</code></td></tr>
<tr><td><code>fullscreen</code></td><td>全屏切换</td><td><code>(fullscreen, editor)</code></td></tr>
<tr><td><code>themeChange</code></td><td>主题变更</td><td><code>({ theme, resolved, config })</code></td></tr>
<tr><td><code>localeChange</code></td><td>语言变更</td><td><code>({ locale, direction })</code></td></tr>
<tr><td><code>zenChange</code></td><td>Zen 切换</td><td><code>(zen, editor)</code></td></tr>
<tr><td><code>destroy</code></td><td>销毁</td><td><code>()</code></td></tr>
<tr><td><code>autosave</code></td><td>autoSave 保存</td><td><code>({ key, value })</code></td></tr>
<tr><td><code>beforeRender</code></td><td>渲染前</td><td><code>(editor)</code></td></tr>
<tr><td><code>afterRender</code></td><td>渲染后</td><td><code>(editor)</code></td></tr>
<tr><td><code>beforeRender</code> / <code>afterRender</code></td><td>渲染前后</td><td><code>(editor)</code></td></tr>
<tr><td><code>autosave</code></td><td>autoSave 触发</td><td><code>({ key, value })</code></td></tr>
<tr><td><code>linkClick</code></td><td>预览链接点击</td><td><code>({ href, text })</code></td></tr>
</table>
<h4>全局钩子(所有实例共享)</h4>
<h4>全局钩子</h4>
<table>
<tr><th>钩子</th><th>触发时机</th></tr>
<tr><td><code>beforeCreate</code></td><td>构造函数初始化前</td></tr>
<tr><td><code>afterCreate</code></td><td>构造函数初始化完成</td></tr>
<tr><td><code>beforeRender</code></td><td>每次渲染前(全局)</td></tr>
<tr><td><code>afterRender</code></td><td>每次渲染后(全局)</td></tr>
<tr><td><code>beforeDestroy</code></td><td>destroy 前</td></tr>
<tr><td><code>afterDestroy</code></td><td>destroy 后</td></tr>
<tr><td><code>beforeCreate</code> / <code>afterCreate</code></td><td>实例构造前后</td></tr>
<tr><td><code>beforeRender</code> / <code>afterRender</code></td><td>全局渲染前后</td></tr>
<tr><td><code>beforeDestroy</code> / <code>afterDestroy</code></td><td>销毁前后</td></tr>
</table>
</section>
<section id="api-plugin">
<h3>插件管理</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>plugin<span class="c-punc">,</span> options<span class="c-punc">);</span> <span class="c-com">// 安装插件</span>
editor<span class="c-punc">.</span><span class="c-fn">getPlugins</span><span class="c-punc">();</span> <span class="c-com">// 获取已安装插件列表</span>
editor<span class="c-punc">.</span><span class="c-fn">addToolbarButton</span><span class="c-punc">(</span>config<span class="c-punc">);</span> <span class="c-com">// 追加工具栏按钮</span></pre>
<section id="api-plugin"><h3>插件管理</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>plugin<span class="c-punc">,</span> options<span class="c-punc">);</span> <span class="c-com">// 安装</span>
editor<span class="c-punc">.</span><span class="c-fn">unuse</span><span class="c-punc">(</span>name<span class="c-punc">);</span> <span class="c-com">// 卸载</span>
editor<span class="c-punc">.</span><span class="c-fn">getPlugins</span><span class="c-punc">();</span> <span class="c-com">// 已安装列表</span>
editor<span class="c-punc">.</span><span class="c-fn">addToolbarButton</span><span class="c-punc">(</span>config<span class="c-punc">);</span> <span class="c-com">// 追加按钮</span></pre>
</section>
<section id="api-lifecycle">
<h3>生命周期</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">destroy</span><span class="c-punc">();</span> <span class="c-com">// 销毁实例</span>
editor<span class="c-punc">.</span><span class="c-fn">isDestroyed</span><span class="c-punc">();</span> <span class="c-com">// 是否已销毁</span></pre>
<section id="api-shortcuts"><h3>快捷键管理</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">registerShortcut</span><span class="c-punc">(</span><span class="c-str">'Ctrl+Shift+T'</span><span class="c-punc">,</span> handler<span class="c-punc">,</span> <span class="c-str">'desc'</span><span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">unregisterShortcut</span><span class="c-punc">(</span><span class="c-str">'Ctrl+Shift+T'</span><span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">getShortcuts</span><span class="c-punc">();</span></pre>
</section>
<!-- ======== 静态 API ======== -->
<section id="static-api">
<h2>静态 API</h2>
<section id="api-destroy"><h3>生命周期 / Toast</h3>
<pre>editor<span class="c-punc">.</span><span class="c-fn">destroy</span><span class="c-punc">();</span> <span class="c-fn">isDestroyed</span><span class="c-punc">();</span>
editor<span class="c-punc">.</span><span class="c-fn">toast</span><span class="c-punc">(</span>msg<span class="c-punc">,</span> <span class="c-punc">{</span> type<span class="c-punc">,</span> duration<span class="c-punc">,</span> animation <span class="c-punc">});</span></pre>
</section>
<!-- 静态 API -->
<section id="static-api"><h2>静态 API</h2>
<pre><span class="c-kw">import</span> MeEditor <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-com">// 工厂函数</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">create</span><span class="c-punc">(</span>container<span class="c-punc">,</span> options<span class="c-punc">);</span>
<span class="c-com">// 全局默认插件</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>presetPlugins<span class="c-punc">.</span>autoSave<span class="c-punc">,</span> <span class="c-punc">{</span> delay<span class="c-punc">:</span> <span class="c-num">2000</span> <span class="c-punc">});</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'searchReplace'</span><span class="c-punc">);</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'autoSave'</span><span class="c-punc">,</span> <span class="c-punc">{</span> delay<span class="c-punc">:</span> <span class="c-num">2000</span> <span class="c-punc">});</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>customPlugin<span class="c-punc">);</span>
<span class="c-com">// 全局事件钩子</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">on</span><span class="c-punc">(</span><span class="c-str">'beforeCreate'</span><span class="c-punc">,</span> <span class="c-punc">(</span>editor<span class="c-punc">)</span> <span class="c-punc">=&gt;</span> <span class="c-punc">{});</span>
<span class="c-com">// 全局钩子</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">on</span><span class="c-punc">(</span><span class="c-str">'beforeCreate'</span><span class="c-punc">,</span> <span class="c-punc">(</span>editor<span class="c-punc">)</span> <span class="c-punc">=></span> <span class="c-punc">{});</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">off</span><span class="c-punc">(</span><span class="c-str">'beforeCreate'</span><span class="c-punc">,</span> handler<span class="c-punc">);</span>
<span class="c-com">// 主题与语言</span>
<span class="c-com">// 全局主题 / 语言</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'dark'</span><span class="c-punc">);</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setLocale</span><span class="c-punc">(</span><span class="c-str">'en-US'</span><span class="c-punc">);</span>
<span class="c-com">// 状态查询</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">getStatus</span><span class="c-punc">();</span> <span class="c-com">// =&gt; { version, theme, locale, globalPlugins, presetPlugins }</span>
<span class="c-com">// 销毁全局资源</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">destroy</span><span class="c-punc">();</span>
<span class="c-com">// 内置解析器</span>
<span class="c-kw">import</span> <span class="c-punc">{</span> parseMarkdown<span class="c-punc">,</span> safeUrl<span class="c-punc">,</span> slugify <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-fn">parseMarkdown</span><span class="c-punc">(</span><span class="c-str">'# Hello'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; '&lt;h1 id="hello"&gt;Hello&lt;/h1&gt;'</span>
<span class="c-fn">safeUrl</span><span class="c-punc">(</span><span class="c-str">'javascript:alert(1)'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; ''</span>
<span class="c-fn">slugify</span><span class="c-punc">(</span><span class="c-str">'Hello World'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; 'hello-world'</span></pre>
<span class="c-com">// 解析器独立使用</span>
<span class="c-kw">import</span> <span class="c-punc">{</span> parseMarkdown<span class="c-punc">,</span> parseTokens<span class="c-punc">,</span> renderTokens <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span></pre>
</section>
<!-- ======== 语法参考 ======== -->
<section id="syntax">
<h2>解析器语法参考</h2>
<!-- 解析器语法 -->
<section id="parser-syntax"><h2>解析器语法参考</h2>
<table>
<tr><th>语法</th><th>示例</th><th>输出</th></tr>
<tr><td>标题</td><td><code># H1</code> ~ <code>###### H6</code></td><td><code>&lt;h1&gt;</code> ~ <code>&lt;h6&gt;</code></td></tr>
<tr><td>段落</td><td>纯文本</td><td><code>&lt;p&gt;</code></td></tr>
<tr><td>粗体</td><td><code>**bold**</code> <code>__bold__</code></td><td><code>&lt;strong&gt;</code></td></tr>
<tr><td>斜体</td><td><code>*italic*</code> <code>_italic_</code></td><td><code>&lt;em&gt;</code></td></tr>
<tr><td>删除线</td><td><code>~~text~~</code></td><td><code>&lt;del&gt;</code></td></tr>
<tr><td>高亮标记</td><td><code>==text==</code></td><td><code>&lt;mark&gt;</code></td></tr>
<tr><td>上标</td><td><code>x^2^</code></td><td><code>&lt;sup&gt;</code></td></tr>
<tr><td>下标</td><td><code>H~2~O</code></td><td><code>&lt;sub&gt;</code></td></tr>
<tr><td>行内代码</td><td><code>`code`</code></td><td><code>&lt;code&gt;</code></td></tr>
<tr><td>代码块</td><td><code>```lang</code></td><td><code>&lt;pre&gt;&lt;code&gt;</code></td></tr>
<tr><td>引用</td><td><code>&gt; quote</code></td><td><code>&lt;blockquote&gt;</code></td></tr>
<tr><td>无序列表</td><td><code>- item</code></td><td><code>&lt;ul&gt;&lt;li&gt;</code></td></tr>
<tr><td>有序列表</td><td><code>1. item</code></td><td><code>&lt;ol&gt;&lt;li&gt;</code></td></tr>
<tr><td>任务列表</td><td><code>- [x] done</code></td><td><code>&lt;li class="me-task-item"&gt;</code></td></tr>
<tr><td>水平线</td><td><code>---</code> <code>***</code> <code>___</code></td><td><code>&lt;hr&gt;</code></td></tr>
<tr><td>表格</td><td><code>| a | b |</code></td><td><code>&lt;table&gt;</code>(支持 <code>:---:</code> 列对齐)</td></tr>
<tr><td>链接</td><td><code>[text](url)</code></td><td><code>&lt;a&gt;</code></td></tr>
<tr><td>图片</td><td><code>![alt](url)</code></td><td><code>&lt;img&gt;</code></td></tr>
<tr><td>自动链接</td><td><code>&lt;https://...&gt;</code></td><td><code>&lt;a&gt;</code></td></tr>
<tr><td>Emoji</td><td><code>:smile:</code> <code>:rocket:</code></td><td>😊 🚀(80+ 常用)</td></tr>
<tr><td>标题</td><td><code># H1</code> ~ <code>###### H6</code></td><td>&lt;h1&gt; ~ &lt;h6&gt;</td></tr>
<tr><td>Setext 标题</td><td><code>H1\n===</code></td><td>&lt;h1&gt; / &lt;h2&gt;</td></tr>
<tr><td>粗体</td><td><code>**bold**</code> <code>__bold__</code></td><td>&lt;strong&gt;</td></tr>
<tr><td>斜体 <span class="badge">v0.1.15</span></td><td><code>***text***</code> <code>___text___</code></td><td>&lt;em&gt;&lt;strong&gt;</td></tr>
<tr><td>斜体</td><td><code>*italic*</code> <code>_italic_</code></td><td>&lt;em&gt;</td></tr>
<tr><td>删除线 / 高亮</td><td><code>~~del~~</code> <code>==mark==</code></td><td>&lt;del&gt; / &lt;mark&gt;</td></tr>
<tr><td>上标 / 下标</td><td><code>x^2^</code> <code>H~2~O</code></td><td>&lt;sup&gt; / &lt;sub&gt;</td></tr>
<tr><td>行内代码</td><td><code>`code`</code></td><td>&lt;code&gt;</td></tr>
<tr><td>代码</td><td><code>```lang</code></td><td>&lt;pre&gt;&lt;code&gt;</td></tr>
<tr><td>缩进代码块</td><td><code> code</code></td><td>&lt;pre&gt;&lt;code&gt;</td></tr>
<tr><td>引用</td><td><code>&gt; quote</code></td><td>&lt;blockquote&gt;</td></tr>
<tr><td>无序列表</td><td><code>- * + item</code></td><td>&lt;ul&gt;&lt;li&gt;</td></tr>
<tr><td>有序列表</td><td><code>1. item</code></td><td>&lt;ol&gt;&lt;li&gt;</td></tr>
<tr><td>嵌套列表</td><td>多级缩进</td><td>多级 &lt;ul&gt;/&lt;ol&gt;</td></tr>
<tr><td>任务列表</td><td><code>- [x] done</code></td><td>&lt;li class="me-task-item"&gt;</td></tr>
<tr><td>水平线</td><td><code>---</code> <code>***</code> <code>___</code></td><td>&lt;hr&gt;</td></tr>
<tr><td>表格</td><td><code>| a | b |</code></td><td>&lt;table&gt; + 列对齐</td></tr>
<tr><td>链接 <span class="badge">v0.1.15</span></td><td><code>[**t**](url)</code></td><td>文本内支持行内格式</td></tr>
<tr><td>图片</td><td><code>![alt](url)</code></td><td>&lt;img&gt;</td></tr>
<tr><td>引用链接 <span class="badge">v0.2.2</span></td><td><code>[text][ref]</code> + <code>[ref]: url</code></td><td>&lt;a&gt; 引用式</td></tr>
<tr><td>引用图片 <span class="badge">v0.2.2</span></td><td><code>![alt][ref]</code> + <code>[ref]: url</code></td><td>&lt;img&gt; 引用式</td></tr>
<tr><td>自动链接</td><td><code>&lt;https://...&gt;</code></td><td>&lt;a&gt;</td></tr>
<tr><td>数学公式</td><td><code>$E=mc^2$</code></td><td>&lt;span&gt; / &lt;div&gt;</td></tr>
<tr><td>脚注</td><td><code>text[^1]</code></td><td>&lt;sup&gt; + 底部</td></tr>
<tr><td>定义列表</td><td><code>Term\n: def</code></td><td>&lt;dl&gt;&lt;dt&gt;&lt;dd&gt;</td></tr>
<tr><td>Emoji</td><td><code>:smile:</code> <code>:rocket:</code></td><td>😊 🚀(150+</td></tr>
<tr><td>反斜杠转义 <span class="badge">v0.1.15</span></td><td><code>\*</code> <code>\_</code></td><td>取消标点特殊含义</td></tr>
</table>
<h3>XSS 防护</h3>
<p>所有文本经 HTML 转义。URL 自动过滤 <code>javascript:</code> <code>vbscript:</code> <code>file:</code> 及非图片 <code>data:</code> 协议。</p>
<h4>XSS 防护</h4>
<p>所有文本经 <code>escapeHTML</code> 转义,URL 过滤 <code>javascript:</code> / <code>vbscript:</code> / <code>file:</code> / 非图片 <code>data:</code>,提供 <code>sanitize</code> 钩子。</p>
</section>
<!-- ======== 插件 ======== -->
<section id="plugins">
<h2>插件约定</h2>
<section id="parser-api"><h2>解析器 API</h2>
<h3>parseTokens / renderTokens</h3>
<pre><span class="c-kw">import</span> <span class="c-punc">{</span> parseTokens<span class="c-punc">,</span> renderTokens <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-kw">const</span> <span class="c-punc">{</span> tokens<span class="c-punc">,</span> footnotes <span class="c-punc">}</span> <span class="c-punc">=</span> <span class="c-fn">parseTokens</span><span class="c-punc">(</span><span class="c-str">'# Hello\n\n**world**'</span><span class="c-punc">);</span>
tokens<span class="c-punc">.</span><span class="c-fn">unshift</span><span class="c-punc">({</span> type<span class="c-punc">:</span> <span class="c-str">'hr'</span> <span class="c-punc">});</span> <span class="c-com">// 渲染前变换</span>
<span class="c-kw">const</span> html <span class="c-punc">=</span> <span class="c-fn">renderTokens</span><span class="c-punc">(</span>tokens<span class="c-punc">,</span> env<span class="c-punc">,</span> footnotes<span class="c-punc">);</span></pre>
<h3>registerBlockHandler</h3>
<pre><span class="c-kw">import</span> <span class="c-punc">{</span> registerBlockHandler <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-fn">registerBlockHandler</span><span class="c-punc">({</span>
name<span class="c-punc">:</span> <span class="c-str">'custom'</span><span class="c-punc">,</span> priority<span class="c-punc">:</span> <span class="c-num">8.5</span><span class="c-punc">,</span>
test<span class="c-punc">:</span> <span class="c-punc">(</span>line<span class="c-punc">)</span> <span class="c-punc">=></span> line<span class="c-punc">.</span><span class="c-fn">match</span><span class="c-punc">(</span><span class="c-re">/^:::/</span><span class="c-punc">),</span>
parse<span class="c-punc">:</span> <span class="c-punc">(</span>lines<span class="c-punc">,</span> i<span class="c-punc">,</span> match<span class="c-punc">)</span> <span class="c-punc">=></span> <span class="c-punc">({</span> token<span class="c-punc">:</span> <span class="c-punc">{</span> type<span class="c-punc">:</span> <span class="c-str">'alert'</span> <span class="c-punc">},</span> newIndex<span class="c-punc">:</span> j <span class="c-punc">})</span>
<span class="c-punc">});</span></pre>
</section>
<!-- 插件 -->
<section id="plugins-convention"><h2>插件约定</h2>
<pre><span class="c-kw">const</span> myPlugin <span class="c-punc">=</span> <span class="c-punc">{</span>
name<span class="c-punc">:</span> <span class="c-str">'myPlugin'</span><span class="c-punc">,</span>
description<span class="c-punc">:</span> <span class="c-str">'我的自定义插件'</span><span class="c-punc">,</span>
<span class="c-fn">install</span><span class="c-punc">(</span>editor<span class="c-punc">,</span> options<span class="c-punc">)</span> <span class="c-punc">{</span>
<span class="c-com">// this 指向插件对象本身</span>
<span class="c-kw">this</span><span class="c-punc">.</span>_timer <span class="c-punc">=</span> <span class="c-kw">null</span><span class="c-punc">;</span>
editor<span class="c-punc">.</span><span class="c-fn">on</span><span class="c-punc">(</span><span class="c-str">'change'</span><span class="c-punc">,</span> <span class="c-kw">this</span><span class="c-punc">.</span>_onChange<span class="c-punc">);</span>
<span class="c-punc">},</span>
<span class="c-fn">destroy</span><span class="c-punc">(</span>editor<span class="c-punc">)</span> <span class="c-punc">{</span>
<span class="c-kw">if</span> <span class="c-punc">(</span><span class="c-kw">this</span><span class="c-punc">.</span>_timer<span class="c-punc">)</span> <span class="c-fn">clearTimeout</span><span class="c-punc">(</span><span class="c-kw">this</span><span class="c-punc">.</span>_timer<span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">off</span><span class="c-punc">(</span><span class="c-str">'change'</span><span class="c-punc">,</span> <span class="c-kw">this</span><span class="c-punc">.</span>_onChange<span class="c-punc">);</span>
<span class="c-punc">},</span>
name<span class="c-punc">:</span> <span class="c-str">'myPlugin'</span><span class="c-punc">,</span> description<span class="c-punc">:</span> <span class="c-str">'我的插件'</span><span class="c-punc">,</span>
<span class="c-fn">install</span><span class="c-punc">(</span>editor<span class="c-punc">)</span> <span class="c-punc">{</span> editor<span class="c-punc">.</span><span class="c-fn">on</span><span class="c-punc">(</span><span class="c-str">'change'</span><span class="c-punc">,</span> <span class="c-kw">this</span><span class="c-punc">.</span>_onChange<span class="c-punc">);</span> <span class="c-punc">},</span>
<span class="c-fn">destroy</span><span class="c-punc">(</span>editor<span class="c-punc">)</span> <span class="c-punc">{</span> editor<span class="c-punc">.</span><span class="c-fn">off</span><span class="c-punc">(</span><span class="c-str">'change'</span><span class="c-punc">,</span> <span class="c-kw">this</span><span class="c-punc">.</span>_onChange<span class="c-punc">);</span> <span class="c-punc">},</span>
<span class="c-punc">};</span></pre>
</section>
<section id="preset-plugins">
<h3>预设插件</h3>
<h4>autoSave — 自动保存草稿</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>presetPlugins<span class="c-punc">.</span>autoSave<span class="c-punc">,</span> <span class="c-punc">{</span>
key<span class="c-punc">:</span> <span class="c-str">'me-draft-'</span> <span class="c-punc">+</span> editor<span class="c-punc">.</span>id<span class="c-punc">,</span> <span class="c-com">// localStorage key</span>
delay<span class="c-punc">:</span> <span class="c-num">1000</span><span class="c-punc">,</span> <span class="c-com">// 防抖延迟(ms</span>
<span class="c-punc">});</span>
editor<span class="c-punc">.</span><span class="c-fn">restoreDraft</span><span class="c-punc">();</span> <span class="c-com">// 恢复草稿</span>
editor<span class="c-punc">.</span><span class="c-fn">clearDraft</span><span class="c-punc">();</span> <span class="c-com">// 清除草稿</span>
editor<span class="c-punc">.</span><span class="c-fn">getDraftKey</span><span class="c-punc">();</span> <span class="c-com">// 获取存储 key</span></pre>
<h4>exportTool — 导出文件</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>presetPlugins<span class="c-punc">.</span>exportTool<span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">exportMarkdown</span><span class="c-punc">(</span><span class="c-str">'my-doc.md'</span><span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">exportHTML</span><span class="c-punc">(</span><span class="c-str">'my-doc.html'</span><span class="c-punc">,</span> <span class="c-punc">{</span>
title<span class="c-punc">:</span> <span class="c-str">'文档标题'</span><span class="c-punc">,</span>
css<span class="c-punc">:</span> <span class="c-str">'body { font-family: sans-serif; }'</span><span class="c-punc">,</span>
lang<span class="c-punc">:</span> <span class="c-str">'zh-CN'</span><span class="c-punc">,</span>
<span class="c-punc">});</span></pre>
<h4>searchReplace — 查找替换</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>presetPlugins<span class="c-punc">.</span>searchReplace<span class="c-punc">);</span>
<span class="c-com">// Ctrl+F → 查找 Ctrl+H → 替换</span>
<span class="c-com">// Enter → 下一个 Shift+Enter → 上一个 Escape → 关闭</span></pre>
<h4>imagePaste — 粘贴图片转 base64</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span>presetPlugins<span class="c-punc">.</span>imagePaste<span class="c-punc">);</span>
<span class="c-com">// 在编辑区 Ctrl+V 粘贴剪贴板图片</span>
<span class="c-com">// 自动插入 ![](data:image/png;base64,...)</span></pre>
<section id="preset-plugins"><h3>预设插件</h3>
<h4>autoSave</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'autoSave'</span><span class="c-punc">,</span> <span class="c-punc">{</span> key<span class="c-punc">:</span> <span class="c-str">'draft-key'</span><span class="c-punc">,</span> delay<span class="c-punc">:</span> <span class="c-num">1000</span> <span class="c-punc">});</span>
editor<span class="c-punc">.</span><span class="c-fn">restoreDraft</span><span class="c-punc">();</span> editor<span class="c-punc">.</span><span class="c-fn">clearDraft</span><span class="c-punc">();</span></pre>
<h4>exportTool</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'exportTool'</span><span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">exportMarkdown</span><span class="c-punc">(</span><span class="c-str">'doc.md'</span><span class="c-punc">);</span> editor<span class="c-punc">.</span><span class="c-fn">exportHTML</span><span class="c-punc">(</span><span class="c-str">'doc.html'</span><span class="c-punc">);</span></pre>
<h4>searchReplace</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'searchReplace'</span><span class="c-punc">);</span>
<span class="c-com">// Ctrl+F 查找 / Ctrl+H 替换</span></pre>
<h4>imagePaste</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'imagePaste'</span><span class="c-punc">);</span> <span class="c-com">// 粘贴图片自动转 base64</span></pre>
<h4>shortcutHelp</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'shortcutHelp'</span><span class="c-punc">);</span> <span class="c-com">// 按 ? 弹出快捷键面板</span></pre>
<h4>fileSystem</h4>
<pre>editor<span class="c-punc">.</span><span class="c-fn">use</span><span class="c-punc">(</span><span class="c-str">'fileSystem'</span><span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">openFile</span><span class="c-punc">();</span> editor<span class="c-punc">.</span><span class="c-fn">saveFile</span><span class="c-punc">();</span> editor<span class="c-punc">.</span><span class="c-fn">saveFileAs</span><span class="c-punc">();</span></pre>
</section>
<!-- ======== 主题 ======== -->
<section id="themes">
<h2>主题系统</h2>
<!-- 主题 -->
<section id="themes"><h2>主题系统</h2>
<pre><span class="c-com">// 实例级</span>
editor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'dark'</span><span class="c-punc">);</span> editor<span class="c-punc">.</span><span class="c-fn">getTheme</span><span class="c-punc">();</span>
<pre><span class="c-com">// 全局切换主题</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'light'</span><span class="c-punc">);</span> <span class="c-com">// 亮色</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'dark'</span><span class="c-punc">);</span> <span class="c-com">// 暗色</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'warm'</span><span class="c-punc">);</span> <span class="c-com">// 暖色</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'auto'</span><span class="c-punc">);</span> <span class="c-com">// 自动(默认)</span>
<span class="c-com">// 实例级主题(v0.1.5,每个编辑器独立)</span>
editor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span><span class="c-str">'dark'</span><span class="c-punc">);</span>
editor<span class="c-punc">.</span><span class="c-fn">getTheme</span><span class="c-punc">();</span> <span class="c-com">// 'dark'</span>
editor<span class="c-punc">.</span><span class="c-fn">on</span><span class="c-punc">(</span><span class="c-str">'themeChange'</span><span class="c-punc">,</span> <span class="c-punc">({</span> theme <span class="c-punc">})</span> <span class="c-punc">=></span> <span class="c-punc">{});</span>
<span class="c-com">// 注册自定义主题(v0.1.5: 支持继承)</span>
<span class="c-com">// 注册自定义</span>
<span class="c-kw">import</span> <span class="c-punc">{</span> themeUtils <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
themeUtils<span class="c-punc">.</span><span class="c-fn">registerTheme</span><span class="c-punc">(</span><span class="c-str">'ocean'</span><span class="c-punc">,</span> <span class="c-punc">{</span>
extends<span class="c-punc">:</span> <span class="c-str">'dark'</span><span class="c-punc">,</span> <span class="c-com">// 继承 dark 主题</span>
accent<span class="c-punc">:</span> <span class="c-str">'#00ddff'</span><span class="c-punc">,</span>
<span class="c-punc">});</span>
themeUtils<span class="c-punc">.</span><span class="c-fn">registerTheme</span><span class="c-punc">(</span><span class="c-str">'ocean'</span><span class="c-punc">,</span> <span class="c-punc">{</span> extends<span class="c-punc">:</span> <span class="c-str">'dark'</span><span class="c-punc">,</span> accent<span class="c-punc">:</span> <span class="c-str">'#00ddff'</span> <span class="c-punc">});</span>
<span class="c-com">// 外部主题跟随(v0.1.5</span>
themeUtils<span class="c-punc">.</span><span class="c-fn">followExternalTheme</span><span class="c-punc">(</span>
<span class="c-punc">{</span> element<span class="c-punc">:</span> document<span class="c-punc">.</span>body<span class="c-punc">,</span> attr<span class="c-punc">:</span> <span class="c-str">'data-theme'</span> <span class="c-punc">},</span>
<span class="c-punc">(</span>theme<span class="c-punc">)</span> <span class="c-punc">=></span> editor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span>theme<span class="c-punc">)</span>
<span class="c-punc">);</span>
<span class="c-com">// 从父容器继承主题</span>
themeUtils<span class="c-punc">.</span><span class="c-fn">adoptFromParent</span><span class="c-punc">(</span>editor<span class="c-punc">.</span>container<span class="c-punc">,</span>
<span class="c-punc">(</span>theme<span class="c-punc">)</span> <span class="c-punc">=></span> editor<span class="c-punc">.</span><span class="c-fn">setTheme</span><span class="c-punc">(</span>theme<span class="c-punc">)</span>
<span class="c-punc">);</span>
<span class="c-com">// 外部跟随</span>
editor<span class="c-punc">.</span><span class="c-fn">getThemeContext</span><span class="c-punc">().</span><span class="c-fn">adopt</span><span class="c-punc">();</span> <span class="c-com">// 从父容器继承</span>
<span class="c-com">// CSS 变量导出</span>
themeUtils<span class="c-punc">.</span><span class="c-fn">exportCSSVars</span><span class="c-punc">();</span> <span class="c-com">// { '--md-bg': '#fff', ... }</span>
themeUtils<span class="c-punc">.</span><span class="c-fn">getCSSVariable</span><span class="c-punc">(</span><span class="c-str">'accent'</span><span class="c-punc">);</span> <span class="c-com">// '#3b82f6'</span></pre>
<p>CSS 变量限定在 <code>.me-wrapper</code> 元素上,不污染全局。支持 <code>@media print</code> 打印样式。v0.1.5 新增实例级主题隔离、外部跟随、主题继承。</p>
themeUtils<span class="c-punc">.</span><span class="c-fn">exportCSSVars</span><span class="c-punc">();</span> themeUtils<span class="c-punc">.</span><span class="c-fn">getCSSVariable</span><span class="c-punc">(</span><span class="c-str">'accent'</span><span class="c-punc">);</span></pre>
</section>
<!-- ======== 国际化 ======== -->
<section id="i18n">
<h2>国际化</h2>
<!-- 国际化 -->
<section id="i18n"><h2>国际化</h2>
<pre><span class="c-com">// 切换</span>
editor<span class="c-punc">.</span><span class="c-fn">setLocale</span><span class="c-punc">(</span><span class="c-str">'en-US'</span><span class="c-punc">);</span> MeEditor<span class="c-punc">.</span><span class="c-fn">setLocale</span><span class="c-punc">(</span><span class="c-str">'zh-CN'</span><span class="c-punc">);</span>
<pre><span class="c-com">// 切换语言</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setLocale</span><span class="c-punc">(</span><span class="c-str">'en-US'</span><span class="c-punc">);</span>
MeEditor<span class="c-punc">.</span><span class="c-fn">setLocale</span><span class="c-punc">(</span><span class="c-str">'zh-CN'</span><span class="c-punc">);</span>
<span class="c-com">// 翻译函数</span>
<span class="c-com">// 添加翻译</span>
<span class="c-kw">import</span> <span class="c-punc">{</span> i18nUtils <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">t</span><span class="c-punc">(</span><span class="c-str">'bold'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; '粗体'zh-CN</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">t</span><span class="c-punc">(</span><span class="c-str">'bold'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; 'Bold'en-US</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">addTranslations</span><span class="c-punc">(</span><span class="c-str">'ja'</span><span class="c-punc">,</span> <span class="c-punc">{</span> bold<span class="c-punc">:</span> <span class="c-str">'太字'</span> <span class="c-punc">});</span>
<span class="c-com">// 添加语言</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">addTranslations</span><span class="c-punc">(</span><span class="c-str">'ja'</span><span class="c-punc">,</span> <span class="c-punc">{</span> bold<span class="c-punc">:</span> <span class="c-str">'太字'</span><span class="c-punc">,</span> italic<span class="c-punc">:</span> <span class="c-str">'斜体'</span> <span class="c-punc">});</span>
<span class="c-com">// 远程加载</span>
<span class="c-kw">await</span> i18nUtils<span class="c-punc">.</span><span class="c-fn">loadRemote</span><span class="c-punc">(</span><span class="c-str">'/locales/ja.json'</span><span class="c-punc">,</span> <span class="c-str">'ja'</span><span class="c-punc">);</span>
<span class="c-com">// 格式化</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">formatNumber</span><span class="c-punc">(</span><span class="c-num">1234567</span><span class="c-punc">);</span> <span class="c-com">// =&gt; '1,234,567'</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">formatCurrency</span><span class="c-punc">(</span><span class="c-num">99.99</span><span class="c-punc">,</span> <span class="c-str">'USD'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; '$99.99'</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">formatDate</span><span class="c-punc">(</span><span class="c-str">'2024-01-15'</span><span class="c-punc">);</span> <span class="c-com">// =&gt; '1/15/2024'</span></pre>
i18nUtils<span class="c-punc">.</span><span class="c-fn">formatNumber</span><span class="c-punc">(</span><span class="c-num">1234567</span><span class="c-punc">);</span>
i18nUtils<span class="c-punc">.</span><span class="c-fn">formatDate</span><span class="c-punc">(</span><span class="c-str">'2024-01-15'</span><span class="c-punc">);</span></pre>
</section>
</main>
@@ -619,24 +433,18 @@ i18nUtils<span class="c-punc">.</span><span class="c-fn">formatDate</span><span
<footer>
<div class="container">
MetonaEditor v0.1.10 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
MetonaEditor v0.2.4 · TypeScript · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
</div>
</footer>
<script>
// 高亮当前 section 的侧边栏链接
const sections = document.querySelectorAll('section[id]');
const links = document.querySelectorAll('.sidebar a');
window.addEventListener('scroll', () => {
const sections = document.querySelectorAll('section[id]');
const links = document.querySelectorAll('.sidebar a');
window.addEventListener('scroll', () => {
let current = '';
sections.forEach(s => {
if (window.scrollY >= s.offsetTop - 100) current = s.id;
});
links.forEach(a => {
a.classList.toggle('active', a.getAttribute('href') === '#' + current);
});
});
sections.forEach(s => { if (window.scrollY >= s.offsetTop - 100) current = s.id; });
links.forEach(a => { a.classList.toggle('active', a.getAttribute('href') === '#' + current); });
});
</script>
</body>
</html>
+2 -2
View File
@@ -244,7 +244,7 @@
<!-- 14. 错误回调 -->
<div class="card">
<div class="label">🛡️ onError 错误捕获 <span style="color:#34d399;font-size:10px;">v0.1.2</span></div>
<div class="label">🛡️ onError 错误捕获 <span style="color:#34d399;font-size:10px;">v0.2.1</span></div>
<div class="btn-row">
<button class="btn c-e" onclick="demoOnError()">演示 onError</button>
<button class="btn c-w" onclick="demoOnErrorReset()">重置回调</button>
@@ -282,7 +282,7 @@
</main>
<footer>
MetonaToast v0.1.2 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
MetonaToast v0.2.1 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
</footer>
<script src="../dist/metona-toast.js"></script>
+1 -1
View File
@@ -87,7 +87,7 @@
<main class="main">
<h1>API 文档</h1>
<p>MetonaToast v0.1.2 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
<p>MetonaToast v0.2.1 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
<!-- ===== 基础通知 ===== -->
<h2 id="show">show(message, opts?)</h2>
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "marklite",
"version": "0.4.3",
"version": "0.4.4",
"description": "Lightweight Markdown Editor for Windows",
"main": "./dist/main/index.js",
"scripts": {
@@ -28,8 +28,8 @@
"author": "MarkLite",
"license": "MIT",
"dependencies": {
"@metona-team/metona-editor": "0.1.14",
"@metona-team/metona-toast": "^0.1.2",
"@metona-team/metona-editor": "0.2.4",
"@metona-team/metona-toast": "0.2.1",
"dexie": "^4.0.11",
"nanoid": "^5.1.5",
"react": "^18.3.1",
+29 -9
View File
@@ -1,17 +1,15 @@
import React, { useEffect, useRef } from 'react'
import MeEditor from '@metona-team/metona-editor'
import type { MarkdownEditor, MarkdownEditorOptions, EditMode, ThemeName } from '@metona-team/metona-editor'
import type { MarkdownEditor } from '@metona-team/metona-editor'
import { useTabStore } from '../../stores/tabStore'
import { setMetonaEditorGetter, useEditorStore } from '../../stores/editorStore'
import { settingsRepository } from '../../db/settingsRepository'
import { renderMarkdownSync } from '../../lib/markdown'
import type { ThemeMode } from '../../types/settings'
// v0.1.9 demo 验证过但类型声明未收录的配置项
interface ExtendedEditorOptions extends MarkdownEditorOptions {
lineNumbers?: boolean
autoBrackets?: boolean
}
// v0.2.4: 这些类型不再作为命名导出暴露,本地声明以保持类型安全
type EditMode = 'edit' | 'split' | 'preview'
type ThemeName = 'light' | 'dark' | 'auto' | 'warm' | string
interface EditorProps {
themeMode: ThemeMode
@@ -37,6 +35,7 @@ const EDITOR_PLUGINS: string[] = [
'searchReplace', // Ctrl+F/Ctrl+H
'imagePaste', // Ctrl+V 粘贴图片
'exportTool', // 导出 MD/HTML
'shortcutHelp', // 按 ? 弹出快捷键面板
]
/**
@@ -64,7 +63,7 @@ export const Editor = React.memo(function Editor({ themeMode, onAppSave }: Edito
const container = containerRef.current
if (!container) return
const config: ExtendedEditorOptions = {
const config = {
value: activeTab?.content ?? '',
mode: mapViewMode(viewMode),
height: '100%',
@@ -87,6 +86,12 @@ export const Editor = React.memo(function Editor({ themeMode, onAppSave }: Edito
autoBrackets: true,
readOnly: viewMode === 'preview',
plugins: EDITOR_PLUGINS,
// v0.2.4 新增配置项
syncScroll: true,
wordWrap: true,
outline: false, // 使用自研 OutlinePanel
historyLimit: 100,
historyDebounce: 400,
// 使用 unified 管线渲染,保留图片路径修复能力
render: (md: string) => {
@@ -109,8 +114,8 @@ export const Editor = React.memo(function Editor({ themeMode, onAppSave }: Edito
},
// 模式切换 → 同步到 editorStore 并持久化
onModeChange: (mode: EditMode) => {
const mapped = reverseMapMode(mode)
onModeChange: (mode: string) => {
const mapped = reverseMapMode(mode as EditMode)
setViewMode(mapped)
settingsRepository.save({ viewMode: mapped })
},
@@ -118,7 +123,22 @@ export const Editor = React.memo(function Editor({ themeMode, onAppSave }: Edito
// Ctrl+S → 触发应用层保存(Electron IPC 写文件系统)
onSave: () => {
onAppSave?.()
},
// v0.2.4 新增回调: 链接点击 → 安全打开外部链接
onLinkClick: (href: string) => {
if (window.electronAPI?.openExternal) {
window.electronAPI.openExternal(href)
}
},
// v0.2.4 新增回调: 焦点事件(预留扩展点)
onFocus: () => {
// 编辑器获得焦点
},
onBlur: () => {
// 编辑器失去焦点
},
}
const editor = MeEditor.create(container, config)
+9
View File
@@ -5,6 +5,8 @@ import MeToast from '@metona-team/metona-toast'
*
* 替代原自研 Toast 组件(components/Toast),由 MeToast 接管全部通知渲染。
* MeToast 自管理 DOM 与样式,无需在 React 树中挂载容器组件。
*
* v0.4.4: 升级 MeToast 0.2.1,安装 keyboard / accessibility 插件。
*/
MeToast.configure({
position: 'top-right',
@@ -17,8 +19,15 @@ MeToast.configure({
showProgress: true,
draggable: true,
locale: 'zh-CN',
width: 360,
})
// 安装内置插件
// eslint-disable-next-line react-hooks/rules-of-hooks -- MeToast.use() 是插件安装方法,非 React Hook
MeToast.use('keyboard') // ESC 关闭所有 Toast
// eslint-disable-next-line react-hooks/rules-of-hooks
MeToast.use('accessibility') // 屏幕阅读器实时朗读 Toast 内容
/** 与原 showToast 保持兼容的类型子集 */
export type ToastType = 'success' | 'error' | 'warning' | 'info'
+1 -1
View File
@@ -1,5 +1,5 @@
// 共享常量 — 主进程和渲染进程共用
export const APP_VERSION = 'v0.4.3'
export const APP_VERSION = 'v0.4.4'
export const MAX_FILE_SIZE = 20 * 1024 * 1024 // 20MB
export const ALLOWED_EXTENSIONS = ['.md', '.markdown', '.txt'] as const
export const SKIP_DIRS = new Set([