1 line
7.5 KiB
JavaScript
1 line
7.5 KiB
JavaScript
(function(window){'use strict';const MYP = window.MYP ||{};const config ={apiTimeout:30000,cacheExpiry:5 * 60 * 1000,notificationDuration:5000,debounceDelay:300,throttleDelay:100};const cache = new Map();const requestCache = new Map();const csrf ={token:null,get(){if(!this.token){const meta = document.querySelector('meta[name="csrf-token"]');this.token = meta ? meta.getAttribute('content'):'';}return this.token;},headers(){return{'X-CSRFToken':this.get(),'Content-Type':'application/json'};}};const dom ={selectors:new Map(),get(selector,parent = document){const key = `${parent === document ? 'doc':'el'}_${selector}`;if(!this.selectors.has(key)){this.selectors.set(key,parent.querySelector(selector));}return this.selectors.get(key);},getAll(selector,parent = document){return parent.querySelectorAll(selector);},clearCache(){this.selectors.clear();},create(tag,attrs ={},text = ''){const el = document.createElement(tag);Object.entries(attrs).forEach(([key,val])=>{if(key === 'class'){el.className = val;}else if(key === 'dataset'){Object.entries(val).forEach(([k,v])=>{el.dataset[k] = v;});}else{el.setAttribute(key,val);}});if(text)el.textContent = text;return el;}};const api ={pending:new Map(),async request(url,options ={}){const key = `${options.method || 'GET'}_${url}`;if(this.pending.has(key)){return this.pending.get(key);}if(!options.method || options.method === 'GET'){const cached = requestCache.get(key);if(cached && Date.now()- cached.timestamp < config.cacheExpiry){return Promise.resolve(cached.data);}}const requestOptions ={...options,headers:{...csrf.headers(),...options.headers}};const promise = fetch(url,requestOptions).then(response =>{if(!response.ok){throw new Error(`HTTP error! status:${response.status}`);}return response.json();}).then(data =>{if(!options.method || options.method === 'GET'){requestCache.set(key,{data,timestamp:Date.now()});}return data;}).finally(()=>{this.pending.delete(key);});this.pending.set(key,promise);return promise;},get(url,options ={}){return this.request(url,{...options,method:'GET'});},post(url,data,options ={}){return this.request(url,{...options,method:'POST',body:JSON.stringify(data)});},put(url,data,options ={}){return this.request(url,{...options,method:'PUT',body:JSON.stringify(data)});},delete(url,options ={}){return this.request(url,{...options,method:'DELETE'});}};const notifications ={container:null,queue:[],init(){if(this.container)return;this.container = dom.create('div',{id:'myp-notifications',class:'fixed top-4 right-4 z-50 space-y-2'});document.body.appendChild(this.container);},show(message,type = 'info',duration = config.notificationDuration){this.init();const notification = dom.create('div',{class:`notification notification-${type}glass-navbar p-4 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300`,role:'alert'});const content = dom.create('div',{class:'flex items-center space-x-3'});const icon = dom.create('i',{class:`fas ${this.getIcon(type)}text-lg`});const text = dom.create('span',{class:'flex-1'},message);const close = dom.create('button',{class:'ml-4 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200','aria-label':'Close'});close.innerHTML = '×';close.onclick =()=> this.remove(notification);content.appendChild(icon);content.appendChild(text);content.appendChild(close);notification.appendChild(content);this.container.appendChild(notification);requestAnimationFrame(()=>{notification.classList.remove('translate-x-full');});if(duration > 0){setTimeout(()=> this.remove(notification),duration);}return notification;},remove(notification){notification.classList.add('translate-x-full');setTimeout(()=>{notification.remove();},300);},getIcon(type){const icons ={success:'fa-check-circle text-green-500',error:'fa-exclamation-circle text-red-500',warning:'fa-exclamation-triangle text-yellow-500',info:'fa-info-circle text-blue-500'};return icons[type] || icons.info;},success(message,duration){return this.show(message,'success',duration);},error(message,duration){return this.show(message,'error',duration);},warning(message,duration){return this.show(message,'warning',duration);},info(message,duration){return this.show(message,'info',duration);}};const performance ={debounce(func,delay = config.debounceDelay){let timeoutId;return function(...args){clearTimeout(timeoutId);timeoutId = setTimeout(()=> func.apply(this,args),delay);};},throttle(func,delay = config.throttleDelay){let lastCall = 0;return function(...args){const now = Date.now();if(now - lastCall >= delay){lastCall = now;return func.apply(this,args);}};},memoize(func){const cache = new Map();return function(...args){const key = JSON.stringify(args);if(cache.has(key)){return cache.get(key);}const result = func.apply(this,args);cache.set(key,result);return result;};},lazy(selector,callback){const observer = new IntersectionObserver((entries)=>{entries.forEach(entry =>{if(entry.isIntersecting){callback(entry.target);observer.unobserve(entry.target);}});});dom.getAll(selector).forEach(el => observer.observe(el));return observer;}};const storage ={set(key,value,expiry = null){const data ={value,timestamp:Date.now(),expiry};try{localStorage.setItem(`myp_${key}`,JSON.stringify(data));return true;}catch(e){console.error('Storage error:',e);return false;}},get(key){try{const item = localStorage.getItem(`myp_${key}`);if(!item)return null;const data = JSON.parse(item);if(data.expiry && Date.now()- data.timestamp > data.expiry){this.remove(key);return null;}return data.value;}catch(e){console.error('Storage error:',e);return null;}},remove(key){localStorage.removeItem(`myp_${key}`);},clear(){Object.keys(localStorage).filter(key => key.startsWith('myp_')).forEach(key => localStorage.removeItem(key));}};const 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);},off(element,event,handler){const key = `${element}_${event}`;if(this.listeners.has(key)){this.listeners.get(key).delete(handler);if(this.listeners.get(key).size === 0){this.listeners.delete(key);}}element.removeEventListener(event,handler);},once(element,event,handler,options ={}){const onceHandler =(e)=>{handler(e);this.off(element,event,onceHandler);};this.on(element,event,onceHandler,options);},emit(name,detail ={}){const event = new CustomEvent(name,{detail,bubbles:true,cancelable:true});document.dispatchEvent(event);},cleanup(){this.listeners.forEach((handlers,key)=>{const [element,event] = key.split('_');handlers.forEach(handler =>{element.removeEventListener(event,handler);});});this.listeners.clear();}};const forms ={serialize(form){const data = new FormData(form);const obj ={};for(const [key,value] of data.entries()){if(obj[key]){if(!Array.isArray(obj[key])){obj[key] = [obj[key]];}obj[key].push(value);}else{obj[key] = value;}}return obj;},validate(form){const inputs = form.querySelectorAll('[required]');let valid = true;inputs.forEach(input =>{if(!input.value.trim()){input.classList.add('border-red-500');valid = false;}else{input.classList.remove('border-red-500');}});return valid;},reset(form){form.reset();form.querySelectorAll('.border-red-500').forEach(el =>{el.classList.remove('border-red-500');});}};const init =()=>{notifications.init();window.addEventListener('beforeunload',()=>{events.cleanup();dom.clearCache();});};MYP.utils ={csrf,dom,api,notifications,performance,storage,events,forms,init,config};if(document.readyState === 'loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}window.MYP = MYP;})(window); |