2024-11-28 13:34:25 +00:00
// ==UserScript==
2024-12-31 14:37:55 +00:00
// @name Hacker News URL Highlighter with Enhanced Panel and Scores
2024-11-28 13:34:25 +00:00
// @namespace https://news.ycombinator.com/
2024-12-31 14:37:55 +00:00
// @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.
2024-11-28 13:34:25 +00:00
// @author MorganGeek
// @match https://news.ycombinator.com/item?id=*
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @connect *
// @run-at document-end
// ==/UserScript==
( function ( ) {
'use strict' ;
2024-12-31 14:32:58 +00:00
// 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)
2024-11-28 13:34:25 +00:00
const panel = document . createElement ( 'div' ) ;
panel . id = 'url-panel' ;
panel . style . position = 'fixed' ;
panel . style . top = '10px' ;
panel . style . right = '10px' ;
2024-12-31 14:37:55 +00:00
panel . style . width = '350px' ;
2024-11-28 13:34:25 +00:00
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 ;
2024-12-31 14:32:58 +00:00
panel . style . display = 'none' ; // Caché par défaut
panel . innerHTML = `
< h4 > URLs < / h 4 >
< div style = "margin-bottom: 10px;" >
< label for = "sort-select" > Trier par : < / l a b e l >
< select id = "sort-select" style = "width: 100%; padding: 5px; margin-top: 5px;" >
< option value = "frequency" > Fréquence < / o p t i o n >
< option value = "name" > Nom < / o p t i o n >
< option value = "upvotes" > Upvotes < / o p t i o n >
< / s e l e c t >
< / d i v >
< ul id = "url-list" style = "margin: 0; padding: 0; list-style: none;" > < / u l >
< div style = "margin-top: 10px;" >
< label for = "limit-select" > Afficher : < / l a b e l >
< select id = "limit-select" style = "width: 100%; padding: 5px; margin-top: 5px;" >
< option value = "all" > Tout < / o p t i o n >
< option value = "10" > Top 10 < / o p t i o n >
< option value = "20" > Top 20 < / o p t i o n >
< / s e l e c t >
< / d i v >
< button id = "copy-urls" style = "margin-top: 10px; display: block; width: 100%; padding: 10px; background-color: #ff6600; color: #fff; border: none; border-radius: 5px; cursor: pointer;" > Copier les URLs < / b u t t o n >
` ;
2024-11-28 13:34:25 +00:00
document . body . appendChild ( panel ) ;
2024-12-31 14:32:58 +00:00
// Créer une infobulle pour les aperçus
2024-11-28 13:34:25 +00:00
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' ;
2024-12-31 14:32:58 +00:00
tooltip . style . padding = '10px' ;
2024-11-28 13:34:25 +00:00
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 ) ;
2024-12-31 14:32:58 +00:00
// Structure de données pour stocker les URLs avec leurs métadonnées
const urlData = { } ;
2024-11-28 13:34:25 +00:00
2024-12-31 14:32:58 +00:00
// Extraire les URLs des commentaires et les mettre en évidence
2024-11-28 13:34:25 +00:00
const comments = document . querySelectorAll ( '.commtext' ) ;
comments . forEach ( comment => {
const links = comment . querySelectorAll ( 'a[href]' ) ;
links . forEach ( link => {
const url = link . href ;
2024-12-31 14:32:58 +00:00
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
2024-11-28 13:34:25 +00:00
2024-12-31 14:32:58 +00:00
// Ajouter l'URL à la liste dans le panneau
2024-11-28 13:34:25 +00:00
const listItem = document . createElement ( 'li' ) ;
2024-12-31 14:37:55 +00:00
listItem . innerHTML = ` <span> ${ url } </span> <span style="float: right; font-weight: bold;">1</span> ` ;
2024-11-28 13:34:25 +00:00
listItem . style . marginBottom = '5px' ;
listItem . style . wordWrap = 'break-word' ;
listItem . style . cursor = 'pointer' ;
listItem . dataset . url = url ;
2024-12-31 14:32:58 +00:00
panel . querySelector ( '#url-list' ) . appendChild ( listItem ) ;
} else {
urlData [ url ] . count += 1 ;
2024-12-31 14:37:55 +00:00
// Mettre à jour le compteur dans le panneau
const existingItem = panel . querySelector ( ` li[data-url=" ${ CSS . escape ( url ) } "] span:last-child ` ) ;
if ( existingItem ) {
existingItem . textContent = urlData [ url ] . count ;
}
2024-11-28 13:34:25 +00:00
}
2024-12-31 14:32:58 +00:00
// Ajouter la fonctionnalité d'aperçu au survol des liens
2024-11-28 13:34:25 +00:00
link . addEventListener ( 'mouseover' , ( ) => {
GM _xmlhttpRequest ( {
method : 'GET' ,
url : url ,
onload : response => {
tooltip . innerHTML = '' ;
const parser = new DOMParser ( ) ;
const doc = parser . parseFromString ( response . responseText , 'text/html' ) ;
2024-12-31 14:37:55 +00:00
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' ;
2024-11-28 13:34:25 +00:00
tooltip . innerHTML = ` <strong> ${ title } </strong><p> ${ description } </p> ` ;
tooltip . style . display = 'block' ;
} ,
onerror : ( ) => {
2024-12-31 14:37:55 +00:00
tooltip . innerHTML = '<strong>Preview unavailable</strong>' ;
2024-11-28 13:34:25 +00:00
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' ;
} ) ;
} ) ;
} ) ;
2024-12-31 14:32:58 +00:00
// 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 ;
2024-12-31 14:37:55 +00:00
// Créer le contenu avec URL et score
listItem . innerHTML = `
< span > $ { item . url } < / s p a n >
< span style = "float: right; font-weight: bold;" > $ { item . count } $ { item . upvotes > 0 ? ` / ${ item . upvotes } ` : '' } < / s p a n >
` ;
2024-12-31 14:32:58 +00:00
// 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 ) ;
2024-11-28 13:34:25 +00:00
GM _setClipboard ( urlArray . join ( '\n' ) , 'text' ) ;
2024-12-31 14:32:58 +00:00
alert ( 'URLs copiées dans le presse-papiers!' ) ;
2024-11-28 13:34:25 +00:00
} ) ;
2024-12-31 14:32:58 +00:00
// 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.
2024-12-31 14:37:55 +00:00
2024-12-31 14:32:58 +00:00
// 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 ( ) ;
2024-11-28 13:34:25 +00:00
} ) ( ) ;