// ==UserScript== // @name Hacker News URL Highlighter with Enhanced Panel and Scores // @namespace https://news.ycombinator.com/ // @version 1.5 // @description Highlights URLs in comments on Hacker News, lists them in a toggleable panel with sorting, color coding based on frequency or upvotes, and displays scores next to each URL. Provides export functionality and hover previews only in comments. // @author MorganGeek // @match https://news.ycombinator.com/item?id=* // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @connect * // @run-at document-end // ==/UserScript== (function () { 'use strict'; // Configuration des couleurs en fonction des fréquences ou des upvotes const COLORS = { low: '#d1e7dd', // Vert pâle medium: '#fff3cd', // Jaune pâle high: '#f8d7da' // Rouge pâle }; // Créer un bouton pour basculer l'affichage du panneau const toggleButton = document.createElement('button'); toggleButton.textContent = 'Afficher les URLs'; toggleButton.style.position = 'fixed'; toggleButton.style.bottom = '10px'; toggleButton.style.right = '10px'; toggleButton.style.zIndex = 1000; toggleButton.style.padding = '10px 15px'; toggleButton.style.backgroundColor = '#ff6600'; toggleButton.style.color = '#fff'; toggleButton.style.border = 'none'; toggleButton.style.borderRadius = '5px'; toggleButton.style.cursor = 'pointer'; document.body.appendChild(toggleButton); // Créer un panneau flottant pour afficher les URLs (caché par défaut) const panel = document.createElement('div'); panel.id = 'url-panel'; panel.style.position = 'fixed'; panel.style.top = '10px'; panel.style.right = '10px'; panel.style.width = '350px'; panel.style.backgroundColor = '#fff'; panel.style.border = '1px solid #ccc'; panel.style.borderRadius = '5px'; panel.style.padding = '10px'; panel.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.2)'; panel.style.maxHeight = '80vh'; panel.style.overflowY = 'auto'; panel.style.zIndex = 1000; panel.style.display = 'none'; // Caché par défaut panel.innerHTML = `
${description}
`; tooltip.style.display = 'block'; }, onerror: () => { tooltip.innerHTML = 'Preview unavailable'; tooltip.style.display = 'block'; } }); const rect = link.getBoundingClientRect(); tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`; tooltip.style.left = `${rect.left + window.scrollX}px`; }); link.addEventListener('mouseout', () => { tooltip.style.display = 'none'; }); }); }); // Fonction pour mettre à jour l'affichage du panneau selon les filtres function updatePanel() { const sortBy = panel.querySelector('#sort-select').value; const limit = panel.querySelector('#limit-select').value; // Convertir l'objet en tableau pour le tri let urlsArray = Object.values(urlData); // Trier en fonction du critère sélectionné if (sortBy === 'frequency') { urlsArray.sort((a, b) => b.count - a.count); } else if (sortBy === 'name') { urlsArray.sort((a, b) => a.url.localeCompare(b.url)); } else if (sortBy === 'upvotes') { urlsArray.sort((a, b) => b.upvotes - a.upvotes); } // Appliquer la limite si nécessaire if (limit !== 'all') { const limitNumber = parseInt(limit); urlsArray = urlsArray.slice(0, limitNumber); } const urlList = panel.querySelector('#url-list'); urlList.innerHTML = ''; // Vider la liste actuelle // Déterminer les fréquences pour la coloration const counts = urlsArray.map(item => item.count); const maxCount = Math.max(...counts); const minCount = Math.min(...counts); urlsArray.forEach(item => { const listItem = document.createElement('li'); listItem.style.marginBottom = '5px'; listItem.style.wordWrap = 'break-word'; listItem.style.cursor = 'pointer'; listItem.dataset.url = item.url; // Définir la couleur en fonction de la fréquence let color; const ratio = (item.count - minCount) / (maxCount - minCount + 1); if (ratio > 0.66) { color = COLORS.high; } else if (ratio > 0.33) { color = COLORS.medium; } else { color = COLORS.low; } listItem.style.backgroundColor = color; // Créer le contenu avec URL et score listItem.innerHTML = ` ${item.url} ${item.count}${item.upvotes > 0 ? ` / ${item.upvotes}` : ''} `; // Ajouter l'événement de clic pour copier l'URL listItem.addEventListener('click', () => { GM_setClipboard(item.url, 'text'); alert('URL copiée dans le presse-papiers!'); }); urlList.appendChild(listItem); }); } // Initialiser le panneau updatePanel(); // Écouter les changements dans les sélecteurs de tri et de limite panel.querySelector('#sort-select').addEventListener('change', updatePanel); panel.querySelector('#limit-select').addEventListener('change', updatePanel); // Gérer le bouton de bascule pour afficher/masquer le panneau toggleButton.addEventListener('click', () => { if (panel.style.display === 'none') { panel.style.display = 'block'; toggleButton.textContent = 'Masquer les URLs'; } else { panel.style.display = 'none'; toggleButton.textContent = 'Afficher les URLs'; } }); // Gérer le bouton "Copier les URLs" panel.querySelector('#copy-urls').addEventListener('click', () => { const urlArray = Object.keys(urlData); GM_setClipboard(urlArray.join('\n'), 'text'); alert('URLs copiées dans le presse-papiers!'); }); // Optionnel : Collecter les upvotes des commentaires pour chaque URL // Note : Hacker News n'expose pas directement les upvotes des commentaires via le DOM. // Si vous avez une méthode pour obtenir les upvotes, vous pouvez l'implémenter ici. // Par exemple, si les points des commentaires sont visibles, vous pouvez les extraire et les attribuer aux URLs. // Exemple de récupération des points des commentaires (si disponibles) const commentRows = document.querySelectorAll('tr.comtr'); commentRows.forEach(row => { const commentLink = row.querySelector('a[href^="item?id="]'); if (commentLink) { const pointsSpan = row.querySelector('.score'); const points = pointsSpan ? parseInt(pointsSpan.textContent) : 0; const comment = row.querySelector('.commtext'); if (comment) { const links = comment.querySelectorAll('a[href]'); links.forEach(link => { const url = link.href; if (urlData[url]) { urlData[url].upvotes += points; } }); } } }); // Mettre à jour le panneau après avoir collecté les upvotes updatePanel(); })();