feat(site): complete demo.html redesign with all features enabled

- Modern glassmorphism design with gradient header
- Full control bar: theme/locale/mode/line numbers/outline toggle
- All 4 preset plugins active: autoSave, exportTool, searchReplace, imagePaste
- Rich demo content showcasing all v0.1.7 syntax features
- Toast notification demo button
- Custom shortcut (Ctrl+Shift+T) demo
- Responsive layout with info cards grid
- Light/dark theme CSS variable integration
This commit is contained in:
2026-07-24 10:36:55 +08:00
parent 289b8e55a6
commit a7a66e113f
+438 -364
View File
@@ -1,444 +1,518 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN" data-md-theme="auto">
<head> <head>
<meta charset="UTF-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>MetonaEditor · 功能演示</title> <title>MetonaEditor v0.1.7 — 在线演示</title>
<style> <style>
* { box-sizing: border-box; margin: 0; padding: 0; } *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root { :root {
--bg: #f5f6f8; --app-bg: #f5f6f8;
--card: #ffffff; --app-text: #1a1a2e;
--text: #1f2937; --app-card: #ffffff;
--muted: #6b7280; --app-border: #e5e7eb;
--border: #e5e7eb; --app-accent: #3b82f6;
--accent: #3b82f6; --app-accent2: #8b5cf6;
--accent-soft: rgba(59, 130, 246, 0.1);
--danger: #ef4444; /* dark overrides */
--success: #10b981; --header-bg: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
--code-bg: #1c2029; --header-text: #f1f5f9;
--code-text: #e6e8eb; --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%);
}
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg); background: var(--app-bg);
color: var(--text); color: var(--app-text);
min-height: 100vh;
line-height: 1.6; line-height: 1.6;
padding: 24px; transition: background 0.3s, color 0.3s;
} }
.wrap { max-width: 1120px; margin: 0 auto; }
/* 顶栏 */ /* ====== Header ====== */
.topbar { .header {
display: flex; align-items: center; justify-content: space-between; background: var(--header-bg);
flex-wrap: wrap; gap: 12px; color: var(--header-text);
margin-bottom: 20px; padding: 32px 24px 24px;
text-align: center;
position: relative;
overflow: hidden;
} }
.topbar h1 { font-size: 22px; font-weight: 700; display: flex; align-items: center; gap: 12px; } .header::before {
.topbar h1 a { content: '';
color: var(--accent); text-decoration: none; position: absolute;
font-size: 13px; font-weight: 400; top: -60px; right: -60px;
padding: 4px 10px; border: 1px solid var(--border); border-radius: 6px; width: 300px; height: 300px;
transition: border-color 0.15s; background: radial-gradient(circle, rgba(139,92,246,0.3) 0%, transparent 70%);
border-radius: 50%;
} }
.topbar h1 a:hover { border-color: var(--accent); } .header h1 {
.controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } font-size: 2rem;
.ctrl-label { font-size: 12px; color: var(--muted); margin-right: 4px; } font-weight: 800;
.ctrl-group { letter-spacing: -0.5px;
display: inline-flex; gap: 0; position: relative;
background: var(--card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
} }
.ctrl-btn { .header h1 span.grad {
padding: 7px 14px; border: 0; background: transparent; cursor: pointer; background: linear-gradient(135deg, var(--app-accent), var(--app-accent2));
font-size: 13px; color: var(--text); transition: background 0.15s, color 0.15s; -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);
} }
.ctrl-btn:hover { background: #f3f4f6; }
.ctrl-btn.active { background: var(--accent); color: #fff; }
.ctrl-btn + .ctrl-btn { border-left: 1px solid var(--border); }
/* 编辑器 */ /* ====== Toolbar Strip ====== */
#editor { margin-bottom: 24px; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 20px -8px rgba(0,0,0,0.1); } .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; }
/* 网格布局 */ .toggle-group { display: flex; gap: 2px; }
.grid { .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; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px; gap: 16px;
margin-bottom: 16px; max-width: 1200px;
margin: 24px auto;
padding: 0 24px;
} }
@media (max-width: 768px) { .info-card {
.grid { grid-template-columns: 1fr; } background: var(--app-card);
} border: 1px solid var(--app-border);
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
padding: 16px 18px; padding: 18px 20px;
} transition: box-shadow 0.3s;
.panel-title {
font-size: 13px; color: var(--muted);
margin-bottom: 10px; font-weight: 600;
display: flex; align-items: center; justify-content: space-between;
}
.panel-title .count {
background: var(--accent-soft); color: var(--accent);
padding: 2px 8px; border-radius: 999px; font-size: 11px;
} }
.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 ====== */
.console { .footer {
background: var(--code-bg); color: var(--code-text); text-align: center;
border-radius: 8px; padding: 14px 16px; padding: 32px 24px;
font-family: "SF Mono", "Cascadia Code", Consolas, monospace; font-size: 12.5px; font-size: 13px;
min-height: 140px; max-height: 240px; overflow: auto; white-space: pre-wrap; word-break: break-all; color: var(--app-text);
line-height: 1.7; opacity: 0.5;
} }
.console:empty::before { content: "点击下方按钮查看输出..."; color: #6b7280; } .footer a { color: var(--app-accent); text-decoration: none; }
.console .log-time { color: #5c6370; }
.console .log-label { color: #82aaff; font-weight: 600; }
/* 事件流 */ /* ====== Responsive ====== */
.events { @media (max-width: 768px) {
background: var(--code-bg); color: var(--code-text); .header h1 { font-size: 1.5rem; }
border-radius: 8px; padding: 14px 16px; .toolbar-strip { padding: 8px 12px; gap: 6px; }
font-family: "SF Mono", Consolas, monospace; font-size: 12px; .toolbar-strip button { padding: 5px 10px; font-size: 12px; }
min-height: 140px; max-height: 240px; overflow: auto; .info-grid { grid-template-columns: 1fr 1fr; }
line-height: 1.7;
} }
.events:empty::before { content: "事件流将在这里实时显示..."; color: #6b7280; }
.event-line { padding: 2px 0; }
.event-line .ev-name { color: #c3e88d; }
.event-line .ev-time { color: #5c6370; }
/* API 按钮 */
.api-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 8px;
margin-top: 12px;
}
.api-btn {
padding: 9px 12px; border: 1px solid var(--border); background: var(--card);
border-radius: 7px; cursor: pointer; font-size: 12.5px; color: var(--text);
transition: all 0.15s; text-align: center; font-family: "SF Mono", Consolas, monospace;
}
.api-btn:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-1px); }
.api-btn:active { transform: translateY(0); }
.api-btn.danger:hover { border-color: var(--danger); color: var(--danger); }
.api-btn.success:hover { border-color: var(--success); color: var(--success); }
.hint {
font-size: 12px; color: var(--muted);
margin-top: 14px; padding: 10px 14px;
background: var(--accent-soft); border-radius: 8px;
border-left: 3px solid var(--accent);
}
code { background: #f3f4f6; padding: 1px 6px; border-radius: 4px; font-size: 12px; font-family: "SF Mono", Consolas, monospace; }
/* 状态指示 */
.status-bar {
display: flex; gap: 16px; flex-wrap: wrap;
padding: 10px 14px;
background: var(--card); border: 1px solid var(--border);
border-radius: 8px; margin-bottom: 16px;
font-size: 12px; color: var(--muted);
}
.status-item { display: inline-flex; align-items: center; gap: 6px; }
.status-item strong { color: var(--text); font-weight: 600; }
.status-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--success);
}
.status-dot.destroyed { background: var(--danger); }
</style> </style>
</head> </head>
<body> <body>
<div class="wrap">
<div class="topbar"> <header class="header">
<h1> <h1><span class="grad">Metona</span>Editor</h1>
MetonaEditor 功能演示 <div class="badges">
<a href="index.html">← 返回首页</a> <span class="badge">v0.1.7</span>
<a href="docs.html">📖 文档</a> <span class="badge">零依赖</span>
</h1> <span class="badge">532 tests</span>
<div class="controls"> <span class="badge">MIT</span>
<span class="ctrl-label">主题</span> <span class="badge">中文优先</span>
<div class="ctrl-group" id="themeCtrl">
<button class="ctrl-btn" data-theme="light">亮色</button>
<button class="ctrl-btn" data-theme="dark">暗色</button>
<button class="ctrl-btn" data-theme="warm">暖色</button>
</div>
<span class="ctrl-label">语言</span>
<div class="ctrl-group" id="localeCtrl">
<button class="ctrl-btn" data-locale="zh-CN">中文</button>
<button class="ctrl-btn" data-locale="en-US">English</button>
</div>
<span class="ctrl-label">模式</span>
<div class="ctrl-group" id="readonlyCtrl">
<button class="ctrl-btn" id="btnReadOnly">只读</button>
</div>
</div> </div>
</header>
<div class="toolbar-strip" id="controlBar">
<label>主题</label>
<select id="themeSelect">
<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>
<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> </div>
<div class="status-bar" id="statusBar"> <div class="spacer"></div>
<div class="status-item">
<span class="status-dot" id="statusDot"></span> <label>行号</label>
<span>状态:</span><strong id="stState">运行中</strong> <div class="toggle-group" id="lineNumBtns">
</div> <button class="on"></button>
<div class="status-item"><span>模式:</span><strong id="stMode">split</strong></div> <button></button>
<div class="status-item"><span>主题:</span><strong id="stTheme">light</strong></div>
<div class="status-item"><span>语言:</span><strong id="stLocale">zh-CN</strong></div>
<div class="status-item"><span>字数:</span><strong id="stWords">0</strong></div>
<div class="status-item"><span>全屏:</span><strong id="stFs"></strong></div>
</div> </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>
</div>
<div class="editor-section">
<div class="editor-card">
<div id="editor"></div> <div id="editor"></div>
<div class="grid">
<div class="panel">
<div class="panel-title">
<span>API 控制台</span>
<span class="count" id="apiCount">0 次调用</span>
</div>
<div class="console" id="console"></div>
<div class="api-grid">
<button class="api-btn" data-api="getValue">getValue()</button>
<button class="api-btn" data-api="getHTML">getHTML()</button>
<button class="api-btn" data-api="getStats">getStats()</button>
<button class="api-btn" data-api="getStatus">getStatus()</button>
<button class="api-btn" data-api="insert">insert()</button>
<button class="api-btn" data-api="exec-bold">exec('bold')</button>
<button class="api-btn" data-api="exec-h1">exec('h1')</button>
<button class="api-btn" data-api="setMode-edit">编辑模式</button>
<button class="api-btn" data-api="setMode-split">分屏模式</button>
<button class="api-btn" data-api="setMode-preview">预览模式</button>
<button class="api-btn" data-api="fullscreen">切换全屏</button>
<button class="api-btn" data-api="undo">undo()</button>
<button class="api-btn" data-api="redo">redo()</button>
<button class="api-btn success" data-api="exportMD">导出 .md</button>
<button class="api-btn success" data-api="exportHTML">导出 .html</button>
<button class="api-btn" data-api="restoreDraft">恢复草稿</button>
<button class="api-btn" data-api="clearDraft">清空草稿</button>
<button class="api-btn" data-api="refresh">refresh()</button>
<button class="api-btn danger" data-api="destroy">destroy()</button>
</div> </div>
</div> </div>
<div class="panel"> <div class="info-grid">
<div class="panel-title"> <div class="info-card">
<span>事件流(实时)</span> <div class="icon">📝</div>
<span class="count" id="evCount">0 个事件</span> <h4>丰富语法</h4>
<p>数学公式、脚注、定义列表、嵌套列表、表格对齐、任务列表、Emoji</p>
</div> </div>
<div class="events" id="events"></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>
</div> </div>
<div class="hint"> <footer class="footer">
<strong>快捷键提示:</strong> MetonaEditor v0.1.7 ·
编辑区内按 <code>Ctrl+B</code> 加粗、<code>Ctrl+I</code> 斜体、<code>Ctrl+K</code> 插入链接、 <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> ·
<code>Ctrl+Z</code> 撤销、<code>Ctrl+S</code> 触发保存、<code>Ctrl+F</code> 查找、<code>Ctrl+H</code> 替换、 <a href="docs.html">API 文档</a> ·
<code>Tab</code> 缩进、<code>Esc</code> 关闭面板。 MIT License
</div> </footer>
</div>
<script src="../dist/metona-editor.js"></script> <script src="../dist/metona-editor.js"></script>
<script> <script>
let apiCalls = 0; (function () {
let eventCount = 0; const demoMd = [
'# 🚀 MetonaEditor 演示',
const log = (label, val) => {
apiCalls++;
document.getElementById('apiCount').textContent = apiCalls + ' 次调用';
const c = document.getElementById('console');
const now = new Date().toLocaleTimeString('zh-CN', { hour12: false });
const text = typeof val === 'string' ? val : JSON.stringify(val, null, 2);
const truncated = text.length > 500 ? text.slice(0, 500) + '\n... (已截断)' : text;
c.innerHTML = `<span class="log-time">[${now}]</span> <span class="log-label">${label}</span>\n${escapeHtml(truncated)}`;
};
const logEvent = (name, data) => {
eventCount++;
document.getElementById('evCount').textContent = eventCount + ' 个事件';
const el = document.getElementById('events');
const now = new Date().toLocaleTimeString('zh-CN', { hour12: false });
const dataStr = data !== undefined ? ' ' + (typeof data === 'string' ? data.slice(0, 40) : JSON.stringify(data).slice(0, 40)) : '';
const line = document.createElement('div');
line.className = 'event-line';
line.innerHTML = `<span class="ev-time">${now}</span> <span class="ev-name">${name}</span>${escapeHtml(dataStr)}`;
el.appendChild(line);
el.scrollTop = el.scrollHeight;
// 限制最多 50 条
while (el.children.length > 50) el.removeChild(el.firstChild);
};
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function updateStatus() {
if (editor.isDestroyed()) {
document.getElementById('stState').textContent = '已销毁';
document.getElementById('statusDot').classList.add('destroyed');
return;
}
const s = editor.getStatus();
const stats = editor.getStats();
document.getElementById('stState').textContent = '运行中';
document.getElementById('stMode').textContent = s.mode;
document.getElementById('stTheme').textContent = s.theme;
document.getElementById('stLocale').textContent = s.locale;
document.getElementById('stWords').textContent = stats.words;
document.getElementById('stFs').textContent = s.fullscreen ? '是' : '否';
}
// 创建编辑器,启用全部预设插件
const editor = MeEditor.create('#editor', {
mode: 'split',
height: 420,
theme: 'light',
locale: 'zh-CN',
value: [
'# 功能演示',
'', '',
'这里是 MetonaEditor 的完整功能演示。', '> 轻量、零依赖、精致美观的 **Markdown Editor** 库。',
'> 单文件,开箱即用,中文优先。',
'', '',
'## 行内格式', '## ✨ 核心特性',
'', '',
'- 支持 **加粗**、*斜体*、~~删除线~~、`行内代码`', '- 零依赖 · 单文件打包 · UMD / ESM / CJS',
'- 高亮 ==重点标记== 、上标 x^2^、下标 H~2~O', '- 三模式视图 · 分屏拖拽 · 滚动同步',
'- Emoji 短码 :smile: :rocket: :fire: :tada:', '- 行号装订线 · 当前行高亮',
'- 支持 [链接](https://example.com) 与 ![图片](https://placehold.co/100)', '- 智能 Enter · 括号自动闭合',
'- 4 套主题 · 实例级隔离 · 外部跟随',
'- 插件系统 v2 · 依赖排序 · 异步加载',
'', '',
'## 表格', '## 📐 数学公式',
'', '',
'| 名称 | 值 |', '行内公式:爱因斯坦质能方程 $E = mc^2$',
'| --- | --- |',
'| 版本 | 0.1.7 |',
'| 依赖 | 零 |',
'| 测试 | 532+ |',
'', '',
'## 新增语法', '块级公式:',
'$$',
'\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}',
'$$',
'', '',
'### 数学公式', '## 📋 表格与对齐',
'', '',
'- 行内:$E=mc^2$', '| 版本 | 日期 | 测试数 |',
'- 块级:$$\\int_0^\\infty e^{-x}dx$$', '| :--- | :---: | ---: |',
'| v0.1.7 | 2026-07 | 532+ |',
'| v0.1.6 | 2026-07 | 521 |',
'| v0.1.5 | 2026-07 | 499 |',
'', '',
'### 嵌套列表', '## ✅ 任务列表',
'', '',
'- 一级', '- [x] 解析器增强(11 项新语法)',
' - 二级', '- [x] 主题系统(实例隔离 + 外部跟随)',
' - 三级', '- [x] 插件 v2 + i18n 增强',
'- [x] 编辑体验(行号 / 自动格式化 / 括号闭合)',
'- [ ] 更多语言翻译包',
'- [ ] VS Code 插件',
'', '',
'### 脚注', '## 🎯 嵌套列表',
'', '',
'这是带脚注的文字[^1]', '1. 一级有序',
' 1. 二级有序',
' 2. 二级第二项',
'2. 回到一级',
' - 混杂无序',
' - 第二项',
' - 三级嵌套',
'', '',
'[^1]: 这是脚注内容', '## 📖 定义列表',
'', '',
'### 定义列表', 'MetonaEditor',
': 一款轻量、零依赖的中文优先 Markdown 编辑器库。',
': 支持 CommonMark + GFM 扩展语法。',
'', '',
'Markdown', 'Markdown',
': 一种轻量级标记语言。', ': 由 John Gruber 创建的轻量级标记语言。',
': 由 John Gruber 创建。',
'', '',
'## 任务列表', '## 🔗 链接与图片',
'', '',
'- [x] 分屏预览', '访问 [MetonaEditor 仓库](https://git.metona.cn/MetonaTeam/MetonaEditor \'点击访问源码\')',
'- [x] 插件系统',
'- [x] 主题与国际化',
'- [ ] 你的创意',
'', '',
'## 代码块', '图片示例:![示例](https://placehold.co/400x120/3b82f6/white?text=MetonaEditor)',
'',
'## 💻 代码块',
'', '',
'```js', '```js',
'function hello(name) {', 'import MeEditor from \'@metona-team/metona-editor\';',
' return `Hello, ${name}!`;', '',
'}', 'const editor = MeEditor.create(\'#editor\', {',
' value: \'# Hello World\',',
' mode: \'split\',',
' lineNumbers: true,',
' autoBrackets: true,',
' plugins: [\'autoSave\', \'searchReplace\'],',
' onChange: (value) => console.log(value),',
'});',
'```', '```',
'', '',
'> 试试顶部的主题/语言切换与下方的 API 按钮。', '## 🎨 文本样式',
'', '',
'右侧事件流会实时显示编辑器触发的事件。' '- **粗体文本** 与 __另一种粗体__',
].join('\n'), '- *斜体文本* 与 _另一种斜体_',
'- ~~删除线~~ 与 ==高亮标记==',
'- 化学式:H~2~O 与 数学:x^2^ + y^2^ = z^2^',
'- 行内代码:`const x = 42;`',
'- Emoji:smile: :rocket: :fire: :tada: :rainbow: :coffee:',
'',
'## 📌 脚注',
'',
'这是一个带脚注的句子[^1],这是另一个脚注[^note]。',
'',
'[^1]: 第一个脚注的详细说明内容。',
'[^note]: 脚注可以包含 **格式化** 和 `代码`。',
'',
'---',
'',
'> **提示:** 试试拖放图片到编辑器、按 `Ctrl+F` 搜索、`Ctrl+H` 替换、',
'> 在列表行尾按 `Enter` 体验智能延续、选中文字后输入 `(` 体验自动包裹。',
].join('\n');
const editor = window.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'], plugins: ['autoSave', 'exportTool', 'searchReplace', 'imagePaste'],
onChange: (val) => { logEvent('change', val); updateStatus(); },
onInput: (val) => logEvent('input', val),
onFocus: () => logEvent('focus'),
onBlur: () => logEvent('blur'),
onSave: () => logEvent('save'),
onModeChange: (mode) => { logEvent('modeChange', mode); updateStatus(); },
onFullscreen: (fs) => { logEvent('fullscreen', fs); updateStatus(); },
}); });
// 监听 autosave 事件(来自 autoSave 插件) // ====== Control Bar Handlers ======
editor.on('autosave', (data) => logEvent('autosave', data));
// 主题切换 // Theme
document.getElementById('themeCtrl').addEventListener('click', (e) => { document.getElementById('themeSelect').addEventListener('change', function () {
const btn = e.target.closest('[data-theme]'); editor.setTheme(this.value);
if (!btn) return;
document.querySelectorAll('#themeCtrl .ctrl-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
MeEditor.setTheme(btn.dataset.theme);
editor.el.className = editor.el.className.replace(/me-theme-\w+/g, '') + ' me-theme-' + btn.dataset.theme;
updateStatus();
});
document.querySelector('#themeCtrl [data-theme="light"]').classList.add('active');
// 语言切换
document.getElementById('localeCtrl').addEventListener('click', (e) => {
const btn = e.target.closest('[data-locale]');
if (!btn) return;
document.querySelectorAll('#localeCtrl .ctrl-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
MeEditor.setLocale(btn.dataset.locale);
updateStatus();
});
document.querySelector('#localeCtrl [data-locale="zh-CN"]').classList.add('active');
// 只读切换
document.getElementById('btnReadOnly').addEventListener('click', () => {
const isRO = editor.isReadOnly();
editor.setReadOnly(!isRO);
document.getElementById('btnReadOnly').classList.toggle('active', !isRO);
document.getElementById('btnReadOnly').textContent = !isRO ? '只读中' : '只读';
log('setReadOnly', String(!isRO));
updateStatus();
}); });
// API 控制台 // Locale
document.querySelector('.api-grid').addEventListener('click', (e) => { document.getElementById('localeSelect').addEventListener('change', function () {
const btn = e.target.closest('[data-api]'); editor.setLocale(this.value);
if (!btn) return; });
const api = btn.dataset.api;
if (editor.isDestroyed()) { log('error', '编辑器已销毁,请刷新页面'); return; } // Mode buttons
switch (api) { document.querySelectorAll('#modeBtns button').forEach(function (btn) {
case 'getValue': log('getValue', editor.getValue().slice(0, 200) + '...'); break; btn.addEventListener('click', function () {
case 'getHTML': log('getHTML', editor.getHTML()); break; editor.setMode(this.dataset.mode);
case 'getStats': log('getStats', editor.getStats()); break; document.querySelectorAll('#modeBtns button').forEach(function (b) { b.classList.remove('on'); });
case 'getStatus': log('getStatus', editor.getStatus()); break; this.classList.add('on');
case 'insert': editor.insert('\n\n## 新插入的标题\n'); log('insert', '已插入内容'); break; });
case 'exec-bold': editor.exec('bold'); log('exec', "执行 'bold'"); break; });
case 'exec-h1': editor.exec('h1'); log('exec', "执行 'h1'"); break;
case 'setMode-edit': editor.setMode('edit'); log('setMode', '切换到 edit'); break; // Line numbers toggle
case 'setMode-split': editor.setMode('split'); log('setMode', '切换到 split'); break; var lineNumBtns = document.querySelectorAll('#lineNumBtns button');
case 'setMode-preview': editor.setMode('preview'); log('setMode', '切换到 preview'); break; lineNumBtns[0].addEventListener('click', function () {
case 'fullscreen': editor.toggleFullscreen(); log('fullscreen', '已切换全屏'); break; editor.config.lineNumbers = true;
case 'undo': editor.undo(); log('undo', '撤销'); break; if (editor.gutter) editor.gutter.style.display = '';
case 'redo': editor.redo(); log('redo', '重做'); break; editor._renderGutter();
case 'exportMD': editor.exportMarkdown(); log('export', '已触发 .md 下载'); break; lineNumBtns[0].classList.add('on');
case 'exportHTML': editor.exportHTML('demo.html', { title: 'MetonaEditor 导出' }); log('export', '已触发 .html 下载'); break; lineNumBtns[1].classList.remove('on');
case 'restoreDraft': editor.restoreDraft(); log('restoreDraft', '已从 localStorage 恢复草稿'); break; });
case 'clearDraft': editor.clearDraft(); log('clearDraft', '已清空草稿'); break; lineNumBtns[1].addEventListener('click', function () {
case 'refresh': editor.refresh(); log('refresh', '强制刷新预览'); break; editor.config.lineNumbers = false;
case 'destroy': editor.destroy(); log('destroy', '编辑器已销毁'); updateStatus(); break; 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');
} }
};
// Register custom shortcut
editor.registerShortcut('Ctrl+Shift+T', function () {
editor.toast('快捷键 Ctrl+Shift+T 已触发!', { type: 'info' });
}, 'Toast test');
// Listen for theme change to update select
editor.on('themeChange', function (data) {
document.getElementById('themeSelect').value = data.theme;
}); });
// 初始状态 // Sync editor mode button state
updateStatus(); editor.on('modeChange', function (mode) {
logEvent('ready', '编辑器已就绪'); document.querySelectorAll('#modeBtns button').forEach(function (b) {
b.classList.toggle('on', b.dataset.mode === mode);
});
});
})();
</script> </script>
</body> </body>
</html> </html>