Initial commit: DNS Things - Comprehensive DNS utility website
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
This commit is contained in:
197
assets/js/theme.js
Normal file
197
assets/js/theme.js
Normal file
@@ -0,0 +1,197 @@
|
||||
// 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);
|
||||
}
|
||||
Reference in New Issue
Block a user