/** * MetonaToast Plugins - 插件系统 * @module plugins * @version 2.0.0 * @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility) */ import { t } from './i18n.js'; /** * 插件管理器 */ 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 typeName = t(toast.type) || toast.type; el.textContent = `${typeName}: ${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 };