// Main JavaScript for TikDown - Professional Version document.addEventListener('DOMContentLoaded', function() { // Initialize Bootstrap components initializeBootstrapComponents(); // Initialize theme initializeTheme(); // Initialize URL validation initializeURLValidation(); // Initialize password strength initializePasswordStrength(); // Lazy load images lazyLoadImages(); // Initialize tooltips initializeTooltips(); }); // Bootstrap Components Initialization function initializeBootstrapComponents() { // Tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.forEach(tooltipTriggerEl => { new bootstrap.Tooltip(tooltipTriggerEl); }); // Popovers const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); popoverTriggerList.forEach(popoverTriggerEl => { new bootstrap.Popover(popoverTriggerEl); }); // Auto-hide alerts setTimeout(() => { const alerts = document.querySelectorAll('.alert:not(.alert-permanent)'); alerts.forEach(alert => { const bsAlert = new bootstrap.Alert(alert); bsAlert.close(); }); }, 5000); } // Theme Initialization function initializeTheme() { const themeToggle = document.getElementById('themeToggle'); const currentTheme = localStorage.getItem('theme') || 'light'; if (currentTheme === 'dark') { document.body.classList.add('dark-theme'); if (themeToggle) themeToggle.checked = true; } if (themeToggle) { themeToggle.addEventListener('change', function() { if (this.checked) { document.body.classList.add('dark-theme'); localStorage.setItem('theme', 'dark'); } else { document.body.classList.remove('dark-theme'); localStorage.setItem('theme', 'light'); } }); } } // URL Validation function initializeURLValidation() { const urlInput = document.getElementById('tiktokUrl'); if (urlInput) { urlInput.addEventListener('input', function() { const url = this.value.trim(); const isValid = validateTikTokUrl(url); if (url && !isValid) { this.classList.add('is-invalid'); this.classList.remove('is-valid'); } else if (url && isValid) { this.classList.remove('is-invalid'); this.classList.add('is-valid'); } else { this.classList.remove('is-invalid', 'is-valid'); } }); } } // Password Strength Indicator function initializePasswordStrength() { const passwordInput = document.getElementById('password'); if (passwordInput) { passwordInput.addEventListener('input', updatePasswordStrength); } } // Lazy Load Images function lazyLoadImages() { const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); observer.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } // Tooltips Initialization function initializeTooltips() { // Initialize any additional tooltips here } // Validate TikTok URL function validateTikTokUrl(url) { if (!url) return false; const patterns = [ /^https?:\/\/(vm|vt|www)\.tiktok\.com\/.+/, /^https?:\/\/(www\.)?tiktok\.com\/@.+\/video\/\d+/, /tiktok\.com\/.+\/video\/\d+/, /^https?:\/\/tiktok\.com\/t\/[a-zA-Z0-9]+/, /^https?:\/\/(vm|vt)\.tiktok\.com\/[a-zA-Z0-9]+/ ]; return patterns.some(pattern => pattern.test(url)); } // Update Password Strength function updatePasswordStrength() { const password = this.value; const strength = calculatePasswordStrength(password); const strengthBar = document.getElementById('password-strength-bar'); const strengthText = document.getElementById('password-strength-text'); if (strengthBar && strengthText) { strengthBar.style.width = strength.percentage + '%'; strengthBar.className = 'progress-bar ' + strength.class; strengthText.textContent = strength.text; strengthText.className = 'text-' + strength.color; } } // Calculate Password Strength function calculatePasswordStrength(password) { let score = 0; const requirements = []; if (password.length >= 8) { score++; requirements.push('length'); } if (/[A-Z]/.test(password)) { score++; requirements.push('uppercase'); } if (/[0-9]/.test(password)) { score++; requirements.push('number'); } if (/[^A-Za-z0-9]/.test(password)) { score++; requirements.push('special'); } const levels = [ { class: 'bg-danger', color: 'danger', text: 'Rất yếu', percentage: 20 }, { class: 'bg-danger', color: 'danger', text: 'Yếu', percentage: 40 }, { class: 'bg-warning', color: 'warning', text: 'Trung bình', percentage: 60 }, { class: 'bg-info', color: 'info', text: 'Mạnh', percentage: 80 }, { class: 'bg-success', color: 'success', text: 'Rất mạnh', percentage: 100 } ]; return levels[Math.min(score, 4)]; } // Copy to Clipboard function copyToClipboard(text, element = null) { navigator.clipboard.writeText(text).then(() => { showNotification('Đã sao chép vào clipboard', 'success'); if (element) { const originalText = element.innerHTML; element.innerHTML = ' Đã sao chép'; setTimeout(() => { element.innerHTML = originalText; }, 2000); } }).catch(err => { showNotification('Không thể sao chép: ' + (err.message || err), 'error'); }); } // Show Notification function showNotification(message, type = 'info') { const alertTypes = { 'success': 'alert-success', 'error': 'alert-danger', 'warning': 'alert-warning', 'info': 'alert-info' }; const alertClass = alertTypes[type] || 'alert-info'; // Create alert element const alert = document.createElement('div'); alert.className = `alert ${alertClass} alert-dismissible fade show position-fixed`; alert.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; alert.innerHTML = `
${message}
`; // Add to body document.body.appendChild(alert); // Auto remove after 5 seconds setTimeout(() => { if (alert.parentNode) { const bsAlert = new bootstrap.Alert(alert); bsAlert.close(); } }, 5000); } // Format Bytes function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } // Format Duration function formatDuration(seconds) { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // Debounce Function function debounce(func, wait, immediate) { let timeout; return function() { const context = this, args = arguments; const later = function() { timeout = null; if (!immediate) func.apply(context, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // Query Parameter Functions function getQueryParam(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } function setQueryParam(name, value) { const url = new URL(window.location); url.searchParams.set(name, value); window.history.pushState({}, '', url); } function removeQueryParam(name) { const url = new URL(window.location); url.searchParams.delete(name); window.history.pushState({}, '', url); } // Check if Element is in Viewport function isInViewport(element) { const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } // Smooth Scroll to Element function smoothScrollTo(element, offset = 20) { const elementPosition = element.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } // Toggle Password Visibility function togglePasswordVisibility(inputId, button) { const input = document.getElementById(inputId); if (input.type === 'password') { input.type = 'text'; button.innerHTML = ''; } else { input.type = 'password'; button.innerHTML = ''; } } // Export functions for global use window.TikDown = { copyToClipboard, showNotification, formatBytes, formatDuration, validateTikTokUrl, getQueryParam, setQueryParam, removeQueryParam, smoothScrollTo, togglePasswordVisibility }; // Add to script.js // Button click animation function addButtonClickAnimation(button) { button.classList.add('button-click'); setTimeout(() => { button.classList.remove('button-click'); }, 200); } // Add click animation to all buttons document.addEventListener('click', function(e) { if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { const button = e.target.tagName === 'BUTTON' ? e.target : e.target.closest('button'); addButtonClickAnimation(button); } }); // Add loading state to form submit buttons document.addEventListener('submit', function(e) { const submitButton = e.target.querySelector('button[type="submit"]'); if (submitButton) { submitButton.classList.add('button-loading'); submitButton.disabled = true; // Remove loading state when form is done (you need to handle this per form) // Example: In your fetch().then().catch() blocks, remove the class and enable the button } });