From 1525cc4a3210c7d2c26ffc3075ad63d828183ef8 Mon Sep 17 00:00:00 2001 From: SansGuidon Date: Tue, 31 Dec 2024 14:32:58 +0000 Subject: [PATCH] feat(hackernews): update user scripts add toggleable URL panel with color coding and sorting options --- .../hn_url_highlighter_with_preview.js | 205 ++++++++++++++++-- 1 file changed, 182 insertions(+), 23 deletions(-) diff --git a/tampermonkey/hn_url_highlighter_with_preview.js b/tampermonkey/hn_url_highlighter_with_preview.js index f630ee7..edda250 100644 --- a/tampermonkey/hn_url_highlighter_with_preview.js +++ b/tampermonkey/hn_url_highlighter_with_preview.js @@ -1,8 +1,8 @@ // ==UserScript== -// @name Hacker News URL Highlighter with Preview +// @name Hacker News URL Highlighter with Enhanced Panel // @namespace https://news.ycombinator.com/ -// @version 1.3 -// @description Highlights URLs in comments on Hacker News, lists them in a panel, and provides export functionality. Hover previews only in comments. +// @version 1.4 +// @description Highlights URLs in comments on Hacker News, lists them in a toggleable panel with sorting and coloring based on frequency or upvotes, and provides export functionality. Hover previews only in comments. // @author MorganGeek // @match https://news.ycombinator.com/item?id=* // @grant GM_xmlhttpRequest @@ -14,12 +14,35 @@ (function () { 'use strict'; - // Create a floating panel for displaying URLs + // 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 = '300px'; panel.style.backgroundColor = '#fff'; panel.style.border = '1px solid #ccc'; panel.style.borderRadius = '5px'; @@ -28,46 +51,74 @@ panel.style.maxHeight = '80vh'; panel.style.overflowY = 'auto'; panel.style.zIndex = 1000; - panel.innerHTML = '

URLs

'; + panel.style.display = 'none'; // Caché par défaut + panel.innerHTML = ` +

URLs

+
+ + +
+ +
+ + +
+ + `; document.body.appendChild(panel); - // Tooltip for previews + // Créer une infobulle pour les aperçus const tooltip = document.createElement('div'); tooltip.id = 'url-tooltip'; tooltip.style.position = 'absolute'; tooltip.style.backgroundColor = '#fff'; tooltip.style.border = '1px solid #ccc'; tooltip.style.borderRadius = '5px'; - tooltip.style.padding = '5px'; + tooltip.style.padding = '10px'; tooltip.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.2)'; tooltip.style.maxWidth = '300px'; tooltip.style.display = 'none'; tooltip.style.zIndex = 1001; document.body.appendChild(tooltip); - const urlSet = new Set(); + // Structure de données pour stocker les URLs avec leurs métadonnées + const urlData = {}; - // Extract URLs from comments and highlight them + // Extraire les URLs des commentaires et les mettre en évidence const comments = document.querySelectorAll('.commtext'); comments.forEach(comment => { const links = comment.querySelectorAll('a[href]'); links.forEach(link => { const url = link.href; - if (!urlSet.has(url)) { - urlSet.add(url); - link.style.backgroundColor = '#ffff99'; // Highlight the link + if (!urlData[url]) { + urlData[url] = { + url: url, + count: 1, + upvotes: 0 // Placeholder, à implémenter si les upvotes sont disponibles + }; + link.style.backgroundColor = '#ffff99'; // Couleur de surbrillance initiale - // Add the URL to the floating panel + // Ajouter l'URL à la liste dans le panneau const listItem = document.createElement('li'); listItem.textContent = url; listItem.style.marginBottom = '5px'; listItem.style.wordWrap = 'break-word'; listItem.style.cursor = 'pointer'; listItem.dataset.url = url; - document.getElementById('url-list').appendChild(listItem); + panel.querySelector('#url-list').appendChild(listItem); + } else { + urlData[url].count += 1; } - // Add hover preview functionality to comment links + // Ajouter la fonctionnalité d'aperçu au survol des liens link.addEventListener('mouseover', () => { GM_xmlhttpRequest({ method: 'GET', @@ -76,14 +127,14 @@ tooltip.innerHTML = ''; const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, 'text/html'); - const title = doc.querySelector('title') ? doc.querySelector('title').innerText : 'No Title'; - const description = doc.querySelector('meta[name="description"]') ? doc.querySelector('meta[name="description"]').content : 'No Description'; + const title = doc.querySelector('title') ? doc.querySelector('title').innerText : 'Pas de titre'; + const description = doc.querySelector('meta[name="description"]') ? doc.querySelector('meta[name="description"]').content : 'Pas de description'; tooltip.innerHTML = `${title}

${description}

`; tooltip.style.display = 'block'; }, onerror: () => { - tooltip.innerHTML = 'Preview unavailable'; + tooltip.innerHTML = 'Aperçu indisponible'; tooltip.style.display = 'block'; } }); @@ -99,10 +150,118 @@ }); }); - // Handle the Copy URLs button - document.getElementById('copy-urls').addEventListener('click', () => { - const urlArray = Array.from(urlSet); - GM_setClipboard(urlArray.join('\n'), 'text'); - alert('URLs copied to clipboard!'); + // 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.textContent = item.url; + 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; + + // 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(); + })();