UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

125 lines (106 loc) • 4.42 kB
/** * @moduleName: Frontend Utilities - DOM Manipulation and Event System * @version: 2.0.0 * @since: 2025-07-23 * @lastUpdated: 2025-07-24 * @projectSummary: MCP Quiz Server - Utility classes for DOM manipulation, event handling, and common frontend operations * @techStack: TypeScript, DOM API, Event System, HTML Sanitization * @dependency: Browser DOM API * @interModuleDependency: Used by all frontend components and services * @requirementsTraceability: * {@link Requirements.REQ_ARCH_001} (Component Integration System) * {@link Requirements.REQ_SEC_001} (OWASP Input Sanitization) * @briefDescription: Collection of utility classes providing DOM manipulation, event handling, HTML escaping, and common frontend operations with XSS protection * @methods: DOMUtils (escapeHtml, createElement), EventEmitter (on, off, emit), FormUtils (serialize, validate) * @contributors: Architecture Team, GitHub Copilot * @examples: DOMUtils.escapeHtml(userInput), new EventEmitter().on('event', handler) * @vulnerabilitiesAssessment: HTML escaping prevents XSS attacks, DOM element creation is sanitized, event handling prevents memory leaks */ export class DOMUtils { static escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } static createElement<T extends HTMLElement>( tag: string, options: { className?: string; id?: string; innerHTML?: string; textContent?: string; attributes?: Record<string, string>; dataset?: Record<string, string>; } = {} ): T { const element = document.createElement(tag) as T; if (options.className) element.className = options.className; if (options.id) element.id = options.id; if (options.innerHTML) element.innerHTML = options.innerHTML; if (options.textContent) element.textContent = options.textContent; if (options.attributes) { Object.entries(options.attributes).forEach(([key, value]) => { element.setAttribute(key, value); }); } if (options.dataset) { Object.entries(options.dataset).forEach(([key, value]) => { element.dataset[key] = value; }); } return element; } static showToast(message: string, type: 'success' | 'error' | 'info' = 'info'): void { const colors = { success: 'bg-green-500', error: 'bg-red-500', info: 'bg-blue-500', }; const toast = this.createElement('div', { className: `fixed top-4 right-4 ${colors[type]} text-white px-4 py-2 rounded-lg shadow-lg z-50 transition-all duration-300`, textContent: message, }); document.body.appendChild(toast); // Animate in setTimeout(() => toast.classList.add('translate-x-0'), 10); // Animate out and remove setTimeout(() => { toast.classList.add('translate-x-full', 'opacity-0'); setTimeout(() => toast.remove(), 300); }, 3000); } } export class QuizUtils { static getGrade(percentage: number): { text: string; color: string; icon: string } { if (percentage >= 90) return { text: 'Excellent!', color: 'green', icon: 'trophy' }; if (percentage >= 80) return { text: 'Great Job!', color: 'blue', icon: 'thumbs-up' }; if (percentage >= 70) return { text: 'Good Work!', color: 'yellow', icon: 'star' }; if (percentage >= 60) return { text: 'Keep Trying!', color: 'orange', icon: 'target' }; return { text: 'Study More!', color: 'red', icon: 'book-open' }; } static calculateEstimatedTime(questionCount: number): number { return Math.ceil(questionCount * 1.5); } } export class EventEmitter<T = any> { private events: Map<string, Set<(data: T) => void>> = new Map(); on(event: string, callback: (data: T) => void): () => void { if (!this.events.has(event)) { this.events.set(event, new Set()); } this.events.get(event)!.add(callback); return () => { this.events.get(event)?.delete(callback); }; } emit(event: string, data: T): void { this.events.get(event)?.forEach(callback => callback(data)); } off(event: string, callback?: (data: T) => void): void { if (callback) { this.events.get(event)?.delete(callback); } else { this.events.delete(event); } } }