Files
thzxx d6a618af5d feat: v0.4.3 — 浮动工具栏定位引擎重做 + 程序化编辑统一刷新管线
- 浮动工具栏 mirror 镜像测量:隐藏镜像层复制 textarea 排版样式,选区锚点插零宽字符读像素坐标,CJK/软换行/跨行选区误差全部消除

- 实测尺寸替代魔法数字;滚动 rAF 节流重定位;滚出视口自动隐藏;a11y:role=toolbar + aria-label、visibility 移出 Tab 序列、键盘可达

- 修复死开关/监听器泄漏/销毁后 focus 崩溃/blur 焦点抖动误隐藏

- _afterProgrammaticEdit 统一 8 条程序化路径的渲染/行号/字数/大纲/事件刷新;右键菜单 label 转义、copyAsHTML Firefox 降级、paste 失败提示、Esc 退出全屏

- 测试 871 → 895,e2e 22 → 28 项断言(含真实浏览器浮动工具栏布局验证)
2026-08-20 20:43:01 +08:00

305 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN" data-md-theme="auto">
<head>
<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.4.3 — 全功能演示</title>
<style>
*,*::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}
.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)}
.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}
.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)}
.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}
.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 v0.4.3</h1>
<div class="badges">
<span class="badge">TypeScript</span>
<span class="badge">零运行时依赖</span>
<span class="badge">895 tests</span>
<span class="badge">6 个插件</span>
<span class="badge">桌面端优先</span>
</div>
</header>
<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="selTheme"><option value="auto">🌓 跟随系统</option><option value="light">☀️ 亮色</option><option value="dark">🌙 暗色</option><option value="warm">🔥 暖色</option></select>
<label>语言</label>
<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>
<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>
<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>
<div class="info-grid">
<div class="info-card"><div class="icon">🏗️</div><h4>TypeScript 全模块重构</h4><p>17 个 .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>零依赖 tokenizerjs / ts / python / bash 等 16 个语言标识</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>工具栏自动隐藏,宽度可配置(zenMaxWidth,默认 960px</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 class="info-card"><div class="icon"></div><h4>增量性能</h4><p>字数/词数/行数差异统计,击键零全量扫描;高亮规则缓存;大纲 O(n)</p></div>
</div>
<footer class="footer">
MetonaEditor v0.4.3 · 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(){
var demoMd=[
'# 🚀 MetonaEditor v0.4.3 全功能演示',
'',
'> TypeScript 重构 · 零运行时依赖 · **桌面端** Markdown Editor 库。',
'> 全模块 TypeScript 严格模式,17 个源文件,895 个测试全部通过。',
'',
'## ✨ v0.4.3 新特性',
'',
'- **浮动工具栏定位重做**:mirror 镜像测量像素级定位,中文混排 / 长行软换行 / 跨行选区精确跟随',
'- **滚动跟随**:选区滚出可视区自动隐藏,滚动时节流重定位,分隔条拖拽与窗口缩放后自动隐藏',
'- **键盘可达**:隐藏态移出 Tab 序列,按钮支持 Enter / Space 执行,`role="toolbar"` + aria-label',
'- **程序化编辑统一刷新管线**:命令 / 快捷键 / 插件路径的预览 / 行号 / 字数 / 大纲刷新与 input / change 事件完全对齐原生输入',
'- **修复 13 处问题**:浮动工具栏死开关 · 监听器泄漏 · 销毁后 focus 崩溃 · 右键菜单注入 · copyAsHTML 在 Firefox 崩溃 · Esc 退出全屏等',
'- **测试补强**871 → 895 用例,e2e 冒烟 22 → 28 项断言(含真实浏览器布局验证)',
'',
'## 📐 文本样式',
'',
'- **粗体** / __另一种粗体__',
'- ***粗斜体*** / ___另一种粗斜体___',
'- *斜体* / _另一种斜体_',
'- ~~删除线~~ / ==高亮标记==',
'- <u>下划线</u>Ctrl+U)· 链接内格式: [**粗体链接**](https://example.com)',
'- 化学:H~2~O / 数学:x^2^',
'- 代码:`const x = 42;`',
'- Emoji:smile: :rocket: :fire: :rainbow: :coffee:',
'',
'## 📊 数学公式 & 图表',
'',
'行内:$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.4.3 | 2026-08 | 895 | 浮动工具栏重做+统一编辑管线 |',
'| v0.4.2 | 2026-08 | 871 | 审查清偿+大纲API+8项修复 |',
'| v0.4.1 | 2026-08 | 841 | Zen宽度可配置+5项修复 |',
'| v0.4.0 | 2026-08 | 826 | E2E冒烟+实例i18n |',
'| v0.3.1 | 2026-08 | 821 | 增量统计+高亮缓存 |',
'| v0.3.0 | 2026-08 | 801 | 安全修复+缓存指纹 |',
'| v0.2.5 | 2026-08 | 782 | 内置高亮+安全修复 |',
'| 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] TypeScript 全模块重构',
'- [x] 解析器 99% 行覆盖率',
'- [x] parseTokens / renderTokens 分离',
'- [x] registerBlockHandler 扩展',
'- [x] 6 个预设插件完整测试',
'- [ ] VS Code 扩展',
'',
'## 🎯 嵌套列表',
'',
'1. 一级有序',
' 1. 二级有序(自动递增)',
' 2. 第二项',
'2. 回到一级',
' - 混杂无序列表',
' - 第二项',
' - [x] 三级嵌套 + 任务',
'',
'## 💻 代码块',
'',
'```ts',
'import MeEditor from "@metona-team/metona-editor";',
'',
'const editor = MeEditor.create("#editor", {',
' mode: "split",',
' theme: "dark",',
' lineNumbers: true,',
' autoBrackets: true,',
' plugins: ["autoSave", "fileSystem", "shortcutHelp"],',
' onChange: (value, editor) => {',
' console.log("changed:", value.length, "chars");',
' },',
'});',
'',
'// 链式 API',
'editor.exec("bold").exec("h1").focus();',
'```',
'',
'```python',
'def fib(n):',
' """返回斐波那契数列第 n 项"""',
' a, b = 0, 1',
' for _ in range(n):',
' a, b = b, a + b',
' return a',
'',
'print(fib(10)) # 55',
'```',
'',
'```bash',
'# 安装(Gitea 私有源)',
'npm install @metona-team/metona-editor --registry=https://git.metona.cn/api/packages/MetonaTeam/npm/',
'npm run dev',
'```',
'',
'## 📌 脚注',
'',
'这是一个带脚注的句子[^1],另一个脚注[^note]。',
'',
'[^1]: 脚注可以包含 **格式化** 和 `代码`。',
'[^note]: 第二个脚注的说明。',
'',
'---',
'',
'> **提示:** 按 `?` 查看快捷键 · 拖放文件到编辑区 · Ctrl+F 查找 · Ctrl+H 替换',
'> 在列表行尾 Enter 体验智能延续 · 选中文字后按 `(` 体验自动包裹',
'> 点击「📂 打开」加载磁盘 .md 文件 · 点击「💾 保存」写入磁盘',
].join('\n');
var editor = MeEditor.create('#editor',{
value: demoMd,
mode: 'split', height: 620,
lineNumbers: true, autoBrackets: true, wordCount: true,
theme: 'auto', locale: 'zh-CN', outline: false,
highlight: MeEditor.highlight,
plugins: ['autoSave','exportTool','searchReplace','imagePaste','shortcutHelp','fileSystem'],
onLinkClick: function(uri, text, ed){
ed.toast('链接: ' + uri, { type: 'info', duration: 2000 });
}
});
// Control bar
var bar = document.getElementById('bar');
document.getElementById('selTheme').addEventListener('change', function(){
editor.setTheme(this.value);
});
document.getElementById('selLocale').addEventListener('change', function(){
editor.setLocale(this.value);
});
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){
// v0.4.2 起使用公开 API setOutline/isOutline(替代操作私有 config/_buildOutline 的写法)
var next = !editor.isOutline();
editor.setOutline(next);
btn.classList.toggle('on', next);
}
// 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>
</html>