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
text/typescript
/**
* @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);
}
}
}