feat(popularity) add ranking by nb# of comments
This commit is contained in:
parent
4cfa80e9fc
commit
b33b45de94
@ -1,8 +1,8 @@
|
||||
// ==UserScript==
|
||||
// @name Hacker News URL Highlighter with Enhanced Panel and Scores
|
||||
// @name Hacker News URL Highlighter with Enhanced Panel and Scores & Comments
|
||||
// @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.
|
||||
// @version 1.6
|
||||
// @description Highlights URLs in comments, displays them in a toggleable panel with sorting (frequency, name, upvotes, comment count).
|
||||
// @author MorganGeek
|
||||
// @match https://news.ycombinator.com/item?id=*
|
||||
// @grant GM_xmlhttpRequest
|
||||
@ -11,267 +11,175 @@
|
||||
// @run-at document-end
|
||||
// ==/UserScript==
|
||||
|
||||
(function () {
|
||||
(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';
|
||||
const COLORS={low:'#d1e7dd',medium:'#fff3cd',high:'#f8d7da'};
|
||||
const toggleButton=document.createElement('button');
|
||||
toggleButton.textContent='Afficher les URLs';
|
||||
Object.assign(toggleButton.style,{
|
||||
position:'fixed',bottom:'10px',right:'10px',zIndex:1000,padding:'10px 15px',
|
||||
backgroundColor:'#ff6600',color:'#fff',border:'none',borderRadius:'5px',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 = `
|
||||
const panel=document.createElement('div');
|
||||
panel.id='url-panel';
|
||||
Object.assign(panel.style,{
|
||||
position:'fixed',top:'10px',right:'10px',width:'350px',backgroundColor:'#fff',
|
||||
border:'1px solid #ccc',borderRadius:'5px',padding:'10px',boxShadow:'0 0 10px rgba(0,0,0,.2)',
|
||||
maxHeight:'80vh',overflowY:'auto',zIndex:1000,display:'none'
|
||||
});
|
||||
panel.innerHTML=`
|
||||
<h4>URLs</h4>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div style="margin-bottom:10px;">
|
||||
<label for="sort-select">Trier par:</label>
|
||||
<select id="sort-select" style="width: 100%; padding: 5px; margin-top: 5px;">
|
||||
<select id="sort-select" style="width:100%;padding:5px;margin-top:5px;">
|
||||
<option value="frequency">Fréquence</option>
|
||||
<option value="name">Nom</option>
|
||||
<option value="upvotes">Upvotes</option>
|
||||
<option value="commentaires">Commentaires</option>
|
||||
</select>
|
||||
</div>
|
||||
<ul id="url-list" style="margin: 0; padding: 0; list-style: none;"></ul>
|
||||
<div style="margin-top: 10px;">
|
||||
<ul id="url-list" style="margin:0;padding:0;list-style:none;"></ul>
|
||||
<div style="margin-top:10px;">
|
||||
<label for="limit-select">Afficher:</label>
|
||||
<select id="limit-select" style="width: 100%; padding: 5px; margin-top: 5px;">
|
||||
<select id="limit-select" style="width:100%;padding:5px;margin-top:5px;">
|
||||
<option value="all">Tout</option>
|
||||
<option value="10">Top 10</option>
|
||||
<option value="20">Top 20</option>
|
||||
</select>
|
||||
</div>
|
||||
<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</button>
|
||||
<button id="copy-urls" style="margin-top:10px;display:block;width:100%;padding:10px;background:#ff6600;color:#fff;border:none;border-radius:5px;cursor:pointer;">
|
||||
Copier les URLs
|
||||
</button>
|
||||
`;
|
||||
document.body.appendChild(panel);
|
||||
|
||||
// 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 = '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;
|
||||
const tooltip=document.createElement('div');
|
||||
tooltip.id='url-tooltip';
|
||||
Object.assign(tooltip.style,{
|
||||
position:'absolute',background:'#fff',border:'1px solid #ccc',borderRadius:'5px',
|
||||
padding:'10px',boxShadow:'0 0 10px rgba(0,0,0,.2)',maxWidth:'300px',
|
||||
display:'none',zIndex:1001
|
||||
});
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
// Structure de données pour stocker les URLs avec leurs métadonnées
|
||||
const urlData = {};
|
||||
|
||||
// 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 (!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
|
||||
|
||||
// Ajouter l'URL à la liste dans le panneau
|
||||
const listItem = document.createElement('li');
|
||||
listItem.innerHTML = `<span>${url}</span> <span style="float: right; font-weight: bold;">1</span>`;
|
||||
listItem.style.marginBottom = '5px';
|
||||
listItem.style.wordWrap = 'break-word';
|
||||
listItem.style.cursor = 'pointer';
|
||||
listItem.dataset.url = url;
|
||||
panel.querySelector('#url-list').appendChild(listItem);
|
||||
} else {
|
||||
urlData[url].count += 1;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter la fonctionnalité d'aperçu au survol des liens
|
||||
link.addEventListener('mouseover', () => {
|
||||
const urlData={};
|
||||
const comments=document.querySelectorAll('.commtext');
|
||||
comments.forEach(comment=>{
|
||||
const links=comment.querySelectorAll('a[href]');
|
||||
links.forEach(link=>{
|
||||
const u=link.href;
|
||||
if(!urlData[u]) urlData[u]={url:u,count:0,upvotes:0,commentCount:0};
|
||||
urlData[u].count++;
|
||||
link.style.backgroundColor='#ffff99';
|
||||
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');
|
||||
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';
|
||||
|
||||
tooltip.innerHTML = `<strong>${title}</strong><p>${description}</p>`;
|
||||
tooltip.style.display = 'block';
|
||||
method:'GET',url:u,
|
||||
onload:r=>{
|
||||
tooltip.innerHTML='';
|
||||
const d=new DOMParser().parseFromString(r.responseText,'text/html');
|
||||
const t=d.querySelector('title')?d.querySelector('title').innerText:'No Title';
|
||||
const desc=d.querySelector('meta[name="description"]')?d.querySelector('meta[name="description"]').content:'No Description';
|
||||
tooltip.innerHTML=`<strong>${t}</strong><p>${desc}</p>`;
|
||||
tooltip.style.display='block';
|
||||
},
|
||||
onerror: () => {
|
||||
tooltip.innerHTML = '<strong>Preview unavailable</strong>';
|
||||
tooltip.style.display = 'block';
|
||||
onerror:()=>{
|
||||
tooltip.innerHTML='<strong>Preview unavailable</strong>';
|
||||
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';
|
||||
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 = `
|
||||
function updatePanel(){
|
||||
const sortBy=panel.querySelector('#sort-select').value;
|
||||
const limit=panel.querySelector('#limit-select').value;
|
||||
let arr=Object.values(urlData);
|
||||
if(sortBy==='frequency') arr.sort((a,b)=>b.count-a.count);
|
||||
else if(sortBy==='name') arr.sort((a,b)=>a.url.localeCompare(b.url));
|
||||
else if(sortBy==='upvotes') arr.sort((a,b)=>b.upvotes-a.upvotes);
|
||||
else if(sortBy==='commentaires') arr.sort((a,b)=>b.commentCount-a.commentCount);
|
||||
if(limit!=='all') arr=arr.slice(0,parseInt(limit));
|
||||
const urlList=panel.querySelector('#url-list');
|
||||
urlList.innerHTML='';
|
||||
const counts=arr.map(i=>i.count);
|
||||
const maxC=Math.max(...counts),minC=Math.min(...counts);
|
||||
arr.forEach(item=>{
|
||||
const li=document.createElement('li');
|
||||
li.style.marginBottom='5px';li.style.wordWrap='break-word';li.style.cursor='pointer';
|
||||
li.dataset.url=item.url;
|
||||
const r=(item.count-minC)/(maxC-minC+1);
|
||||
li.style.backgroundColor=r>0.66?COLORS.high:r>0.33?COLORS.medium:COLORS.low;
|
||||
li.innerHTML=`
|
||||
<span>${item.url}</span>
|
||||
<span style="float: right; font-weight: bold;">${item.count}${item.upvotes > 0 ? ` / ${item.upvotes}` : ''}</span>
|
||||
<span style="float:right;font-weight:bold;">
|
||||
${item.count}${item.upvotes>0?` / ${item.upvotes}`:''}${item.commentCount>0?` / ${item.commentCount}`:''}
|
||||
</span>
|
||||
`;
|
||||
|
||||
// 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!');
|
||||
li.addEventListener('click',()=>{
|
||||
GM_setClipboard(item.url,'text');
|
||||
alert('URL copiée.');
|
||||
});
|
||||
|
||||
urlList.appendChild(listItem);
|
||||
urlList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// 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';
|
||||
}
|
||||
panel.querySelector('#sort-select').addEventListener('change',updatePanel);
|
||||
panel.querySelector('#limit-select').addEventListener('change',updatePanel);
|
||||
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';}
|
||||
});
|
||||
panel.querySelector('#copy-urls').addEventListener('click',()=>{
|
||||
GM_setClipboard(Object.keys(urlData).join('\n'),'text');
|
||||
alert('URLs copiées.');
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
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 pts=pointsSpan?parseInt(pointsSpan.textContent):0;
|
||||
const c=row.querySelector('.commtext');
|
||||
if(c){
|
||||
const links=c.querySelectorAll('a[href]');
|
||||
links.forEach(l=>{if(urlData[l.href]) urlData[l.href].upvotes+=pts;});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mettre à jour le panneau après avoir collecté les upvotes
|
||||
const childrenMap={};
|
||||
commentRows.forEach(r=>{
|
||||
const id=r.id;const pid=r.getAttribute('data-parent');
|
||||
if(!pid||!id) return;
|
||||
if(!childrenMap[pid]) childrenMap[pid]=[];
|
||||
childrenMap[pid].push(id);
|
||||
});
|
||||
const cache={};
|
||||
function size(id){
|
||||
if(!childrenMap[id]) return 0;
|
||||
if(cache[id]!=null) return cache[id];
|
||||
let s=childrenMap[id].length;
|
||||
childrenMap[id].forEach(k=>{s+=size(k);});
|
||||
cache[id]=s;return s;
|
||||
}
|
||||
commentRows.forEach(r=>{
|
||||
const id=r.id;
|
||||
const c=r.querySelector('.commtext');
|
||||
if(!id||!c) return;
|
||||
const count=size(id);
|
||||
const links=c.querySelectorAll('a[href]');
|
||||
links.forEach(l=>{if(urlData[l.href]) urlData[l.href].commentCount+=count;});
|
||||
});
|
||||
|
||||
updatePanel();
|
||||
|
||||
})();
|
||||
|
Loading…
x
Reference in New Issue
Block a user