Spaces:
Running
Running
| /* | |
| ELYSIA MARKDOWN STUDIO v1.0 - Utility Functions | |
| Toast, modals, storage, helpers | |
| */ | |
| const Utils = { | |
| // Toast Notifications | |
| toast: { | |
| show(message, type = "info", duration = 3000) { | |
| const container = document.getElementById("toast-container"); | |
| const toast = document.createElement("div"); | |
| toast.className = `toast ${type}`; | |
| const icons = { | |
| success: "✅", | |
| error: "❌", | |
| warning: "⚠️", | |
| info: "ℹ️", | |
| loading: "⏳" | |
| }; | |
| toast.innerHTML = ` | |
| <span class="toast-icon">${icons[type]}</span> | |
| <span class="toast-message">${message}</span> | |
| <button class="toast-close">×</button> | |
| `; | |
| container.appendChild(toast); | |
| const close = () => { | |
| toast.style.animation = "slideOut 0.3s ease"; | |
| setTimeout(() => toast.remove(), 300); | |
| }; | |
| toast.querySelector(".toast-close").onclick = close; | |
| if (duration > 0) setTimeout(close, duration); | |
| return toast; | |
| }, | |
| success: (msg, duration) => Utils.toast.show(msg, "success", duration), | |
| error: (msg, duration) => Utils.toast.show(msg, "error", duration), | |
| warning: (msg, duration) => Utils.toast.show(msg, "warning", duration), | |
| info: (msg, duration) => Utils.toast.show(msg, "info", duration) | |
| }, | |
| // Modal Management | |
| modal: { | |
| open(modalId) { | |
| const modal = document.getElementById(modalId); | |
| if (modal) { | |
| modal.classList.add("active"); | |
| document.body.style.overflow = "hidden"; | |
| } | |
| }, | |
| close(modalId) { | |
| const modal = document.getElementById(modalId); | |
| if (modal) { | |
| modal.classList.remove("active"); | |
| document.body.style.overflow = ""; | |
| } | |
| }, | |
| init() { | |
| document.querySelectorAll(".modal").forEach(modal => { | |
| modal.addEventListener("click", e => { | |
| if (e.target === modal) { | |
| Utils.modal.close(modal.id); | |
| } | |
| }); | |
| }); | |
| document.querySelectorAll(".modal-close, [data-modal]").forEach(btn => { | |
| btn.addEventListener("click", () => { | |
| const modalId = btn.getAttribute("data-modal"); | |
| if (modalId) Utils.modal.close(modalId); | |
| }); | |
| }); | |
| } | |
| }, | |
| // Local Storage Wrapper (with encryption for sensitive data) | |
| storage: { | |
| // Simple XOR encryption (basic obfuscation - better than plaintext) | |
| _encrypt(text) { | |
| const key = "ElysiaStudio2025"; // Simple key for obfuscation | |
| let encrypted = ""; | |
| for (let i = 0; i < text.length; i++) { | |
| encrypted += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); | |
| } | |
| return btoa(encrypted); // Base64 encode | |
| }, | |
| _decrypt(encrypted) { | |
| try { | |
| const decoded = atob(encrypted); | |
| const key = "ElysiaStudio2025"; | |
| let decrypted = ""; | |
| for (let i = 0; i < decoded.length; i++) { | |
| decrypted += String.fromCharCode(decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)); | |
| } | |
| return decrypted; | |
| } catch { | |
| return null; | |
| } | |
| }, | |
| get(key, defaultValue = null) { | |
| try { | |
| const value = localStorage.getItem(key); | |
| if (!value) return defaultValue; | |
| // Decrypt API key if it's stored | |
| if (key === "apiKey") { | |
| const decrypted = this._decrypt(value); | |
| return decrypted || defaultValue; | |
| } | |
| return JSON.parse(value); | |
| } catch { | |
| return defaultValue; | |
| } | |
| }, | |
| set(key, value) { | |
| try { | |
| // Encrypt API key before storing | |
| if (key === "apiKey" && value) { | |
| localStorage.setItem(key, this._encrypt(value)); | |
| return true; | |
| } | |
| localStorage.setItem(key, JSON.stringify(value)); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| }, | |
| remove(key) { | |
| localStorage.removeItem(key); | |
| }, | |
| clear() { | |
| localStorage.clear(); | |
| } | |
| }, | |
| // Format Date/Time | |
| formatDateTime(date) { | |
| const d = new Date(date); | |
| return d.toLocaleString(); | |
| }, | |
| // Format Date (short) | |
| formatDate(date) { | |
| const d = new Date(date); | |
| const now = new Date(); | |
| const diff = now - d; | |
| if (diff < 60000) return "Just now"; | |
| if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`; | |
| if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`; | |
| if (diff < 604800000) return `${Math.floor(diff / 86400000)}d ago`; | |
| return d.toLocaleDateString(); | |
| }, | |
| // Count Words | |
| countWords(text) { | |
| return text.trim() ? text.trim().split(/\s+/).length : 0; | |
| }, | |
| // Count Characters | |
| countChars(text) { | |
| return text.length; | |
| }, | |
| // Count Lines | |
| countLines(text) { | |
| return text.split("\n").length; | |
| }, | |
| // Calculate reading time (average 200 words per minute) | |
| readingTime(wordCount) { | |
| const minutes = Math.ceil(wordCount / 200); | |
| if (minutes < 1) return "< 1 min read"; | |
| if (minutes === 1) return "1 min read"; | |
| return `${minutes} min read`; | |
| }, | |
| // Download File | |
| downloadFile(content, filename, mimeType = "text/plain") { | |
| const blob = new Blob([content], { type: mimeType }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = filename; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| }, | |
| // Copy to Clipboard | |
| async copyToClipboard(text) { | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| Utils.toast.success("Copied to clipboard!"); | |
| return true; | |
| } catch (err) { | |
| Utils.toast.error("Failed to copy"); | |
| return false; | |
| } | |
| }, | |
| // Debounce Function | |
| debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| }, | |
| // Generate UUID | |
| uuid() { | |
| return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => { | |
| const r = (Math.random() * 16) | 0; | |
| const v = c === "x" ? r : (r & 0x3) | 0x8; | |
| return v.toString(16); | |
| }); | |
| }, | |
| // Sanitize Filename | |
| sanitizeFilename(name) { | |
| return name.replace(/[^a-z0-9_\-\.]/gi, "_"); | |
| }, | |
| // Truncate Text | |
| truncate(text, maxLength) { | |
| return text.length > maxLength ? text.substring(0, maxLength) + "..." : text; | |
| }, | |
| // Escape HTML | |
| escapeHtml(text) { | |
| const div = document.createElement("div"); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| }; | |
| // Initialize modals on load | |
| document.addEventListener("DOMContentLoaded", () => { | |
| Utils.modal.init(); | |
| }); | |
| export default Utils; | |