Features implemented: - Modular JavaScript architecture (theme.js, dns-tools.js, whois.js, punycode.js, ip-tools.js, main.js) - Responsive design with dark/light theme toggle - DNS Lookup and Reverse DNS Lookup tools - Whois Lookup functionality - IDN Punycode Converter with full Unicode support - Comprehensive IP Address Tools (validation, IPv4-to-IPv6 mapping, IPv6 compression/expansion) - Dynamic tab descriptions that change based on active tool - Mobile-responsive horizontal scrollable tabs - Copy-to-clipboard functionality for all results - Clean footer with dynamic year - IPv4-mapped IPv6 address explanation with clear warnings Technical improvements: - Separated concerns with modular JS files - Fixed browser compatibility issues with punycode library - Implemented proper error handling and user feedback - Added comprehensive input validation - Optimized for mobile devices with touch-friendly UI
198 lines
7.7 KiB
JavaScript
198 lines
7.7 KiB
JavaScript
// DNS Things - Theme and UI Utilities
|
|
|
|
// Theme toggle functionality
|
|
function initTheme() {
|
|
const themeToggleBtn = document.getElementById('theme-toggle');
|
|
const lightIcon = document.getElementById('theme-toggle-light-icon');
|
|
const darkIcon = document.getElementById('theme-toggle-dark-icon');
|
|
|
|
if (!themeToggleBtn || !lightIcon || !darkIcon) return;
|
|
|
|
// Check for saved theme preference or default to system preference
|
|
const savedTheme = localStorage.getItem('theme');
|
|
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
|
let currentTheme = savedTheme || (systemPrefersDark ? 'dark' : 'light');
|
|
|
|
// Apply initial theme
|
|
if (currentTheme === 'dark') {
|
|
document.documentElement.classList.add('dark');
|
|
lightIcon.classList.add('hidden');
|
|
darkIcon.classList.remove('hidden');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
lightIcon.classList.remove('hidden');
|
|
darkIcon.classList.add('hidden');
|
|
}
|
|
|
|
// Toggle theme function
|
|
function toggleTheme() {
|
|
if (document.documentElement.classList.contains('dark')) {
|
|
document.documentElement.classList.remove('dark');
|
|
lightIcon.classList.remove('hidden');
|
|
darkIcon.classList.add('hidden');
|
|
localStorage.setItem('theme', 'light');
|
|
} else {
|
|
document.documentElement.classList.add('dark');
|
|
lightIcon.classList.add('hidden');
|
|
darkIcon.classList.remove('hidden');
|
|
localStorage.setItem('theme', 'dark');
|
|
}
|
|
}
|
|
|
|
themeToggleBtn.addEventListener('click', toggleTheme);
|
|
}
|
|
|
|
// Tab descriptions for dynamic updates
|
|
const tabDescriptions = {
|
|
'dns-lookup-tab': 'Perform DNS lookups to resolve domain names and check DNS records',
|
|
'reverse-dns-tab': 'Find domain names associated with IP addresses using reverse DNS',
|
|
'whois-tab': 'Get detailed registration and ownership information for domains and IPs',
|
|
'punycode-tab': 'Convert international domain names between Unicode and Punycode formats',
|
|
'ip-tools-tab': 'Comprehensive IP address utilities including validation, conversion, and analysis'
|
|
};
|
|
|
|
// Tab switching functionality
|
|
function initTabs() {
|
|
const tabButtons = document.querySelectorAll('.tab-btn');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
const descriptionElement = document.querySelector('p.text-lg.text-gray-500.mt-2');
|
|
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const targetTab = button.getAttribute('data-tab');
|
|
|
|
// Remove active class from all buttons and contents
|
|
tabButtons.forEach(btn => {
|
|
btn.classList.remove('active', 'text-indigo-600', 'dark:text-indigo-400', 'bg-white', 'dark:bg-gray-800', 'shadow');
|
|
btn.classList.add('text-gray-600', 'dark:text-gray-300', 'hover:bg-white', 'hover:dark:bg-gray-800/50');
|
|
});
|
|
tabContents.forEach(content => {
|
|
content.classList.remove('active');
|
|
content.classList.add('hidden');
|
|
});
|
|
|
|
// Add active class to clicked button and show corresponding content
|
|
button.classList.remove('text-gray-600', 'dark:text-gray-300', 'hover:bg-white', 'hover:dark:bg-gray-800/50');
|
|
button.classList.add('active', 'text-indigo-600', 'dark:text-indigo-400', 'bg-white', 'dark:bg-gray-800', 'shadow');
|
|
|
|
const targetContent = document.getElementById(targetTab);
|
|
if (targetContent) {
|
|
targetContent.classList.remove('hidden');
|
|
targetContent.classList.add('active');
|
|
}
|
|
|
|
// Update description text
|
|
if (descriptionElement && tabDescriptions[targetTab]) {
|
|
descriptionElement.textContent = tabDescriptions[targetTab];
|
|
}
|
|
});
|
|
});
|
|
|
|
// Set initial description based on active tab
|
|
const activeTab = document.querySelector('.tab-btn.active');
|
|
if (activeTab && descriptionElement) {
|
|
const activeTabId = activeTab.getAttribute('data-tab');
|
|
if (tabDescriptions[activeTabId]) {
|
|
descriptionElement.textContent = tabDescriptions[activeTabId];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy to clipboard functionality
|
|
function copyToClipboard(text, button) {
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
showCopyFeedback(button);
|
|
}).catch(err => {
|
|
console.error('Failed to copy: ', err);
|
|
fallbackCopyTextToClipboard(text, button);
|
|
});
|
|
} else {
|
|
fallbackCopyTextToClipboard(text, button);
|
|
}
|
|
}
|
|
|
|
// Make copyToClipboard globally available
|
|
window.copyToClipboard = copyToClipboard;
|
|
|
|
function fallbackCopyTextToClipboard(text, button) {
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = text;
|
|
textArea.style.position = 'fixed';
|
|
textArea.style.left = '-999999px';
|
|
textArea.style.top = '-999999px';
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
|
|
try {
|
|
document.execCommand('copy');
|
|
showCopyFeedback(button);
|
|
} catch (err) {
|
|
console.error('Fallback: Oops, unable to copy', err);
|
|
}
|
|
|
|
document.body.removeChild(textArea);
|
|
}
|
|
|
|
function showCopyFeedback(button) {
|
|
const originalHTML = button.innerHTML;
|
|
button.innerHTML = `
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
`;
|
|
button.classList.add('bg-green-500');
|
|
|
|
setTimeout(() => {
|
|
button.innerHTML = originalHTML;
|
|
button.classList.remove('bg-green-500');
|
|
}, 1000);
|
|
}
|
|
|
|
// Initialize copy buttons for the original HTML structure
|
|
function initCopyButtons() {
|
|
document.querySelectorAll('.copy-btn').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const targetId = this.getAttribute('data-copy-target');
|
|
const targetElement = document.getElementById(targetId);
|
|
const copyIcon = this.querySelector('.copy-icon');
|
|
const copyFeedback = this.querySelector('.copy-feedback');
|
|
|
|
if (targetElement) {
|
|
const textToCopy = targetElement.textContent.trim();
|
|
|
|
// Skip if text is loading or not available
|
|
if (!textToCopy || textToCopy === 'loading...' || textToCopy === 'Not available') {
|
|
return;
|
|
}
|
|
|
|
// Try to use the modern clipboard API
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(textToCopy).then(() => {
|
|
showCopyFeedback(copyIcon, copyFeedback);
|
|
}).catch(err => {
|
|
console.error('Failed to copy: ', err);
|
|
fallbackCopyTextToClipboard(textToCopy, copyIcon, copyFeedback);
|
|
});
|
|
} else {
|
|
// Fallback for older browsers
|
|
fallbackCopyTextToClipboard(textToCopy, copyIcon, copyFeedback);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function showCopyFeedback(copyIcon, copyFeedback) {
|
|
if (copyIcon) copyIcon.classList.add('hidden');
|
|
if (copyFeedback) copyFeedback.classList.remove('hidden');
|
|
|
|
setTimeout(() => {
|
|
if (copyIcon) copyIcon.classList.remove('hidden');
|
|
if (copyFeedback) copyFeedback.classList.add('hidden');
|
|
}, 2000);
|
|
}
|