init: MetonaToast v2.0.0 基线 — 已完成改进(模板抽象/constants拆分/测试增强)
This commit is contained in:
+204
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* MetonaToast Plugins - 插件系统
|
||||
* @module plugins
|
||||
* @version 2.0.0
|
||||
* @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
*/
|
||||
class PluginManager {
|
||||
constructor() {
|
||||
this.plugins = new Map();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
register(name, plugin) {
|
||||
if (this.plugins.has(name)) {
|
||||
console.warn(`Plugin "${name}" is already registered`);
|
||||
return this;
|
||||
}
|
||||
if (!plugin || typeof plugin !== 'object' || (!plugin.name && !plugin.version)) {
|
||||
console.error(`Invalid plugin "${name}"`);
|
||||
return this;
|
||||
}
|
||||
|
||||
this.plugins.set(name, { name, ...plugin, installed: false, enabled: true });
|
||||
|
||||
if (plugin.install) {
|
||||
try { plugin.install(this); this.plugins.get(name).installed = true; }
|
||||
catch (e) { console.error(`Failed to install plugin "${name}":`, e); }
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
unregister(name) {
|
||||
const plugin = this.plugins.get(name);
|
||||
if (!plugin) return this;
|
||||
if (plugin.uninstall) {
|
||||
try { plugin.uninstall(this); } catch (e) { console.error(`Failed to uninstall plugin "${name}":`, e); }
|
||||
}
|
||||
this.plugins.delete(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
get(name) { return this.plugins.get(name) || null; }
|
||||
has(name) { return this.plugins.has(name); }
|
||||
getAll() { return Array.from(this.plugins.values()); }
|
||||
getNames() { return Array.from(this.plugins.keys()); }
|
||||
|
||||
enable(name) {
|
||||
const p = this.plugins.get(name);
|
||||
if (p) p.enabled = true;
|
||||
return this;
|
||||
}
|
||||
disable(name) {
|
||||
const p = this.plugins.get(name);
|
||||
if (p) p.enabled = false;
|
||||
return this;
|
||||
}
|
||||
isEnabled(name) {
|
||||
const p = this.plugins.get(name);
|
||||
return p ? p.enabled : false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.plugins.forEach((p, name) => {
|
||||
if (p.destroy) { try { p.destroy(this); } catch (e) {} }
|
||||
});
|
||||
this.plugins.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设插件
|
||||
*/
|
||||
const presetPlugins = {
|
||||
|
||||
/**
|
||||
* 键盘快捷键插件 — ESC 关闭所有 Toast
|
||||
*/
|
||||
keyboard: {
|
||||
name: 'keyboard',
|
||||
version: '1.0.0',
|
||||
description: 'ESC 关闭所有 Toast',
|
||||
|
||||
_handler: null,
|
||||
|
||||
install() {
|
||||
if (typeof document === 'undefined') return;
|
||||
this._handler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
// 点击所有关闭按钮
|
||||
document.querySelectorAll('.met-toast .met-close').forEach(btn => {
|
||||
try { btn.click(); } catch (_) {}
|
||||
});
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', this._handler);
|
||||
},
|
||||
|
||||
uninstall() {
|
||||
if (this._handler && typeof document !== 'undefined') {
|
||||
document.removeEventListener('keydown', this._handler);
|
||||
this._handler = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 持久化插件 — 自动保存/加载配置到 localStorage
|
||||
*/
|
||||
persistence: {
|
||||
name: 'persistence',
|
||||
version: '1.0.0',
|
||||
description: '自动持久化配置到 localStorage',
|
||||
storageKey: 'metona-toast-config',
|
||||
|
||||
install() {
|
||||
// 加载已保存的配置
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
try {
|
||||
const saved = localStorage.getItem(this.storageKey);
|
||||
return saved ? JSON.parse(saved) : null;
|
||||
} catch (_) { return null; }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
save(config) {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
try { localStorage.setItem(this.storageKey, JSON.stringify(config)); } catch (_) {}
|
||||
}
|
||||
},
|
||||
|
||||
uninstall() {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
try { localStorage.removeItem(this.storageKey); } catch (_) {}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 无障碍插件 — 屏幕阅读器公告
|
||||
*/
|
||||
accessibility: {
|
||||
name: 'accessibility',
|
||||
version: '1.0.0',
|
||||
description: '通过屏幕阅读器朗读 Toast 内容',
|
||||
|
||||
announce(toast) {
|
||||
if (typeof document === 'undefined') return;
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('aria-live', 'assertive');
|
||||
el.setAttribute('aria-atomic', 'true');
|
||||
el.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;';
|
||||
const typeNames = { success:'成功', error:'错误', warning:'警告', info:'信息', loading:'加载中', default:'通知' };
|
||||
el.textContent = `${typeNames[toast.type] || '通知'}: ${toast.title || ''} ${toast.message || ''}`;
|
||||
document.body.appendChild(el);
|
||||
setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 插件工具
|
||||
*/
|
||||
const pluginUtils = {
|
||||
createManager() { return new PluginManager(); },
|
||||
register(name, plugin) { return defaultPluginManager.register(name, plugin); },
|
||||
unregister(name) { return defaultPluginManager.unregister(name); },
|
||||
get(name) { return defaultPluginManager.get(name); },
|
||||
has(name) { return defaultPluginManager.has(name); },
|
||||
getAll() { return defaultPluginManager.getAll(); },
|
||||
getNames() { return defaultPluginManager.getNames(); },
|
||||
enable(name) { return defaultPluginManager.enable(name); },
|
||||
disable(name) { return defaultPluginManager.disable(name); },
|
||||
isEnabled(name) { return defaultPluginManager.isEnabled(name); },
|
||||
getPreset(name) { return presetPlugins[name] || null; },
|
||||
getAllPresets() { return { ...presetPlugins }; },
|
||||
|
||||
createPlugin(config) {
|
||||
return {
|
||||
name: config.name || 'custom',
|
||||
version: config.version || '1.0.0',
|
||||
description: config.description || '',
|
||||
hooks: config.hooks || {},
|
||||
install: config.install || null,
|
||||
uninstall: config.uninstall || null,
|
||||
...config,
|
||||
};
|
||||
},
|
||||
|
||||
validatePlugin(plugin) {
|
||||
const errors = [];
|
||||
if (!plugin || typeof plugin !== 'object') errors.push('Plugin must be an object');
|
||||
if (!plugin.name && !plugin.version) errors.push('Plugin must have a name or version');
|
||||
return { valid: errors.length === 0, errors };
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPluginManager = new PluginManager();
|
||||
|
||||
export { presetPlugins, pluginUtils, defaultPluginManager, PluginManager };
|
||||
Reference in New Issue
Block a user