export class LinksManager { constructor() { this.iconPacks = { 'Font Awesome Icons': { prefix: { brands: 'fab fa-', solid: 'fas fa-' }, icons: { brands: [ 'github', 'gitlab', 'reddit', 'twitter', 'youtube', 'spotify', 'discord', 'twitch', 'steam', 'linkedin', 'instagram', 'facebook', 'amazon', 'apple', 'google', 'pinterest', 'microsoft', 'windows', 'linux', 'chrome', 'firefox', 'opera', 'html5', 'css3', 'js', 'npm', 'node', 'react', 'angular', 'wordpress', 'sass', 'gulp', 'python', 'java', 'php', 'stack-overflow', 'docker', 'android' ], solid: [ 'home', 'link', 'folder', 'file', 'book', 'code', 'film', 'envelope', 'star', 'heart', 'user', 'cog', 'search', 'lock', 'check', 'times', 'plus', 'minus', 'edit', 'trash', 'download', 'upload', 'print', 'camera', 'phone', 'desktop', 'wifi', 'moon', 'sun', 'cloud', 'bell', 'bookmark', 'map', 'globe', 'eye', 'pen', 'terminal', 'folder-open', 'image' ] } } }; this.customIcons = JSON.parse(localStorage.getItem('customIcons')) || {}; this.links = JSON.parse(localStorage.getItem('customLinks')) || this.getDefaultLinks(); this.categoriesEditor = document.getElementById('categories-editor'); this.init(); } getDefaultLinks() { return { 'Development': [ { name: 'GitHub', url: 'https://github.com', icon: 'fab fa-github' }, { name: 'Stack Overflow', url: 'https://stackoverflow.com', icon: 'fab fa-stack-overflow' }, { name: 'MDN', url: 'https://developer.mozilla.org', icon: 'fas fa-book' } ], 'Social': [ { name: 'Reddit', url: 'https://www.reddit.com', icon: 'fab fa-reddit' }, { name: 'Twitter', url: 'https://www.twitter.com', icon: 'fab fa-twitter' }, { name: 'LinkedIn', url: 'https://www.linkedin.com', icon: 'fab fa-linkedin' } ], 'Entertainment': [ { name: 'YouTube', url: 'https://www.youtube.com', icon: 'fab fa-youtube' }, { name: 'Netflix', url: 'https://www.netflix.com', icon: 'fas fa-film' }, { name: 'Spotify', url: 'https://www.spotify.com', icon: 'fab fa-spotify' } ] }; } init() { this.renderLinks(); this.attachEventListeners(); } renderLinks() { // Render links in settings this.renderLinksEditor(); // Render links in main container this.renderLinksContainer(); } renderLinksEditor() { this.categoriesEditor.innerHTML = Object.entries(this.links) .map(([category, links]) => `
${category}
`).join(''); // Add event listeners this.categoriesEditor.addEventListener('click', (e) => { const target = e.target.closest('[data-category]'); if (!target) return; if (target.classList.contains('remove-category')) { this.removeCategory(target.dataset.category); } else if (target.classList.contains('remove-link')) { this.removeLink(target.dataset.category, parseInt(target.dataset.index)); } else if (target.classList.contains('icon-select')) { this.showIconPicker(target.dataset.category, parseInt(target.dataset.index)); } }); this.categoriesEditor.addEventListener('change', (e) => { const target = e.target; if (target.matches('input[data-field]')) { this.updateLink( target.dataset.category, parseInt(target.dataset.index), target.dataset.field, target.value ); } }); } showIconPicker(category, linkIndex) { const popup = document.createElement('div'); popup.className = 'icon-picker-popup'; const content = document.createElement('div'); content.className = 'icon-picker-content'; const pack = this.iconPacks['Font Awesome Icons']; content.innerHTML = `

Icon Picker

${[...pack.icons.brands.map(icon => ({ class: pack.prefix.brands + icon, name: 'brands-' + icon })), ...pack.icons.solid.map(icon => ({ class: pack.prefix.solid + icon, name: 'solid-' + icon }))].map(icon => `
`).join('')}
`; // Event listeners for icons content.querySelectorAll('.icon-item').forEach(item => { item.addEventListener('click', () => { if (item.dataset.icon) { this.setIcon(category, linkIndex, item.dataset.icon); } else if (item.dataset.name && item.dataset.url) { this.setCustomIcon(category, linkIndex, item.dataset.name, item.dataset.url); } }); }); // Add delete icon handler content.querySelectorAll('.delete-icon').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const name = btn.dataset.name; if (confirm(`Delete custom icon "${name}"?`)) { delete this.customIcons[name]; localStorage.setItem('customIcons', JSON.stringify(this.customIcons)); this.showIconPicker(category, linkIndex); } }); }); // Update custom icon click handler content.querySelectorAll('.custom-icon-item').forEach(item => { item.addEventListener('click', () => { const name = item.dataset.name; if (name && this.customIcons[name]) { this.setCustomIcon(category, linkIndex, name, this.customIcons[name]); } }); }); // Upload button handler const uploadBtn = content.querySelector('.upload-icon-btn'); const fileInput = content.querySelector('#customIconUpload'); uploadBtn?.addEventListener('click', () => fileInput.click()); // File input handler fileInput?.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { const name = file.name.replace(/\.[^/.]+$/, ""); this.customIcons[name] = e.target.result; localStorage.setItem('customIcons', JSON.stringify(this.customIcons)); this.showIconPicker(category, linkIndex); }; reader.readAsDataURL(file); } }); // Tab switching content.querySelectorAll('.icon-picker-tabs button').forEach(button => { button.addEventListener('click', () => { content.querySelectorAll('.icon-picker-tabs button').forEach(b => b.classList.remove('active')); button.classList.add('active'); const tab = button.dataset.tab; content.querySelectorAll('.icon-section').forEach(section => { section.style.display = section.classList.contains(`${tab}-section`) ? 'grid' : 'none'; }); }); }); popup.appendChild(content); document.body.appendChild(popup); popup.addEventListener('click', (e) => { if (e.target === popup) popup.remove(); }); } renderIconPackContent(packName) { const pack = this.iconPacks[packName]; return Object.entries(pack.icons).map(([type, icons]) => `
${icons.map(icon => `` ).join('')}
`).join(''); } setCustomIcon(category, index, name, url) { if (this.customIcons[name]) { this.updateLink(category, index, 'icon', `custom-${name}`); // Remove iconUrl since we're storing it in customIcons const link = this.links[category][index]; delete link.iconUrl; this.save(); document.querySelector('.icon-picker-popup').remove(); } } setIcon(category, index, icon) { this.updateLink(category, index, 'icon', icon); document.querySelector('.icon-picker-popup').remove(); } renderLinksContainer() { const container = document.querySelector('.links-container'); container.innerHTML = Object.entries(this.links) .map(([category, links]) => `

${category}

`).join(''); } attachEventListeners() { document.getElementById('addCategory').addEventListener('click', () => { const name = prompt('Enter category name:'); if (name) this.addCategory(name); }); document.getElementById('addLink').addEventListener('click', () => { const category = prompt('Enter category:'); if (category && this.links[category]) { this.addLink(category, { name: 'New Link', url: 'https://', icon: 'fas fa-link' }); } }); document.getElementById('exportLinks').addEventListener('click', () => this.export()); document.getElementById('importLinksBtn').addEventListener('click', () => { document.getElementById('importLinks').click(); }); document.getElementById('importLinks').addEventListener('change', (e) => { if (e.target.files[0]) this.import(e.target.files[0]); }); } addCategory(name) { if (!this.links[name]) { this.links[name] = []; this.save(); this.renderLinks(); } } addLink(category, link) { this.links[category].push(link); this.save(); this.renderLinks(); } updateLink(category, index, field, value) { if (this.links[category] && this.links[category][index]) { this.links[category][index][field] = value; this.save(); this.renderLinks(); } } removeLink(category, index) { if (this.links[category]) { this.links[category].splice(index, 1); this.save(); this.renderLinks(); } } removeCategory(category) { if (confirm(`Are you sure you want to delete the category "${category}" and all its links?`)) { delete this.links[category]; this.save(); this.renderLinks(); } } save() { localStorage.setItem('customLinks', JSON.stringify(this.links)); } export() { const blob = new Blob([JSON.stringify(this.links, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'links-config.json'; a.click(); URL.revokeObjectURL(url); } async import(file) { try { const text = await file.text(); this.links = JSON.parse(text); this.save(); this.renderLinks(); return true; } catch (error) { console.error('Failed to import links:', error); return false; } } }