docs: 添加 MetonaEditor 和 MetonaToast 参考文档
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>MetonaEditor · 功能演示</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #f5f6f8;
|
||||
--card: #ffffff;
|
||||
--text: #1f2937;
|
||||
--muted: #6b7280;
|
||||
--border: #e5e7eb;
|
||||
--accent: #3b82f6;
|
||||
--accent-soft: rgba(59, 130, 246, 0.1);
|
||||
--danger: #ef4444;
|
||||
--success: #10b981;
|
||||
--code-bg: #1c2029;
|
||||
--code-text: #e6e8eb;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 24px;
|
||||
}
|
||||
.wrap { max-width: 1120px; margin: 0 auto; }
|
||||
|
||||
/* 顶栏 */
|
||||
.topbar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
flex-wrap: wrap; gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.topbar h1 { font-size: 22px; font-weight: 700; display: flex; align-items: center; gap: 12px; }
|
||||
.topbar h1 a {
|
||||
color: var(--accent); text-decoration: none;
|
||||
font-size: 13px; font-weight: 400;
|
||||
padding: 4px 10px; border: 1px solid var(--border); border-radius: 6px;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.topbar h1 a:hover { border-color: var(--accent); }
|
||||
.controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
||||
.ctrl-label { font-size: 12px; color: var(--muted); margin-right: 4px; }
|
||||
.ctrl-group {
|
||||
display: inline-flex; gap: 0;
|
||||
background: var(--card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
|
||||
}
|
||||
.ctrl-btn {
|
||||
padding: 7px 14px; border: 0; background: transparent; cursor: pointer;
|
||||
font-size: 13px; color: var(--text); transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.ctrl-btn:hover { background: #f3f4f6; }
|
||||
.ctrl-btn.active { background: var(--accent); color: #fff; }
|
||||
.ctrl-btn + .ctrl-btn { border-left: 1px solid var(--border); }
|
||||
|
||||
/* 编辑器 */
|
||||
#editor { margin-bottom: 24px; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 20px -8px rgba(0,0,0,0.1); }
|
||||
|
||||
/* 网格布局 */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 控制台 */
|
||||
.console {
|
||||
background: var(--code-bg); color: var(--code-text);
|
||||
border-radius: 8px; padding: 14px 16px;
|
||||
font-family: "SF Mono", "Cascadia Code", Consolas, monospace; font-size: 12.5px;
|
||||
min-height: 140px; max-height: 240px; overflow: auto; white-space: pre-wrap; word-break: break-all;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.console:empty::before { content: "点击下方按钮查看输出..."; color: #6b7280; }
|
||||
.console .log-time { color: #5c6370; }
|
||||
.console .log-label { color: #82aaff; font-weight: 600; }
|
||||
|
||||
/* 事件流 */
|
||||
.events {
|
||||
background: var(--code-bg); color: var(--code-text);
|
||||
border-radius: 8px; padding: 14px 16px;
|
||||
font-family: "SF Mono", Consolas, monospace; font-size: 12px;
|
||||
min-height: 140px; max-height: 240px; overflow: auto;
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="topbar">
|
||||
<h1>
|
||||
MetonaEditor 功能演示
|
||||
<a href="index.html">← 返回首页</a>
|
||||
<a href="docs.html">📖 文档</a>
|
||||
</h1>
|
||||
<div class="controls">
|
||||
<span class="ctrl-label">主题</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>
|
||||
|
||||
<div class="status-bar" id="statusBar">
|
||||
<div class="status-item">
|
||||
<span class="status-dot" id="statusDot"></span>
|
||||
<span>状态:</span><strong id="stState">运行中</strong>
|
||||
</div>
|
||||
<div class="status-item"><span>模式:</span><strong id="stMode">split</strong></div>
|
||||
<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 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 class="panel">
|
||||
<div class="panel-title">
|
||||
<span>事件流(实时)</span>
|
||||
<span class="count" id="evCount">0 个事件</span>
|
||||
</div>
|
||||
<div class="events" id="events"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
<strong>快捷键提示:</strong>
|
||||
编辑区内按 <code>Ctrl+B</code> 加粗、<code>Ctrl+I</code> 斜体、<code>Ctrl+K</code> 插入链接、
|
||||
<code>Ctrl+Z</code> 撤销、<code>Ctrl+S</code> 触发保存、<code>Ctrl+F</code> 查找、<code>Ctrl+H</code> 替换、
|
||||
<code>Tab</code> 缩进、<code>Esc</code> 关闭面板。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/metona-editor.js"></script>
|
||||
<script>
|
||||
let apiCalls = 0;
|
||||
let eventCount = 0;
|
||||
|
||||
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
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 的完整功能演示。',
|
||||
'',
|
||||
'## 行内格式',
|
||||
'',
|
||||
'- 支持 **加粗**、*斜体*、~~删除线~~、`行内代码`',
|
||||
'- 高亮 ==重点标记== 、上标 x^2^、下标 H~2~O',
|
||||
'- Emoji 短码 :smile: :rocket: :fire: :tada:',
|
||||
'- 支持 [链接](https://example.com) 与 ',
|
||||
'',
|
||||
'## 表格',
|
||||
'',
|
||||
'| 名称 | 值 |',
|
||||
'| --- | --- |',
|
||||
'| 版本 | 0.1.3 |',
|
||||
'| 依赖 | 零 |',
|
||||
'| 测试 | 390+ |',
|
||||
'',
|
||||
'## 任务列表',
|
||||
'',
|
||||
'- [x] 分屏预览',
|
||||
'- [x] 插件系统',
|
||||
'- [x] 主题与国际化',
|
||||
'- [ ] 你的创意',
|
||||
'',
|
||||
'## 代码块',
|
||||
'',
|
||||
'```js',
|
||||
'function hello(name) {',
|
||||
' return `Hello, ${name}!`;',
|
||||
'}',
|
||||
'```',
|
||||
'',
|
||||
'> 试试顶部的主题/语言切换与下方的 API 按钮。',
|
||||
'',
|
||||
'右侧事件流会实时显示编辑器触发的事件。'
|
||||
].join('\n'),
|
||||
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 插件)
|
||||
editor.on('autosave', (data) => logEvent('autosave', data));
|
||||
|
||||
// 主题切换
|
||||
document.getElementById('themeCtrl').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-theme]');
|
||||
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 控制台
|
||||
document.querySelector('.api-grid').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-api]');
|
||||
if (!btn) return;
|
||||
const api = btn.dataset.api;
|
||||
if (editor.isDestroyed()) { log('error', '编辑器已销毁,请刷新页面'); return; }
|
||||
switch (api) {
|
||||
case 'getValue': log('getValue', editor.getValue().slice(0, 200) + '...'); break;
|
||||
case 'getHTML': log('getHTML', editor.getHTML()); break;
|
||||
case 'getStats': log('getStats', editor.getStats()); break;
|
||||
case 'getStatus': log('getStatus', editor.getStatus()); break;
|
||||
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;
|
||||
case 'setMode-split': editor.setMode('split'); log('setMode', '切换到 split'); break;
|
||||
case 'setMode-preview': editor.setMode('preview'); log('setMode', '切换到 preview'); break;
|
||||
case 'fullscreen': editor.toggleFullscreen(); log('fullscreen', '已切换全屏'); break;
|
||||
case 'undo': editor.undo(); log('undo', '撤销'); break;
|
||||
case 'redo': editor.redo(); log('redo', '重做'); break;
|
||||
case 'exportMD': editor.exportMarkdown(); log('export', '已触发 .md 下载'); break;
|
||||
case 'exportHTML': editor.exportHTML('demo.html', { title: 'MetonaEditor 导出' }); log('export', '已触发 .html 下载'); break;
|
||||
case 'restoreDraft': editor.restoreDraft(); log('restoreDraft', '已从 localStorage 恢复草稿'); break;
|
||||
case 'clearDraft': editor.clearDraft(); log('clearDraft', '已清空草稿'); break;
|
||||
case 'refresh': editor.refresh(); log('refresh', '强制刷新预览'); break;
|
||||
case 'destroy': editor.destroy(); log('destroy', '编辑器已销毁'); updateStatus(); break;
|
||||
}
|
||||
});
|
||||
|
||||
// 初始状态
|
||||
updateStatus();
|
||||
logEvent('ready', '编辑器已就绪');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,621 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>MetonaEditor · 文档</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; }
|
||||
|
||||
/* 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 */
|
||||
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; }
|
||||
|
||||
/* 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); }
|
||||
|
||||
/* 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 */
|
||||
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; }
|
||||
|
||||
/* 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%; }
|
||||
}
|
||||
</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>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="docs-header">
|
||||
<h1><span class="grad">文档</span></h1>
|
||||
<p class="sub">MetonaEditor v0.1.3 完整 API、配置与使用指南</p>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="docs-layout">
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="toc-group">快速入门</div>
|
||||
<a href="#quickstart">安装与引入</a>
|
||||
<div class="toc-group">配置</div>
|
||||
<a href="#config">全部配置项</a>
|
||||
<a href="#toolbar">工具栏</a>
|
||||
<div class="toc-group">实例 API</div>
|
||||
<a href="#api-content">内容操作</a>
|
||||
<a href="#api-exec">命令执行</a>
|
||||
<a href="#api-history">历史栈</a>
|
||||
<a href="#api-mode">模式与全屏</a>
|
||||
<a href="#api-stats">统计与状态</a>
|
||||
<a href="#api-readonly">启用/禁用/只读</a>
|
||||
<a href="#api-events">事件系统</a>
|
||||
<a href="#api-plugin">插件管理</a>
|
||||
<a href="#api-lifecycle">生命周期</a>
|
||||
<div class="toc-group">静态 API</div>
|
||||
<a href="#static-api">顶层函数</a>
|
||||
<div class="toc-group">语法参考</div>
|
||||
<a href="#syntax">解析器语法</a>
|
||||
<div class="toc-group">插件</div>
|
||||
<a href="#plugins">插件约定</a>
|
||||
<a href="#preset-plugins">预设插件</a>
|
||||
<div class="toc-group">主题</div>
|
||||
<a href="#themes">主题系统</a>
|
||||
<div class="toc-group">国际化</div>
|
||||
<a href="#i18n">i18n API</a>
|
||||
</div>
|
||||
|
||||
<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-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"><script src="dist/metona-editor.js"></script></span>
|
||||
<span class="c-com"><script></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"></script></span></pre>
|
||||
</section>
|
||||
|
||||
<!-- ======== 配置项 ======== -->
|
||||
<section id="config">
|
||||
<h2>全部配置项</h2>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
render<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (md, env) => 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) => html</span>
|
||||
sanitize<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (html) => safeHtml</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>
|
||||
|
||||
<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) => 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) => void</span>
|
||||
onFocus<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) => void</span>
|
||||
onBlur<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) => 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) => 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) => 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) => void</span>
|
||||
onCreate<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) => void</span>
|
||||
onDestroy<span class="c-punc">:</span> <span class="c-kw">null</span><span class="c-punc">,</span> <span class="c-com">// (editor) => void</span>
|
||||
<span class="c-punc">});</span></pre>
|
||||
</section>
|
||||
|
||||
<!-- ======== 工具栏 ======== -->
|
||||
<section id="toolbar">
|
||||
<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>
|
||||
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>
|
||||
|
||||
<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>
|
||||
<span class="badge">h1</span><span class="badge">h2</span><span class="badge">h3</span><span class="badge">quote</span><span class="badge">ul</span><span class="badge">ol</span>
|
||||
<span class="badge">indent</span><span class="badge">outdent</span><span class="badge">hr</span>
|
||||
<span class="badge">link</span><span class="badge">image</span><span class="badge">table</span>
|
||||
<span class="badge">undo</span><span class="badge">redo</span>
|
||||
<span class="badge">edit</span><span class="badge">split</span><span class="badge">preview</span><span class="badge">fullscreen</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ======== 实例 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>
|
||||
</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>
|
||||
|
||||
<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>下划线 <u>text</u></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>插入图片 </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>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>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<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">// => { 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">// => { 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>
|
||||
|
||||
<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><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>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>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>
|
||||
</table>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- ======== 静态 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>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">=></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>
|
||||
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">// => { 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">// => '<h1 id="hello">Hello</h1>'</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">// => ''</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">// => 'hello-world'</span></pre>
|
||||
</section>
|
||||
|
||||
<!-- ======== 语法参考 ======== -->
|
||||
<section id="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><h1></code> ~ <code><h6></code></td></tr>
|
||||
<tr><td>段落</td><td>纯文本</td><td><code><p></code></td></tr>
|
||||
<tr><td>粗体</td><td><code>**bold**</code> <code>__bold__</code></td><td><code><strong></code></td></tr>
|
||||
<tr><td>斜体</td><td><code>*italic*</code> <code>_italic_</code></td><td><code><em></code></td></tr>
|
||||
<tr><td>删除线</td><td><code>~~text~~</code></td><td><code><del></code></td></tr>
|
||||
<tr><td>高亮标记</td><td><code>==text==</code></td><td><code><mark></code></td></tr>
|
||||
<tr><td>上标</td><td><code>x^2^</code></td><td><code><sup></code></td></tr>
|
||||
<tr><td>下标</td><td><code>H~2~O</code></td><td><code><sub></code></td></tr>
|
||||
<tr><td>行内代码</td><td><code>`code`</code></td><td><code><code></code></td></tr>
|
||||
<tr><td>代码块</td><td><code>```lang</code></td><td><code><pre><code></code></td></tr>
|
||||
<tr><td>引用</td><td><code>> quote</code></td><td><code><blockquote></code></td></tr>
|
||||
<tr><td>无序列表</td><td><code>- item</code></td><td><code><ul><li></code></td></tr>
|
||||
<tr><td>有序列表</td><td><code>1. item</code></td><td><code><ol><li></code></td></tr>
|
||||
<tr><td>任务列表</td><td><code>- [x] done</code></td><td><code><li class="me-task-item"></code></td></tr>
|
||||
<tr><td>水平线</td><td><code>---</code> <code>***</code> <code>___</code></td><td><code><hr></code></td></tr>
|
||||
<tr><td>表格</td><td><code>| a | b |</code></td><td><code><table></code>(支持 <code>:---:</code> 列对齐)</td></tr>
|
||||
<tr><td>链接</td><td><code>[text](url)</code></td><td><code><a></code></td></tr>
|
||||
<tr><td>图片</td><td><code></code></td><td><code><img></code></td></tr>
|
||||
<tr><td>自动链接</td><td><code><https://...></code></td><td><code><a></code></td></tr>
|
||||
<tr><td>Emoji</td><td><code>:smile:</code> <code>:rocket:</code></td><td>😊 🚀(80+ 常用)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>XSS 防护</h3>
|
||||
<p>所有文本经 HTML 转义。URL 自动过滤 <code>javascript:</code> <code>vbscript:</code> <code>file:</code> 及非图片 <code>data:</code> 协议。</p>
|
||||
</section>
|
||||
|
||||
<!-- ======== 插件 ======== -->
|
||||
<section id="plugins">
|
||||
<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>
|
||||
<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">// 自动插入 </span></pre>
|
||||
</section>
|
||||
|
||||
<!-- ======== 主题 ======== -->
|
||||
<section id="themes">
|
||||
<h2>主题系统</h2>
|
||||
|
||||
<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">// 注册自定义主题</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>
|
||||
bg<span class="c-punc">:</span> <span class="c-str">'#001122'</span><span class="c-punc">,</span> text<span class="c-punc">:</span> <span class="c-str">'#aabbcc'</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-punc">});</span>
|
||||
themeUtils<span class="c-punc">.</span><span class="c-fn">applyTheme</span><span class="c-punc">(</span><span class="c-str">'ocean'</span><span class="c-punc">);</span></pre>
|
||||
|
||||
<p>CSS 变量:主题变量限定在 <code>.me-wrapper</code> 元素上,不污染全局样式。支持 <code>@media print</code> 打印样式。</p>
|
||||
</section>
|
||||
|
||||
<!-- ======== 国际化 ======== -->
|
||||
<section id="i18n">
|
||||
<h2>国际化</h2>
|
||||
|
||||
<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-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">// => '粗体'(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">// => 'Bold'(en-US)</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>
|
||||
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">// => '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">// => '$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">// => '1/15/2024'</span></pre>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
MetonaEditor v0.1.3 · <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', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,417 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>在线演示 — MetonaToast</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b1121; --surface: #151d33; --border: #1e2d4d;
|
||||
--text: #e2e8f0; --muted: #64748b; --accent: #6366f1; --accent2: #818cf8;
|
||||
--radius: 14px;
|
||||
}
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:var(--bg); color:var(--text); line-height:1.6; min-height:100vh; }
|
||||
a { color:var(--accent2); text-decoration:none; }
|
||||
code { background:rgba(99,102,241,0.12); color:#a5b4fc; padding:1px 6px; border-radius:4px; font-size:0.85em; }
|
||||
.nav { position:sticky; top:0; z-index:100; background:rgba(11,17,33,0.88); backdrop-filter:blur(14px); border-bottom:1px solid var(--border); }
|
||||
.nav-inner { max-width:1300px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; padding:0 20px; height:56px; }
|
||||
.nav-logo { font-size:18px; font-weight:700; background:linear-gradient(135deg,#818cf8,#c084fc,#f472b6); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; }
|
||||
.nav-links { display:flex; gap:20px; }
|
||||
.nav-links a { color:var(--muted); font-size:13px; font-weight:500; transition:.2s; }
|
||||
.nav-links a:hover { color:var(--accent2); }
|
||||
.hero { text-align:center; padding:40px 20px 30px; border-bottom:1px solid var(--border); background:radial-gradient(ellipse at 50% 0%,rgba(99,102,241,0.12) 0%,transparent 50%); }
|
||||
.hero h1 { font-size:36px; font-weight:800; }
|
||||
.hero h1 span { background:linear-gradient(135deg,#818cf8,#c084fc,#f472b6); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; }
|
||||
.hero p { color:var(--muted); font-size:14px; margin-top:6px; }
|
||||
|
||||
.container { max-width:1300px; margin:0 auto; padding:24px 20px; display:grid; grid-template-columns:repeat(auto-fill,minmax(400px,1fr)); gap:18px; }
|
||||
.card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:20px; }
|
||||
.card h2 { font-size:15px; margin-bottom:2px; }
|
||||
.card > p { color:var(--muted); font-size:11px; margin-bottom:12px; }
|
||||
.btn-row { display:flex; flex-wrap:wrap; gap:6px; }
|
||||
.btn { padding:8px 14px; border:none; border-radius:7px; font-size:12px; font-weight:500; cursor:pointer; transition:.15s; color:#fff; }
|
||||
.btn:hover { transform:translateY(-1px); filter:brightness(1.15); }
|
||||
.btn-sm { padding:5px 10px; font-size:11px; border-radius:5px; }
|
||||
.btn-outline { background:transparent; border:1px solid var(--border); color:var(--text); }
|
||||
.btn-outline:hover { border-color:var(--accent); background:rgba(99,102,241,0.06); }
|
||||
.btn-tiny { padding:4px 9px; font-size:10px; border-radius:4px; }
|
||||
.c-s { background:#10b981; } .c-e { background:#ef4444; } .c-w { background:#f59e0b; color:#1e293b; }
|
||||
.c-i { background:#3b82f6; } .c-p { background:#6366f1; } .c-pp { background:#8b5cf6; }
|
||||
.c-pk { background:#ec4899; } .c-t { background:#14b8a6; } .c-o { background:#f97316; }
|
||||
.c-sl { background:#475569; } .c-r { background:#f43f5e; } .c-g { background:#22c55e; }
|
||||
.label { font-size:10px; text-transform:uppercase; letter-spacing:0.8px; color:var(--muted); margin-bottom:10px; font-weight:600; }
|
||||
.demo-section { border-top:1px solid var(--border); padding-top:8px; margin-top:8px; }
|
||||
footer { text-align:center; padding:30px 20px; color:var(--muted); font-size:12px; border-top:1px solid var(--border); }
|
||||
@media (max-width:768px) { .container { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav">
|
||||
<div class="nav-inner">
|
||||
<div class="nav-logo">MetonaToast</div>
|
||||
<div class="nav-links">
|
||||
<a href="index.html">首页</a>
|
||||
<a href="docs.html">文档</a>
|
||||
<a href="demo.html">演示</a>
|
||||
<a href="https://git.metona.cn/MetonaTeam/MetonaToast" target="_blank">Gitea</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero">
|
||||
<h1><span>在线演示</span></h1>
|
||||
<p>覆盖项目全部功能的交互演示 — 点击即可体验</p>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
|
||||
<!-- 1. 基础通知 -->
|
||||
<div class="card">
|
||||
<div class="label">📢 基础通知</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-s" onclick="MeToast.success('操作成功!')">✓ 成功</button>
|
||||
<button class="btn c-e" onclick="MeToast.error('操作失败!')">✕ 错误</button>
|
||||
<button class="btn c-w" onclick="MeToast.warning('请注意检查')">⚠ 警告</button>
|
||||
<button class="btn c-i" onclick="MeToast.info('系统维护中')">ℹ 信息</button>
|
||||
<button class="btn c-sl" onclick="MeToast.show('默认消息')">— 默认</button>
|
||||
</div>
|
||||
<div class="demo-section">
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success({title:'保存成功',message:'文件已同步到云端'})">带标题</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.info('持续10秒',{duration:10000})">长时10s</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.warning('手动关闭',{duration:0,closeButton:true})">手动关闭</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.info('',{icon:false})">无图标</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.info('无按钮',{closeButton:false})">无关闭按钮</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="demoCustomIcon()">自定义图标</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Loading 链式 -->
|
||||
<div class="card">
|
||||
<div class="label">🔄 Loading 链式转换</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-s" onclick="demoLoading()">提交成功</button>
|
||||
<button class="btn c-e" onclick="demoLoadingFail()">提交失败</button>
|
||||
<button class="btn c-w" onclick="demoLoadingWarn()">转为警告</button>
|
||||
<button class="btn c-i" onclick="demoLoadingUpdate()">原地更新</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Promise + confirm + prompt -->
|
||||
<div class="card">
|
||||
<div class="label">💬 对话框</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-pp" onclick="demoPromise()">Promise 风格</button>
|
||||
<button class="btn c-pk" onclick="demoConfirm()">确认对话框</button>
|
||||
<button class="btn c-t" onclick="demoPrompt()">输入对话框</button>
|
||||
<button class="btn c-o" onclick="demoPromptPassword()">密码输入</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 进度 & 倒计时 -->
|
||||
<div class="card">
|
||||
<div class="label">📊 进度 & 倒计时</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-o" onclick="demoProgress()">进度条(水平)</button>
|
||||
<button class="btn c-g" onclick="demoProgressV()">进度条(垂直)</button>
|
||||
<button class="btn c-pk" onclick="demoCountdown()">倒计时 5s</button>
|
||||
<button class="btn c-r" onclick="demoCountdownPause()">倒计时(可暂停)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 队列 & 堆叠 -->
|
||||
<div class="card">
|
||||
<div class="label">📋 队列 & 堆叠</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-p" onclick="demoQueue()">队列展示</button>
|
||||
<button class="btn c-e" onclick="demoQueueCancel()">队列中途取消</button>
|
||||
<button class="btn c-pp" onclick="demoStack()">堆叠展示</button>
|
||||
<button class="btn c-t" onclick="demoStackMixed()">混合类型堆叠</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6. Action Toast -->
|
||||
<div class="card">
|
||||
<div class="label">⚡ Action Toast <span style="color:#34d399;font-size:10px;">v2.0</span></div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-e" onclick="demoActionDelete()">删除+撤销</button>
|
||||
<button class="btn c-i" onclick="demoActionMulti()">多按钮</button>
|
||||
<button class="btn c-s" onclick="demoActionNoClose()">不自动关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 7. 分组管理 -->
|
||||
<div class="card">
|
||||
<div class="label">📁 分组管理 <span style="color:#34d399;font-size:10px;">v2.0</span></div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-p" onclick="demoGroup()">创建分组消息</button>
|
||||
<button class="btn c-e" onclick="dismissGroup()">关闭分组</button>
|
||||
<button class="btn c-i" onclick="MeToast.info('分组数量: '+group.count(),{type:'info'})">查看数量</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="demoGroupMixed()">混合类型分组</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 8. 动画效果 -->
|
||||
<div class="card">
|
||||
<div class="label">🎭 11种动画效果</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('slide')">slide</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('fade')">fade</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('scale')">scale</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('bounce')">bounce</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('flip')">flip</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('rotate')">rotate</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('zoom')">zoom</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('slideUp')">slideUp</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('slideDown')">slideDown</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('slideLeft')">slideLeft</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="anim('slideRight')">slideRight</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 9. 6种位置 -->
|
||||
<div class="card">
|
||||
<div class="label">📍 全部位置</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success('左上角',{position:'top-left'})">↖ top-left</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success('顶部居中',{position:'top-center'})">↑ top-center</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success('右上角',{position:'top-right'})">↗ top-right</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success('左下角',{position:'bottom-left'})">↙ bottom-left</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success('底部居中',{position:'bottom-center'})">↓ bottom-center</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.success('右下角',{position:'bottom-right'})">↘ bottom-right</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 10. 主题切换 + 自定义 -->
|
||||
<div class="card">
|
||||
<div class="label">🎨 主题切换 & 自定义</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTheme('light')">☀️ Light</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTheme('dark')">🌙 Dark</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTheme('auto')">🔄 Auto</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTheme('warm')">🔥 Warm</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="demoRegisterTheme()">注册自定义主题</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 11. 插件系统 -->
|
||||
<div class="card">
|
||||
<div class="label">🔌 插件系统</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.use('keyboard');MeToast.info('已安装 — 按 ESC 关闭所有 Toast')">keyboard ESC关闭</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.use('persistence');MeToast.info('已安装 — 配置将持久化到 localStorage')">persistence 持久化</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="MeToast.use('accessibility');MeToast.info('已安装 — 屏幕阅读器可用')">accessibility 无障碍</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="demoCustomPlugin()">自定义插件</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 12. v2.0 新功能 -->
|
||||
<div class="card">
|
||||
<div class="label">🆕 v2.0 新功能 <span style="color:#34d399;font-size:10px;">resetTimer · onUpdate · render · notify</span></div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-i" onclick="demoResetTimer()">resetTimerOnUpdate</button>
|
||||
<button class="btn c-p" onclick="demoOnUpdate()">onUpdate 回调</button>
|
||||
<button class="btn c-pp" onclick="demoCustomRender()">自定义渲染</button>
|
||||
<button class="btn c-w" onclick="demoNotify()">系统通知</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 13. Toast 管理 -->
|
||||
<div class="card">
|
||||
<div class="label">🔧 Toast 管理</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-s" onclick="MeToast.success('消息 '+(MeToast.count()+1))">发一条</button>
|
||||
<button class="btn c-i" onclick="MeToast.info('消息 '+(MeToast.count()+1))">再一条</button>
|
||||
<button class="btn c-w" onclick="MeToast.info('当前 '+MeToast.count()+' 条')">查看数量</button>
|
||||
<button class="btn c-sl" onclick="MeToast.pauseAll();MeToast.info('已暂停全部')">暂停全部</button>
|
||||
<button class="btn c-sl" onclick="MeToast.resumeAll();MeToast.info('已恢复全部')">恢复全部</button>
|
||||
<button class="btn c-e" onclick="MeToast.dismiss()">关闭全部</button>
|
||||
<button class="btn c-r" onclick="MeToast.destroy();MeToast.info('已销毁')">销毁</button>
|
||||
</div>
|
||||
<div class="demo-section">
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline btn-tiny" onclick="demoFindById()">findById</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="demoGetAll()">getAll</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="demoClearByPos()">clear(位置)</button>
|
||||
<button class="btn btn-outline btn-tiny" onclick="demoGetConfig()">查看配置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 14. 交互特性 -->
|
||||
<div class="card">
|
||||
<div class="label">✋ 交互特性</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-i" onclick="demoDraggable()">拖拽关闭</button>
|
||||
<button class="btn c-p" onclick="demoPauseHover()">悬停暂停</button>
|
||||
<button class="btn c-w" onclick="demoCloseOnClick()">点击关闭</button>
|
||||
<button class="btn c-pp" onclick="demoWidth()">自定义宽度</button>
|
||||
<button class="btn c-t" onclick="demoClassName()">自定义className</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 15. 国际化切换 -->
|
||||
<div class="card">
|
||||
<div class="label">🌍 国际化 & 格式化</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn c-s" onclick="switchLocale('zh-CN')">切换到中文</button>
|
||||
<button class="btn c-i" onclick="switchLocale('en-US')">Switch to English</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="demoFormat()">格式化演示</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 16. 107种预设图标 -->
|
||||
<div class="card" style="grid-column:1/-1;">
|
||||
<div class="label">🎨 107种预设图标 — 点击任意图标发送Toast</div>
|
||||
<div class="btn-row" id="icon-grid" style="max-height:300px;overflow-y:auto;padding:4px 0;"></div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
MetonaToast v2.0.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>
|
||||
<script>
|
||||
MeToast.configure({ position:'top-right', duration:4000, theme:'dark', animation:'slide', pauseOnHover:true, showProgress:true });
|
||||
|
||||
// === Loading ===
|
||||
function demoLoading() { const l=MeToast.loading('正在提交...'); setTimeout(()=>l.success('提交成功!'),2000); }
|
||||
function demoLoadingFail() { const l=MeToast.loading('正在处理...'); setTimeout(()=>l.error('处理失败'),2000); }
|
||||
function demoLoadingWarn() { const l=MeToast.loading('检查中...'); setTimeout(()=>l.warning('发现潜在问题'),2000); }
|
||||
function demoLoadingUpdate() { const l=MeToast.loading('处理中 0%'); let i=1; const t=setInterval(()=>{if(i>3){clearInterval(t);l.success('完成!');return;} l.update({message:'处理中 '+i*33+'%'}); i++;},600); }
|
||||
|
||||
// === Promise / confirm / prompt ===
|
||||
async function demoPromise() {
|
||||
const p=new Promise((r,e)=>setTimeout(()=>Math.random()>0.3?r('ok'):e('fail'),2000));
|
||||
try{await MeToast.promise(p,{loading:'处理中...',success:'成功!',error:'失败'});}catch(_){}
|
||||
}
|
||||
async function demoConfirm() { const ok=await MeToast.confirm('确定删除?',{confirmText:'删除',confirmColor:'#ef4444'}); if(ok)MeToast.success('已删除'); }
|
||||
async function demoPrompt() { const v=await MeToast.prompt('请输入姓名',{placeholder:'请输入...'}); if(v) MeToast.info('你好,'+v+'!'); }
|
||||
async function demoPromptPassword() { const v=await MeToast.prompt('请输入密码',{placeholder:'密码',inputType:'password',submitText:'登录'}); if(v) MeToast.info('密码已输入'); }
|
||||
|
||||
// === Progress & Countdown ===
|
||||
function demoProgress() { const p=MeToast.progress('上传中...',{progressColor:'#3b82f6'}); let v=0; const t=setInterval(()=>{v+=Math.random()*25; if(v>=100){clearInterval(t);p.complete('上传完成!');}else p.setProgress(v);},400); }
|
||||
function demoProgressV() { const p=MeToast.progress('上传中...',{progressColor:'#10b981',progressDirection:'vertical'}); let v=0; const t=setInterval(()=>{v+=Math.random()*25; if(v>=100){clearInterval(t);p.complete('完成!');}else p.setProgress(v);},400); }
|
||||
function demoCountdown() { MeToast.countdown('{seconds} 秒后执行',5,{onComplete:()=>MeToast.success('已执行')}); }
|
||||
function demoCountdownPause() {
|
||||
const c=MeToast.countdown('{seconds}s 可暂停',10,{type:'info',onComplete:()=>MeToast.success('倒计时结束')});
|
||||
setTimeout(()=>{c.pause();MeToast.warning('已暂停3秒后恢复',{duration:3000})},4000);
|
||||
setTimeout(()=>{c.resume();MeToast.info('已恢复倒计时')},7000);
|
||||
}
|
||||
|
||||
// === Queue & Stack ===
|
||||
async function demoQueue() { await MeToast.queue(['第一步','第二步','第三步'],{delay:800,duration:2000,type:'info'}); }
|
||||
async function demoQueueCancel() {
|
||||
const q=MeToast.queue(['步骤1','步骤2','步骤3','步骤4','步骤5'],{delay:600,duration:1500,type:'info'});
|
||||
setTimeout(()=>{q.cancel();MeToast.warning('队列已取消',{duration:2000});},3500);
|
||||
}
|
||||
function demoStack() { MeToast.stack(['消息1','消息2','消息3','消息4','消息5'],{stagger:120,type:'info'}); }
|
||||
function demoStackMixed() {
|
||||
MeToast.stack(['普通消息',{message:'警告消息',type:'warning',duration:5000},{message:'错误消息',type:'error'}],{stagger:150});
|
||||
}
|
||||
|
||||
// === Action ===
|
||||
function demoActionDelete() { MeToast.action('文件 data.json 已删除',[{text:'撤销',onClick:()=>MeToast.success('已撤销'),color:'#3b82f6'}]); }
|
||||
function demoActionMulti() { MeToast.action('邮件已发送',[{text:'查看',onClick:()=>MeToast.info('打开邮件详情'),color:'#3b82f6'},{text:'撤回',onClick:()=>MeToast.warning('已撤回'),color:'#f59e0b'}]); }
|
||||
function demoActionNoClose() { MeToast.action('完成后点击关闭',[{text:'我知道了',onClick:()=>{},color:'#10b981'}]); }
|
||||
|
||||
// === Group ===
|
||||
const group = MeToast.group('demo-group');
|
||||
function demoGroup() { group.success('分组成功 #'+group.count()); group.info('分组消息 #'+group.count()); }
|
||||
function dismissGroup() { MeToast.dismissGroup('demo-group'); MeToast.info('分组已关闭'); }
|
||||
function demoGroupMixed() {
|
||||
const g = MeToast.group('mixed-test');
|
||||
g.success('OK'); g.warning('Warning'); g.info('Info');
|
||||
setTimeout(()=>MeToast.dismissGroup('mixed-test'),4000);
|
||||
}
|
||||
|
||||
// === Animation ===
|
||||
function anim(name) { MeToast.success('动画: '+name,{animation:name,duration:3000,showProgress:false}); }
|
||||
|
||||
// === Theme ===
|
||||
function switchTheme(t) { MeToast.themes.switchTheme(t); MeToast.configure({theme:t}); MeToast.info('主题: '+t,{theme:t}); }
|
||||
function demoRegisterTheme() {
|
||||
try {
|
||||
MeToast.themes.registerTheme('sunset',{bg:'rgba(255,248,240,0.96)',text:'#92400e',border:'rgba(249,115,22,0.2)',shadow:'0 10px 36px -10px rgba(249,115,22,0.2)',hoverShadow:'0 14px 48px -10px rgba(249,115,22,0.3)',progressBg:'rgba(249,115,22,0.1)',closeHoverBg:'rgba(249,115,22,0.1)'});
|
||||
MeToast.themes.switchTheme('sunset');
|
||||
MeToast.configure({theme:'sunset'});
|
||||
MeToast.success('已切换到 sunset 主题',{theme:'sunset'});
|
||||
} catch(_) { MeToast.warning('主题已注册,已切换'); }
|
||||
}
|
||||
|
||||
// === Plugins ===
|
||||
function demoCustomPlugin() {
|
||||
MeToast.use({name:'click-counter',version:'1.0',install(){let c=0;Toast.on('afterShow',t=>{c++;setTimeout(()=>t.update({message:t.message.replace(/\\(\\d+\\)/,'')+' (总通知#'+c+')'}),1);});}});
|
||||
MeToast.info('自定义插件已安装(消息后追加计数)');
|
||||
}
|
||||
|
||||
// === v2.0 new features ===
|
||||
function demoResetTimer() {
|
||||
const t=MeToast.info('这条消息不会自动消失(resetTimer)',{duration:3000,resetTimerOnUpdate:true,closeButton:true});
|
||||
let n=0; const iv=setInterval(()=>{n++;if(n>8){clearInterval(iv);t.close();return;}t.update({message:'重置计时器 #'+n+' — 每次更新重置3秒'});},1000);
|
||||
}
|
||||
function demoOnUpdate() { const t=MeToast.success('onUpdate 演示',{onUpdate:t=>{console.log('更新了:',t.message);}}); setTimeout(()=>t.update({message:'已更新!'}),1000); setTimeout(()=>t.update({message:'再次更新!'}),2000); }
|
||||
function demoCustomRender() {
|
||||
MeToast.show({render:toast=>'<div style="background:#111;color:#0f0;padding:16px 20px;border-radius:10px;border:1px solid #0f0;font-family:monospace">🟢 <b>'+toast.message+'</b><br><span style="font-size:11px;opacity:0.6"># custom render by thzxx</span></div>',message:'自定义渲染内容',duration:5000});
|
||||
}
|
||||
function demoNotify() {
|
||||
if(typeof Notification==='undefined'){MeToast.error('浏览器不支持 Notification');return;}
|
||||
if(Notification.permission==='denied'){MeToast.error('通知权限已拒绝');return;}
|
||||
MeToast.info('页面隐藏时看系统通知',{notifyWhenHidden:true,duration:6000});
|
||||
}
|
||||
|
||||
// === Management ===
|
||||
function demoFindById() {
|
||||
const t=MeToast.info('这是测试 toast',{duration:5000});
|
||||
setTimeout(()=>{const found=MeToast.find(t.id); if(found)MeToast.success('找到了 #'+found.id);},500);
|
||||
}
|
||||
function demoGetAll() { const all=MeToast.getAll(); MeToast.info('当前共 '+all.size+' 条 toast'); }
|
||||
function demoClearByPos() { MeToast.error('右下角临时消息',{position:'bottom-right',duration:3000}); setTimeout(()=>{MeToast.clear('bottom-right');MeToast.info('右下角已清除');},500); }
|
||||
function demoGetConfig() { try{const c=MeToast.getConfig();MeToast.info('配置: '+JSON.stringify({position:c.position,duration:c.duration,theme:c.theme,animation:c.animation,locale:c.locale}).replace(/"/g,''),{duration:5000});}catch(e){MeToast.error('无 getConfig 方法');} }
|
||||
|
||||
// === Interaction ===
|
||||
function demoDraggable() { MeToast.info('← 试试拖动我来关闭',{draggable:true,duration:8000,closeButton:true}); }
|
||||
function demoPauseHover() { MeToast.info('鼠标悬停在我上面,倒计时会暂停',{pauseOnHover:true,duration:8000}); }
|
||||
function demoCloseOnClick() { MeToast.warning('点击我即可关闭',{closeOnClick:true,duration:10000}); }
|
||||
function demoWidth() { MeToast.info('宽550px',{width:550}); setTimeout(()=>MeToast.info('宽280px(窄)',{width:280}),500); }
|
||||
function demoClassName() { MeToast.success('带自定义 className',{className:'demo-custom-class'}); }
|
||||
function demoCustomIcon() {
|
||||
MeToast.info('通用通知', {
|
||||
iconHTML: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>'
|
||||
});
|
||||
}
|
||||
|
||||
// === i18n ===
|
||||
function switchLocale(loc) {
|
||||
try{MeToast.i18n.switchLocale(loc);MeToast.configure({locale:loc});MeToast.success(loc==='zh-CN'?'已切换到中文':'Switched to English',{theme:'dark'});}
|
||||
catch(e){MeToast.info('已切换语言');}
|
||||
}
|
||||
function demoFormat() {
|
||||
try{
|
||||
MeToast.info('数字: '+MeToast.i18n.formatNumber(1234567)+' | 货币: '+MeToast.i18n.formatCurrency(99.5,'CNY')+' | 日期: '+MeToast.i18n.formatDate(new Date()),{duration:8000});
|
||||
}catch(e){MeToast.info('格式化: 1234567 → 1,234,567');}
|
||||
}
|
||||
|
||||
// === 图标网格 ===
|
||||
(function renderIcons() {
|
||||
var grid = document.getElementById('icon-grid');
|
||||
if (!grid) return;
|
||||
var icons = ['success','error','warning','info','loading','close','check','x','alert','question','star','heart','bell','mail','settings','user','home','search','plus','minus','edit','trash','download','upload','share','link','external','clock','calendar','map','compass','globe','wifi','cloud','sun','moon','zap','activity','cpu','database','server','terminal','code','git','package','layers','grid','list','filter','sort','refresh','sync','power','battery','bluetooth','volume','mic','camera','image','video','music','file','folder','clipboard','save','print','eye','eyeOff','lock','unlock','shield','key','flag','bookmark','tag','gift','award','target','crosshair','move','maximize','minimize','copy','cut','paste','rotateCw','rotateCcw','zoomIn','zoomOut','crop','sliders','toggleLeft','toggleRight','checkCircle','xCircle','alertCircle','infoCircle','helpCircle','alertTriangle','checkSquare','square','circle','triangle','hexagon','octagon','pentagon','diamond'];
|
||||
icons.forEach(function(ic) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'btn btn-outline btn-tiny';
|
||||
btn.textContent = ic;
|
||||
btn.title = 'type=' + ic;
|
||||
btn.onclick = function() { MeToast.success(ic, { type: ic, duration: 2000, showProgress: false }); };
|
||||
grid.appendChild(btn);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,497 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>API 文档 — MetonaToast</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b1121; --surface: #1a2332; --border: #1e2d4d;
|
||||
--text: #e2e8f0; --muted: #64748b; --accent: #6366f1; --accent2: #818cf8;
|
||||
--radius: 10px;
|
||||
}
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:var(--bg); color:var(--text); line-height:1.7; display:flex; min-height:100vh; }
|
||||
code { background:rgba(99,102,241,0.12); color:#a5b4fc; padding:1px 6px; border-radius:4px; font-size:0.9em; white-space:nowrap; }
|
||||
pre { background:#0a0f1a; border:1px solid var(--border); border-radius:8px; padding:14px 18px; overflow-x:auto; font:13px/1.7 'SF Mono','Cascadia Code','Fira Code',Consolas,monospace; color:#cbd5e1; margin:10px 0; }
|
||||
pre .kw { color:#c084fc; } pre .fn { color:#60a5fa; } pre .s { color:#34d399; } pre .cm { color:#475569; }
|
||||
a { color:var(--accent2); text-decoration:none; }
|
||||
|
||||
.sidebar { width:260px; min-width:260px; background:var(--surface); border-right:1px solid var(--border); padding:24px 0; position:sticky; top:0; height:100vh; overflow-y:auto; }
|
||||
.sidebar h2 { font-size:15px; padding:0 24px; margin-bottom:16px; color:var(--accent2); }
|
||||
.sidebar a { display:block; padding:7px 24px; font-size:13px; color:var(--muted); transition:.15s; border-left:2px solid transparent; }
|
||||
.sidebar a:hover, .sidebar a.active { color:var(--accent2); border-left-color:var(--accent); background:rgba(99,102,241,0.06); }
|
||||
.sidebar .section { font-size:11px; text-transform:uppercase; letter-spacing:1px; padding:16px 24px 6px; color:var(--muted); font-weight:600; }
|
||||
|
||||
.main { flex:1; padding:40px 48px; max-width:950px; }
|
||||
.main h1 { font-size:30px; font-weight:700; margin-bottom:8px; }
|
||||
.main h2 { font-size:22px; font-weight:700; margin:44px 0 14px; padding-top:22px; border-top:1px solid var(--border); }
|
||||
.main h3 { font-size:17px; font-weight:600; margin:28px 0 10px; color:var(--accent2); }
|
||||
.main p { margin:8px 0; color:var(--muted); font-size:14px; }
|
||||
.main table { width:100%; border-collapse:collapse; margin:14px 0 20px; font-size:13px; }
|
||||
.main th, .main td { text-align:left; padding:9px 14px; border-bottom:1px solid var(--border); vertical-align:top; }
|
||||
.main th { color:var(--muted); font-weight:500; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; }
|
||||
.main td:first-child { color:var(--accent2); font-family:monospace; font-size:12px; white-space:nowrap; }
|
||||
.main td:nth-child(3) { font-family:monospace; font-size:11px; color:var(--muted); }
|
||||
.badge { display:inline-block; padding:1px 7px; border-radius:3px; font-size:10px; font-weight:700; margin-left:6px; vertical-align:middle; }
|
||||
.badge-new { background:rgba(16,185,129,0.2); color:#34d399; }
|
||||
.hint { font-size:12px; color:var(--muted); margin-top:2px; }
|
||||
.return-box { background:rgba(16,185,129,0.08); border:1px solid rgba(16,185,129,0.2); border-radius:8px; padding:14px 18px; margin:12px 0; }
|
||||
.return-box h4 { font-size:12px; color:#34d399; margin-bottom:6px; text-transform:uppercase; letter-spacing:0.5px; }
|
||||
|
||||
@media (max-width:768px) {
|
||||
body { flex-direction:column; }
|
||||
.sidebar { width:100%; min-width:0; height:auto; position:static; border-right:none; border-bottom:1px solid var(--border); padding:12px 16px; }
|
||||
.sidebar a { display:inline-block; padding:4px 10px; border-left:none; }
|
||||
.main { padding:24px 20px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="sidebar">
|
||||
<h2>📘 MetonaToast 文档</h2>
|
||||
<a href="index.html">← 返回首页</a>
|
||||
<a href="demo.html">🎮 在线演示</a>
|
||||
<div class="section">基础通知</div>
|
||||
<a href="#show">show()</a>
|
||||
<a href="#success">success()</a>
|
||||
<a href="#error">error()</a>
|
||||
<a href="#warning">warning()</a>
|
||||
<a href="#info">info()</a>
|
||||
<a href="#loading">loading()</a>
|
||||
<div class="section">高级功能</div>
|
||||
<a href="#promise">promise()</a>
|
||||
<a href="#confirm">confirm()</a>
|
||||
<a href="#prompt">prompt()</a>
|
||||
<a href="#progress">progress()</a>
|
||||
<a href="#countdown">countdown()</a>
|
||||
<a href="#queue">queue()</a>
|
||||
<a href="#stack">stack()</a>
|
||||
<a href="#action">action() <span class="badge badge-new">NEW</span></a>
|
||||
<div class="section">管理方法</div>
|
||||
<a href="#group">group() <span class="badge badge-new">NEW</span></a>
|
||||
<a href="#dismiss">dismiss()</a>
|
||||
<a href="#clear">clear()</a>
|
||||
<a href="#configure">configure()</a>
|
||||
<a href="#use">use()</a>
|
||||
<a href="#destroy">destroy()</a>
|
||||
<div class="section">配置参考</div>
|
||||
<a href="#config">全部配置项</a>
|
||||
<a href="#callbacks">回调函数</a>
|
||||
<a href="#animations">动画列表</a>
|
||||
<a href="#themes">主题参考</a>
|
||||
</nav>
|
||||
|
||||
<main class="main">
|
||||
|
||||
<h1>API 文档</h1>
|
||||
<p>MetonaToast v2.0.1 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
|
||||
|
||||
<!-- ===== 基础通知 ===== -->
|
||||
<h2 id="show">show(message, opts?)</h2>
|
||||
<p>显示默认类型的 Toast 通知,无特定颜色和图标。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>消息字符串,或包含 title/message 等属性的配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置对象,覆盖 <a href="#config">全部配置项</a>。仅当 message 为字符串时有效</td></tr>
|
||||
</table>
|
||||
<pre><span class="cm">// 字符串形式</span>
|
||||
MeToast.<span class="fn">show</span>(<span class="s">'默认消息'</span>);
|
||||
MeToast.<span class="fn">show</span>(<span class="s">'自定义'</span>, { <span class="fn">duration</span>: 2000, <span class="fn">position</span>: <span class="s">'bottom-center'</span> });
|
||||
<span class="cm">// 对象形式(所有 opts 作为一级属性)</span>
|
||||
MeToast.<span class="fn">show</span>({ <span class="fn">title</span>: <span class="s">'标题'</span>, <span class="fn">message</span>: <span class="s">'内容'</span>, <span class="fn">duration</span>: 3000 });</pre>
|
||||
|
||||
<h2 id="success">success(message, opts?)</h2>
|
||||
<p>显示成功通知。绿色对勾图标 <code>#10b981</code>,type = success。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>消息字符串或配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 success</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">success</span>(<span class="s">'保存成功!'</span>);
|
||||
MeToast.<span class="fn">success</span>({ <span class="fn">title</span>: <span class="s">'已保存'</span>, <span class="fn">message</span>: <span class="s">'数据已同步'</span> });
|
||||
Met.<span class="fn">success</span>(<span class="s">'MeToast和Met等价'</span>);</pre>
|
||||
|
||||
<h2 id="error">error(message, opts?)</h2>
|
||||
<p>显示错误通知。红色叉号图标 <code>#ef4444</code>,type = error。aria-live 设为 assertive。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>消息字符串或配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 error</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">error</span>(<span class="s">'网络错误,请重试'</span>);
|
||||
MeToast.<span class="fn">error</span>({ <span class="fn">title</span>: <span class="s">'提交失败'</span>, <span class="fn">message</span>: <span class="s">'服务器不可达'</span> });</pre>
|
||||
|
||||
<h2 id="warning">warning(message, opts?)</h2>
|
||||
<p>显示警告通知。黄色三角图标 <code>#f59e0b</code>,type = warning。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>消息字符串或配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 warning</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">warning</span>(<span class="s">'请注意检查输入内容'</span>);</pre>
|
||||
|
||||
<h2 id="info">info(message, opts?)</h2>
|
||||
<p>显示信息通知。蓝色圆形图标 <code>#3b82f6</code>,type = info。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>消息字符串或配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 info</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">info</span>(<span class="s">'系统将于 22:00 维护'</span>);</pre>
|
||||
|
||||
<h2 id="loading">loading(message, opts?)</h2>
|
||||
<p>显示加载状态。type = loading,duration 强制为 0(不自动关闭),closeButton 和 showProgress 强制为 false。返回 LoadingControl 对象。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>加载提示文本或配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
|
||||
<span class="fn">setTimeout</span>(() => loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>), 2000);</pre>
|
||||
<div class="return-box">
|
||||
<h4>返回 LoadingControl</h4>
|
||||
<table>
|
||||
<tr><th>方法</th><th>签名</th><th>说明</th></tr>
|
||||
<tr><td>success</td><td>(msg, opts?) => ToastInstance</td><td>关闭加载 toast,原地替换为 success 类型</td></tr>
|
||||
<tr><td>error</td><td>(msg, opts?) => ToastInstance</td><td>替换为 error 类型</td></tr>
|
||||
<tr><td>info</td><td>(msg, opts?) => ToastInstance</td><td>替换为 info 类型</td></tr>
|
||||
<tr><td>warning</td><td>(msg, opts?) => ToastInstance</td><td>替换为 warning 类型</td></tr>
|
||||
<tr><td>update</td><td>(partial) => MeToast</td><td>不关闭加载 toast,原地更新其内容(支持 resetTimerOnUpdate)</td></tr>
|
||||
<tr><td>dismiss</td><td>()</td><td>直接关闭加载 toast 不替换</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ===== 高级功能 ===== -->
|
||||
<h2 id="promise">promise(promise, opts)</h2>
|
||||
<p>监听 Promise 生命周期。自动显示 loading → 根据 resolve/reject 自动切换 success/error。返回原 Promise,支持 await 获取结果。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>promise</td><td>Promise</td><td>—</td><td>要监听的 Promise 对象。非 Promise 会打印错误并返回 rejected Promise</td></tr>
|
||||
<tr><td>opts.loading</td><td>string</td><td>"加载中..."</td><td>加载中显示的文本</td></tr>
|
||||
<tr><td>opts.success</td><td>string</td><td>"操作成功"</td><td>resolve 后显示的文本</td></tr>
|
||||
<tr><td>opts.error</td><td>string</td><td>"操作失败"</td><td>reject 后显示的文本</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">try</span> {
|
||||
<span class="kw">await</span> MeToast.<span class="fn">promise</span>(<span class="fn">fetch</span>(<span class="s">'/api/data'</span>), {
|
||||
<span class="fn">loading</span>: <span class="s">'加载中...'</span>,
|
||||
<span class="fn">success</span>: <span class="s">'加载完成!'</span>,
|
||||
<span class="fn">error</span>: <span class="s">'加载失败'</span>,
|
||||
});
|
||||
} <span class="kw">catch</span> (<span class="fn">e</span>) { <span class="cm">/* promise reject 会继续抛出 */</span> }</pre>
|
||||
|
||||
<h2 id="confirm">confirm(message, opts?)</h2>
|
||||
<p>确认对话框。返回 <code>Promise<boolean></code>。内置 10 秒安全超时,超时自动 resolve(false)。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string</td><td>—</td><td>对话框消息。非字符串会打印错误并 resolve(false)</td></tr>
|
||||
<tr><td>opts.confirmText</td><td>string</td><td>"确认"</td><td>确认按钮文字</td></tr>
|
||||
<tr><td>opts.confirmColor</td><td>string</td><td>"#10b981"</td><td>确认按钮背景色</td></tr>
|
||||
<tr><td>opts.cancelText</td><td>string</td><td>"取消"</td><td>取消按钮文字</td></tr>
|
||||
<tr><td>opts.cancelColor</td><td>string</td><td>"#6b7280"</td><td>取消按钮背景色</td></tr>
|
||||
<tr><td>opts.type</td><td>string</td><td>"warning"</td><td>Toast 类型(影响图标和颜色)</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">const</span> ok = <span class="kw">await</span> MeToast.<span class="fn">confirm</span>(<span class="s">'确定删除?'</span>, {
|
||||
<span class="fn">confirmText</span>: <span class="s">'删除'</span>,
|
||||
<span class="fn">confirmColor</span>: <span class="s">'#ef4444'</span>,
|
||||
<span class="fn">cancelText</span>: <span class="s">'保留'</span>,
|
||||
});
|
||||
<span class="kw">if</span> (ok) MeToast.<span class="fn">success</span>(<span class="s">'已删除'</span>);</pre>
|
||||
|
||||
<h2 id="prompt">prompt(message, opts?)</h2>
|
||||
<p>输入对话框。返回 <code>Promise<string|null></code>。按 Enter 或点击提交按钮返回输入值,取消返回 null。内置 10 秒安全超时。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string</td><td>—</td><td>对话框消息。非字符串会打印错误并 resolve(null)</td></tr>
|
||||
<tr><td>opts.placeholder</td><td>string</td><td>""</td><td>输入框占位文字</td></tr>
|
||||
<tr><td>opts.defaultValue</td><td>string</td><td>""</td><td>输入框默认值</td></tr>
|
||||
<tr><td>opts.inputType</td><td>string</td><td>"text"</td><td>input 标签 type 属性(如 password/email/number)</td></tr>
|
||||
<tr><td>opts.submitText</td><td>string</td><td>"确认"</td><td>提交按钮文字</td></tr>
|
||||
<tr><td>opts.submitColor</td><td>string</td><td>"#3b82f6"</td><td>提交按钮背景色</td></tr>
|
||||
<tr><td>opts.cancelText</td><td>string</td><td>"取消"</td><td>取消按钮文字</td></tr>
|
||||
<tr><td>opts.cancelColor</td><td>string</td><td>"#6b7280"</td><td>取消按钮背景色</td></tr>
|
||||
<tr><td>opts.type</td><td>string</td><td>"info"</td><td>Toast 类型</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">const</span> name = <span class="kw">await</span> MeToast.<span class="fn">prompt</span>(<span class="s">'请输入姓名'</span>, {
|
||||
<span class="fn">placeholder</span>: <span class="s">'请输入...'</span>,
|
||||
<span class="fn">defaultValue</span>: <span class="s">'张三'</span>,
|
||||
<span class="fn">submitText</span>: <span class="s">'确定'</span>,
|
||||
});
|
||||
<span class="kw">if</span> (name) MeToast.<span class="fn">info</span>(<span class="s">'你好,'</span> + name);</pre>
|
||||
|
||||
<h2 id="progress">progress(message, opts?)</h2>
|
||||
<p>进度条通知。不自动关闭。返回 ProgressControl 对象以手动更新进度。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>进度提示文本或配置对象</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置。type 默认 info</td></tr>
|
||||
<tr><td>opts.progressColor</td><td>string</td><td>"#3b82f6"</td><td>进度条填充颜色</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">const</span> p = MeToast.<span class="fn">progress</span>(<span class="s">'上传中...'</span>, { <span class="fn">progressColor</span>: <span class="s">'#10b981'</span> });
|
||||
p.<span class="fn">setProgress</span>(45); <span class="cm">// → 45%</span>
|
||||
p.<span class="fn">setProgress</span>(90); <span class="cm">// → 90%</span>
|
||||
p.<span class="fn">complete</span>(<span class="s">'上传完成!'</span>); <span class="cm">// → 100% → success</span>
|
||||
<span class="cm">// 或</span>
|
||||
p.<span class="fn">error</span>(<span class="s">'上传失败'</span>);
|
||||
p.<span class="fn">dismiss</span>();</pre>
|
||||
<div class="return-box">
|
||||
<h4>返回 ProgressControl</h4>
|
||||
<table>
|
||||
<tr><th>方法</th><th>签名</th><th>说明</th></tr>
|
||||
<tr><td>setProgress</td><td>(percent: number)</td><td>设置进度 0~100,自动 clamp。更新进度条宽度和百分比文字</td></tr>
|
||||
<tr><td>complete</td><td>(message?: string)</td><td>跳到 100%,300ms 后替换为 success toast,1s 后自动关闭</td></tr>
|
||||
<tr><td>error</td><td>(message?: string)</td><td>替换为 error toast,2s 后自动关闭</td></tr>
|
||||
<tr><td>dismiss</td><td>()</td><td>直接关闭</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="countdown">countdown(message, seconds, opts?)</h2>
|
||||
<p>倒计时 Toast。<code>{seconds}</code> 占位符每秒自动替换为剩余秒数。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string</td><td>—</td><td>消息文本。支持 <code>{seconds}</code> 占位符。非字符串打印错误并返回空控制对象</td></tr>
|
||||
<tr><td>seconds</td><td>number</td><td>10</td><td>倒计时秒数,最小 1</td></tr>
|
||||
<tr><td>opts.onComplete</td><td>function</td><td>—</td><td>倒计时归零时的回调</td></tr>
|
||||
<tr><td>opts.type</td><td>string</td><td>"warning"</td><td>Toast 类型</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">countdown</span>(<span class="s">'{seconds} 秒后执行'</span>, 5, {
|
||||
<span class="fn">onComplete</span>: () => MeToast.<span class="fn">success</span>(<span class="s">'已执行'</span>),
|
||||
});</pre>
|
||||
<div class="return-box">
|
||||
<h4>返回 CountdownControl</h4>
|
||||
<table>
|
||||
<tr><th>方法</th><th>说明</th></tr>
|
||||
<tr><td>cancel()</td><td>清除计时器并关闭 toast</td></tr>
|
||||
<tr><td>pause()</td><td>暂停倒计时</td></tr>
|
||||
<tr><td>resume()</td><td>恢复倒计时</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="queue">queue(messages, opts?)</h2>
|
||||
<p>顺序逐个显示消息队列。前一条关闭后延时显示下一条。返回 QueueControl(thenable + cancel)。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>messages</td><td>Array<string|object></td><td>—</td><td>消息数组。可为字符串或带 message/type/duration/onClose 的对象。非数组打印错误并返回空控制对象</td></tr>
|
||||
<tr><td>opts.delay</td><td>number</td><td>1000</td><td>每条消息关闭后到显示下一条的间隔(ms)</td></tr>
|
||||
<tr><td>opts.duration</td><td>number</td><td>3000</td><td>每条消息显示时长(ms),可被消息级 duration 覆盖</td></tr>
|
||||
<tr><td>opts.onClose</td><td>function</td><td>—</td><td>全部队列完成后的回调。消息级 onClose 和队列级 onClose 都会依次调用</td></tr>
|
||||
<tr><td>opts.type</td><td>string</td><td>—</td><td>默认类型</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">const</span> q = MeToast.<span class="fn">queue</span>([
|
||||
<span class="s">'步骤一'</span>,
|
||||
{ <span class="fn">message</span>: <span class="s">'步骤二'</span>, <span class="fn">duration</span>: 5000, <span class="fn">type</span>: <span class="s">'warning'</span> },
|
||||
<span class="s">'步骤三'</span>,
|
||||
], { <span class="fn">delay</span>: 800, <span class="fn">duration</span>: 2000, <span class="fn">type</span>: <span class="s">'info'</span> });
|
||||
q.<span class="fn">cancel</span>(); <span class="cm">// 中途取消</span>
|
||||
<span class="kw">await</span> q; <span class="cm">// 等待完成(thenable 支持 await)</span></pre>
|
||||
<div class="return-box">
|
||||
<h4>返回 QueueControl (thenable)</h4>
|
||||
<table>
|
||||
<tr><th>方法</th><th>说明</th></tr>
|
||||
<tr><td>.then(fn, rj)</td><td>Promise.then 代理,支持 await</td></tr>
|
||||
<tr><td>.catch(rj)</td><td>Promise.catch 代理</td></tr>
|
||||
<tr><td>.cancel()</td><td>设置取消标志,不再显示下一条消息</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="stack">stack(messages, opts?)</h2>
|
||||
<p>同时错峰显示多条消息。每条间隔 stagger 毫秒依次出现,全部叠加在屏幕上。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>messages</td><td>Array<string|object></td><td>—</td><td>消息数组。可为字符串或带 message/type/duration 的对象。非数组打印错误</td></tr>
|
||||
<tr><td>opts.stagger</td><td>number</td><td>100</td><td>每条消息之间的显示间隔(ms)</td></tr>
|
||||
<tr><td>opts.type</td><td>string</td><td>—</td><td>默认类型</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">stack</span>([<span class="s">'消息1'</span>, <span class="s">'消息2'</span>, { <span class="fn">message</span>: <span class="s">'警告'</span>, <span class="fn">type</span>: <span class="s">'warning'</span> }], {
|
||||
<span class="fn">stagger</span>: 150,
|
||||
<span class="fn">type</span>: <span class="s">'info'</span>,
|
||||
});</pre>
|
||||
|
||||
<h2 id="action">action(message, actions, opts?) <span class="badge badge-new">v2.0</span></h2>
|
||||
<p>Action Toast:内嵌操作按钮。默认 duration=0 不自动关闭,closeButton=true。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>message</td><td>string | object</td><td>—</td><td>消息字符串或配置对象</td></tr>
|
||||
<tr><td>actions</td><td>ActionButton[]</td><td>[]</td><td>按钮数组。每个按钮可配 text/onClick/color/style/close</td></tr>
|
||||
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置。duration 默认 0,closeButton 默认 true</td></tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr><th>ActionButton 字段</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>text</td><td>string</td><td>—(必填)</td><td>按钮显示文字</td></tr>
|
||||
<tr><td>onClick</td><td>(toast) => void</td><td>—(必填)</td><td>点击回调函数,参数为当前 toast 实例</td></tr>
|
||||
<tr><td>color</td><td>string</td><td>"#6366f1"</td><td>按钮背景色</td></tr>
|
||||
<tr><td>style</td><td>object</td><td>{}</td><td>按钮内联样式,可覆盖 color(style.background 优先)</td></tr>
|
||||
<tr><td>close</td><td>boolean</td><td>true</td><td>点击后是否自动关闭 toast。设为 false 可多次点击</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">action</span>(<span class="s">'文件已删除'</span>, [
|
||||
{ <span class="fn">text</span>: <span class="s">'撤销'</span>, <span class="fn">onClick</span>: () => <span class="fn">restore</span>(), <span class="fn">color</span>: <span class="s">'#3b82f6'</span> },
|
||||
{ <span class="fn">text</span>: <span class="s">'查看详情'</span>, <span class="fn">onClick</span>: (t) => <span class="fn">openFile</span>(), <span class="fn">color</span>: <span class="s">'#10b981'</span>, <span class="fn">close</span>: false },
|
||||
]);</pre>
|
||||
|
||||
<!-- ===== 管理方法 ===== -->
|
||||
<h2 id="group">group(name) <span class="badge badge-new">v2.0</span></h2>
|
||||
<p>创建 Toast 分组,返回 GroupAPI 对象。该对象所有方法自动传入 <code>group: name</code>,按组管理。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
|
||||
<tr><td>name</td><td>string</td><td>分组名称。GroupAPI 和 dismissGroup 通过此名称关联</td></tr>
|
||||
</table>
|
||||
<pre><span class="kw">const</span> orders = MeToast.<span class="fn">group</span>(<span class="s">'orders'</span>);
|
||||
orders.<span class="fn">success</span>(<span class="s">'订单已创建'</span>);
|
||||
orders.<span class="fn">error</span>(<span class="s">'支付失败'</span>, { <span class="fn">duration</span>: 5000 });
|
||||
orders.<span class="fn">count</span>(); <span class="cm">// 该组当前 toast 数量</span>
|
||||
orders.<span class="fn">dismiss</span>(); <span class="cm">// 关闭该组全部 toast</span>
|
||||
<span class="cm">// 也支持主对象关闭</span>
|
||||
MeToast.<span class="fn">dismissGroup</span>(<span class="s">'orders'</span>);</pre>
|
||||
<div class="return-box">
|
||||
<h4>返回 GroupAPI</h4>
|
||||
<table>
|
||||
<tr><th>方法</th><th>说明</th></tr>
|
||||
<tr><td>show(msg, opts?)</td><td>等价 MeToast.show,自动注入 group</td></tr>
|
||||
<tr><td>success(msg, opts?)</td><td>等价 MeToast.success</td></tr>
|
||||
<tr><td>error(msg, opts?)</td><td>等价 MeToast.error</td></tr>
|
||||
<tr><td>warning(msg, opts?)</td><td>等价 MeToast.warning</td></tr>
|
||||
<tr><td>info(msg, opts?)</td><td>等价 MeToast.info</td></tr>
|
||||
<tr><td>loading(msg, opts?)</td><td>等价 MeToast.loading</td></tr>
|
||||
<tr><td>action(msg, actions, opts?)</td><td>等价 MeToast.action</td></tr>
|
||||
<tr><td>dismiss()</td><td>关闭该组所有 toast</td></tr>
|
||||
<tr><td>count()</td><td>返回该组当前 toast 数量</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="dismiss">dismiss(id?)</h2>
|
||||
<p>关闭 Toast。无参数则关闭全部。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
|
||||
<tr><td>id</td><td>string</td><td>可选。Toast 的 id,不传则关闭所有</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">dismiss</span>(); <span class="cm">// 关闭所有</span>
|
||||
MeToast.<span class="fn">dismiss</span>(toast.id); <span class="cm">// 关闭指定</span></pre>
|
||||
|
||||
<h2 id="clear">clear(position?)</h2>
|
||||
<p>按位置清除 Toast。不传参数清除所有位置。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
|
||||
<tr><td>position</td><td>string</td><td>可选。位置如 'top-right',不传则清除全部</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">clear</span>(); <span class="cm">// 全部</span>
|
||||
MeToast.<span class="fn">clear</span>(<span class="s">'bottom-right'</span>); <span class="cm">// 仅右下角</span></pre>
|
||||
|
||||
<h2 id="configure">configure(opts)</h2>
|
||||
<p>全局配置,影响后续所有 Toast。theme 和 locale 变化会触发相应副作用(应用主题 CSS / 切换语言)。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
|
||||
<tr><td>opts</td><td>object</td><td>包含任意 <a href="#config">配置项</a> 的对象</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">configure</span>({
|
||||
<span class="fn">position</span>: <span class="s">'top-right'</span>,
|
||||
<span class="fn">duration</span>: 4000,
|
||||
<span class="fn">theme</span>: <span class="s">'dark'</span>,
|
||||
<span class="fn">animation</span>: <span class="s">'slide'</span>,
|
||||
<span class="fn">locale</span>: <span class="s">'zh-CN'</span>,
|
||||
});</pre>
|
||||
|
||||
<h2 id="use">use(plugin)</h2>
|
||||
<p>安装插件。支持字符串(内置预设名)或插件对象。已被拒绝注册的无效插件会打印错误。</p>
|
||||
<table>
|
||||
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
|
||||
<tr><td>plugin</td><td>string | object</td><td>预设名 'keyboard'/'persistence'/'accessibility' 或自定义插件对象 { name, version?, install, uninstall? }</td></tr>
|
||||
</table>
|
||||
<pre>MeToast.<span class="fn">use</span>(<span class="s">'keyboard'</span>); <span class="cm">// ESC 关闭所有 Toast</span>
|
||||
MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>); <span class="cm">// 配置自动保存到 localStorage</span>
|
||||
MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <span class="cm">// 屏幕阅读器实时朗读 toast 内容</span></pre>
|
||||
<p class="hint">内置插件详情:keyboard 监听 keydown Escape 键;persistence 将配置写入 localStorage 并在页面加载时恢复;accessibility 在 afterShow/afterUpdate 钩子中通过 aria-live 区域朗读 toast 内容。</p>
|
||||
|
||||
<h2 id="destroy">destroy()</h2>
|
||||
<p>完全销毁。关闭所有 Toast、移除所有 DOM 容器和注入的样式标签、清空内存缓存。</p>
|
||||
<pre>MeToast.<span class="fn">destroy</span>();</pre>
|
||||
|
||||
<!-- ===== 配置参考 ===== -->
|
||||
<h2 id="config">全部配置项</h2>
|
||||
<p>以下配置可用于 <code>configure()</code>、<code>init()</code> 或单个 Toast 方法的 opts 参数。带 <span class="badge badge-new">v2.0</span> 标识的为 v2.0 新增。</p>
|
||||
<table>
|
||||
<tr><th>配置项</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||
<tr><td>position</td><td>string</td><td>'top-right'</td><td>位置:top-left / top-center / top-right / bottom-left / bottom-center / bottom-right</td></tr>
|
||||
<tr><td>duration</td><td>number</td><td>4000</td><td>显示时长(ms),0=不自动关闭</td></tr>
|
||||
<tr><td>max</td><td>number</td><td>6</td><td>同一位置最多同时显示条数,超出则关闭最早的</td></tr>
|
||||
<tr><td>gap</td><td>number</td><td>12</td><td>Toast 之间的间距(px)</td></tr>
|
||||
<tr><td>offset</td><td>number</td><td>24</td><td>容器到屏幕边缘的距离(px)</td></tr>
|
||||
<tr><td>pauseOnHover</td><td>boolean</td><td>true</td><td>鼠标悬停时暂停 duration 倒计时和进度条</td></tr>
|
||||
<tr><td>closeOnClick</td><td>boolean</td><td>true</td><td>点击 Toast 任意位置关闭。关闭按钮始终触发关闭</td></tr>
|
||||
<tr><td>draggable</td><td>boolean</td><td>true</td><td>允许拖拽关闭。拖拽超过 120px 触发关闭</td></tr>
|
||||
<tr><td>showProgress</td><td>boolean</td><td>true</td><td>显示 duration 倒计时进度条</td></tr>
|
||||
<tr><td>progressDirection</td><td>string</td><td>'horizontal'</td><td>进度条方向:horizontal(底部水平) / vertical(右侧垂直)</td></tr>
|
||||
<tr><td>icon</td><td>boolean</td><td>true</td><td>显示类型对应图标(success→对勾等)</td></tr>
|
||||
<tr><td>closeButton</td><td>boolean</td><td>true</td><td>显示右上角关闭 × 按钮</td></tr>
|
||||
<tr><td>theme</td><td>string</td><td>'auto'</td><td>主题:light / dark / auto(跟随系统)/ warm / 自定义注册名</td></tr>
|
||||
<tr><td>animation</td><td>string</td><td>'slide'</td><td>入场动画名称,支持 CSS 动画列表见下</td></tr>
|
||||
<tr><td>zIndex</td><td>number</td><td>9999</td><td>容器 CSS z-index</td></tr>
|
||||
<tr><td>width</td><td>number|string</td><td>360</td><td>Toast 宽度。数字表示 px</td></tr>
|
||||
<tr><td>className</td><td>string</td><td>''</td><td>附加到 Toast 元素上的 CSS 类名</td></tr>
|
||||
<tr><td>style</td><td>object</td><td>{}</td><td>附加到 Toast 元素上的内联样式对象</td></tr>
|
||||
<tr><td>locale</td><td>string</td><td>'zh-CN'</td><td>语言代码(zh-CN / en-US 等)</td></tr>
|
||||
<tr><td>resetTimerOnUpdate <span class="badge badge-new">v2.0</span></td><td>boolean</td><td>false</td><td>调用 update() 时重置 duration 倒计时</td></tr>
|
||||
<tr><td>notifyWhenHidden <span class="badge badge-new">v2.0</span></td><td>boolean</td><td>false</td><td>页面不可见时自动通过 Notification API 发送系统通知</td></tr>
|
||||
<tr><td>render <span class="badge badge-new">v2.0</span></td><td>function</td><td>—</td><td>自定义渲染函数 <code>(toast) => htmlString</code>,完全接管 DOM 构建</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 id="callbacks">回调函数</h2>
|
||||
<table>
|
||||
<tr><th>回调</th><th>签名</th><th>触发时机</th></tr>
|
||||
<tr><td>onShow</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 创建并播放入场动画后</td></tr>
|
||||
<tr><td>onClose</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 被移除后(离场动画完成时)</td></tr>
|
||||
<tr><td>onClick</td><td>(toast: ToastInstance) => void</td><td>Toast 被点击时(closeOnClick 为 true 时还会自动关闭)</td></tr>
|
||||
<tr><td>onUpdate <span class="badge badge-new">v2.0</span></td><td>(toast: ToastInstance) => void</td><td>Toast 内容通过 update() 更新后</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 id="animations">动画列表</h2>
|
||||
<p>配置 <code>animation</code> 的值,支持以下 CSS 动画。未在此列表中的值将 fallback 到 slide。</p>
|
||||
<table>
|
||||
<tr><th>名称</th><th>效果</th><th>时长</th></tr>
|
||||
<tr><td>slide</td><td>从右侧滑入 + 回弹</td><td>400ms</td></tr>
|
||||
<tr><td>fade</td><td>纯淡入 + blur→清晰</td><td>500ms</td></tr>
|
||||
<tr><td>scale</td><td>弹性放大 (0.55→1.07→1)</td><td>450ms</td></tr>
|
||||
<tr><td>bounce</td><td>从天而降四段弹跳</td><td>650ms</td></tr>
|
||||
<tr><td>flip</td><td>3D 翻转入场 + 回摆</td><td>500ms</td></tr>
|
||||
<tr><td>rotate</td><td>旋转摇摆进入</td><td>500ms</td></tr>
|
||||
<tr><td>zoom</td><td>从中心爆发式弹出</td><td>500ms</td></tr>
|
||||
<tr><td>slideUp</td><td>从下方弹入</td><td>400ms</td></tr>
|
||||
<tr><td>slideDown</td><td>从上方弹入</td><td>400ms</td></tr>
|
||||
<tr><td>slideLeft</td><td>从左侧滑入</td><td>400ms</td></tr>
|
||||
<tr><td>slideRight</td><td>从右侧滑入</td><td>400ms</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 id="themes">主题参考</h2>
|
||||
<pre><span class="cm">// 内置主题</span>
|
||||
light <span class="cm">— 白色玻璃质感</span>
|
||||
dark <span class="cm">— 深色玻璃质感</span>
|
||||
auto <span class="cm">— 跟随系统主题设置</span>
|
||||
warm <span class="cm">— 暖色调</span>
|
||||
|
||||
<span class="cm">// 注册自定义主题</span>
|
||||
MeToast.themes.<span class="fn">registerTheme</span>(<span class="s">'ocean'</span>, {
|
||||
<span class="fn">bg</span>: <span class="s">'rgba(240,249,255,0.96)'</span>,
|
||||
<span class="fn">text</span>: <span class="s">'#0c4a6e'</span>,
|
||||
<span class="fn">border</span>: <span class="s">'rgba(14,165,233,0.2)'</span>,
|
||||
<span class="fn">shadow</span>: <span class="s">'0 10px 36px -10px rgba(14,165,233,0.18)'</span>,
|
||||
<span class="fn">hoverShadow</span>: <span class="s">'0 14px 48px -10px rgba(14,165,233,0.22)'</span>,
|
||||
<span class="fn">progressBg</span>: <span class="s">'rgba(14,165,233,0.1)'</span>,
|
||||
<span class="fn">closeHoverBg</span>: <span class="s">'rgba(14,165,233,0.1)'</span>,
|
||||
});
|
||||
MeToast.themes.<span class="fn">switchTheme</span>(<span class="s">'ocean'</span>);</pre>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="../dist/metona-toast.js"></script>
|
||||
<script>
|
||||
MeToast.configure({ position: 'top-right', duration: 4000, theme: 'dark', animation: 'slide' });
|
||||
document.querySelectorAll('.sidebar a[href^="#"]').forEach(a => {
|
||||
a.addEventListener('click', () => {
|
||||
document.querySelectorAll('.sidebar a').forEach(x => x.classList.remove('active'));
|
||||
a.classList.add('active');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user