/** * MYP Platform Core Utilities - Optimized Version * Consolidates common functionality from multiple files * Reduces redundancy and improves performance */ (function(window) { 'use strict'; // Single instance pattern for core utilities const MYPCore = { // Version for cache busting version: '1.0.0', // CSRF Token Management (replaces 22+ different implementations) csrf: { _token: null, getToken() { if (!this._token) { // Try multiple sources in order of preference this._token = document.querySelector('meta[name="csrf-token"]')?.content || document.querySelector('input[name="csrf_token"]')?.value || this._getFromCookie('csrf_token') || ''; } return this._token; }, _getFromCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? match[2] : null; }, clearCache() { this._token = null; } }, // DOM Utilities with caching dom: { _cache: new Map(), // Cached querySelector get(selector, parent = document) { const key = `${parent === document ? 'doc' : 'el'}_${selector}`; if (!this._cache.has(key)) { this._cache.set(key, parent.querySelector(selector)); } return this._cache.get(key); }, // Cached querySelectorAll getAll(selector, parent = document) { return parent.querySelectorAll(selector); }, // Efficient HTML escaping escapeHtml(text) { const div = this._escapeDiv || (this._escapeDiv = document.createElement('div')); div.textContent = text; return div.innerHTML; }, // Clear cache when DOM changes clearCache() { this._cache.clear(); }, // Efficient element creation create(tag, attrs = {}, children = []) { const el = document.createElement(tag); Object.entries(attrs).forEach(([key, value]) => { if (key === 'class') { el.className = value; } else if (key === 'style' && typeof value === 'object') { Object.assign(el.style, value); } else if (key.startsWith('data-')) { el.dataset[key.slice(5)] = value; } else { el.setAttribute(key, value); } }); children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { el.appendChild(child); } }); return el; } }, // Unified Notification System (replaces 3 separate systems) notify: { _container: null, _queue: [], _activeToasts: new Map(), _toastId: 0, show(message, type = 'info', duration = 5000, options = {}) { // Check for DND mode if (window.dndManager?.isEnabled && !options.force) { console.log(`[DND] Suppressed ${type} notification:`, message); return null; } const id = `toast-${++this._toastId}`; const toast = this._createToast(id, message, type, duration, options); this._ensureContainer(); this._container.appendChild(toast); // Animate in requestAnimationFrame(() => { toast.classList.add('show'); }); // Auto remove if (duration > 0 && !options.persistent) { const timeout = setTimeout(() => this.close(id), duration); this._activeToasts.set(id, { element: toast, timeout }); } return id; }, _createToast(id, message, type, duration, options) { const toast = MYPCore.dom.create('div', { id, class: `myp-toast myp-toast-${type}`, role: 'alert', 'aria-live': 'polite' }); const icon = this._getIcon(type); const content = MYPCore.dom.create('div', { class: 'toast-content' }, [ MYPCore.dom.create('span', { class: 'toast-icon' }, [icon]), MYPCore.dom.create('span', { class: 'toast-message' }, [message]) ]); const closeBtn = MYPCore.dom.create('button', { class: 'toast-close', 'aria-label': 'Close notification', onclick: () => this.close(id) }, ['×']); toast.appendChild(content); toast.appendChild(closeBtn); if (duration > 0 && !options.persistent) { const progress = MYPCore.dom.create('div', { class: 'toast-progress' }); const bar = MYPCore.dom.create('div', { class: 'toast-progress-bar', style: { animationDuration: `${duration}ms` } }); progress.appendChild(bar); toast.appendChild(progress); } return toast; }, _getIcon(type) { const icons = { success: '✓', error: '✗', warning: '⚠', info: 'ℹ' }; return icons[type] || icons.info; }, _ensureContainer() { if (!this._container) { this._container = MYPCore.dom.get('#myp-toast-container') || MYPCore.dom.create('div', { id: 'myp-toast-container', class: 'myp-toast-container' }); if (!this._container.parentNode) { document.body.appendChild(this._container); } } }, close(id) { const toast = this._activeToasts.get(id); if (toast) { clearTimeout(toast.timeout); toast.element.classList.remove('show'); setTimeout(() => { toast.element.remove(); this._activeToasts.delete(id); }, 300); } }, closeAll() { this._activeToasts.forEach((_, id) => this.close(id)); } }, // API Request Utilities api: { _cache: new Map(), _pendingRequests: new Map(), async request(url, options = {}) { const config = { ...options, headers: { 'Content-Type': 'application/json', 'X-CSRFToken': MYPCore.csrf.getToken(), ...options.headers } }; // Check cache for GET requests if (options.method === 'GET' || !options.method) { const cacheKey = this._getCacheKey(url, options); const cached = this._cache.get(cacheKey); if (cached && !options.noCache) { const age = Date.now() - cached.timestamp; const maxAge = options.cacheTime || 300000; // 5 minutes default if (age < maxAge) { return cached.data; } } // Prevent duplicate requests if (this._pendingRequests.has(cacheKey)) { return this._pendingRequests.get(cacheKey); } } // Make request const requestPromise = fetch(url, config) .then(async response => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); // Cache successful GET requests if (config.method === 'GET' || !config.method) { const cacheKey = this._getCacheKey(url, options); this._cache.set(cacheKey, { data, timestamp: Date.now() }); this._pendingRequests.delete(cacheKey); } return data; }) .catch(error => { if (config.method === 'GET' || !config.method) { this._pendingRequests.delete(this._getCacheKey(url, options)); } throw error; }); if (config.method === 'GET' || !config.method) { this._pendingRequests.set(this._getCacheKey(url, options), requestPromise); } return requestPromise; }, _getCacheKey(url, options) { return `${url}_${JSON.stringify(options.params || {})}`; }, clearCache(pattern) { if (pattern) { for (const [key] of this._cache) { if (key.includes(pattern)) { this._cache.delete(key); } } } else { this._cache.clear(); } } }, // Time/Date Utilities time: { formatAgo(timestamp) { const date = timestamp instanceof Date ? timestamp : new Date(timestamp); const now = new Date(); const seconds = Math.floor((now - date) / 1000); const intervals = [ { label: 'Jahr', seconds: 31536000, plural: 'Jahre' }, { label: 'Monat', seconds: 2592000, plural: 'Monate' }, { label: 'Woche', seconds: 604800, plural: 'Wochen' }, { label: 'Tag', seconds: 86400, plural: 'Tage' }, { label: 'Stunde', seconds: 3600, plural: 'Stunden' }, { label: 'Minute', seconds: 60, plural: 'Minuten' } ]; for (const interval of intervals) { const count = Math.floor(seconds / interval.seconds); if (count >= 1) { const label = count === 1 ? interval.label : interval.plural; return `vor ${count} ${label}`; } } return 'Gerade eben'; }, formatDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; const parts = []; if (hours > 0) parts.push(`${hours}h`); if (minutes > 0) parts.push(`${minutes}m`); if (secs > 0 || parts.length === 0) parts.push(`${secs}s`); return parts.join(' '); } }, // Performance Utilities perf: { debounce(func, wait) { let timeout; return function debounced(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; }, throttle(func, limit) { let inThrottle; return function throttled(...args) { const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }, memoize(func, resolver) { const cache = new Map(); return function memoized(...args) { const key = resolver ? resolver(...args) : args[0]; if (cache.has(key)) { return cache.get(key); } const result = func.apply(this, args); cache.set(key, result); return result; }; } }, // Event Management events: { _listeners: new Map(), on(element, event, handler, options = {}) { const key = `${element}_${event}`; if (!this._listeners.has(key)) { this._listeners.set(key, new Set()); } this._listeners.get(key).add(handler); element.addEventListener(event, handler, options); // Return cleanup function return () => this.off(element, event, handler); }, off(element, event, handler) { const key = `${element}_${event}`; const handlers = this._listeners.get(key); if (handlers) { handlers.delete(handler); if (handlers.size === 0) { this._listeners.delete(key); } } element.removeEventListener(event, handler); }, once(element, event, handler, options = {}) { const wrappedHandler = (e) => { handler(e); this.off(element, event, wrappedHandler); }; return this.on(element, event, wrappedHandler, options); } }, // Storage Utilities storage: { get(key, defaultValue = null) { try { const item = localStorage.getItem(`myp_${key}`); return item ? JSON.parse(item) : defaultValue; } catch (e) { console.error('Storage get error:', e); return defaultValue; } }, set(key, value) { try { localStorage.setItem(`myp_${key}`, JSON.stringify(value)); return true; } catch (e) { console.error('Storage set error:', e); return false; } }, remove(key) { localStorage.removeItem(`myp_${key}`); }, clear(prefix = 'myp_') { const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith(prefix)) { localStorage.removeItem(key); } }); } } }; // Global shortcuts for backwards compatibility window.MYPCore = MYPCore; // Legacy function mappings window.showToast = (msg, type, duration) => MYPCore.notify.show(msg, type, duration); window.showNotification = window.showToast; window.showFlashMessage = window.showToast; window.showSuccessMessage = (msg) => MYPCore.notify.show(msg, 'success'); window.showErrorMessage = (msg) => MYPCore.notify.show(msg, 'error'); window.showWarningMessage = (msg) => MYPCore.notify.show(msg, 'warning'); window.showInfoMessage = (msg) => MYPCore.notify.show(msg, 'info'); // Auto-initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { console.log('✅ MYP Core Utilities initialized'); }); } else { console.log('✅ MYP Core Utilities initialized'); } })(window);