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}
${links.map((link, index) => {
const iconHtml = link.icon.startsWith('custom-')
? `
]})
`
: `
`;
return `
`;
}).join('')}
`).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 = `
${[...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}
${links.map(link => {
if (link.icon.startsWith('custom-')) {
const iconData = this.customIcons[link.icon.replace('custom-', '')];
if (!iconData) return `- ${link.name}
`;
return `
${link.name} `;
}
return `- ${link.name}
`;
}).join('')}
`).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;
}
}
}