186 lines
7.2 KiB
JavaScript
186 lines
7.2 KiB
JavaScript
// ==UserScript==
|
|
// @name Hacker News URL Highlighter with Enhanced Panel and Scores & Comments
|
|
// @namespace https://news.ycombinator.com/
|
|
// @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
|
|
// @grant GM_setClipboard
|
|
// @connect *
|
|
// @run-at document-end
|
|
// ==/UserScript==
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
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);
|
|
|
|
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;">
|
|
<label for="sort-select">Trier par:</label>
|
|
<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;">
|
|
<label for="limit-select">Afficher:</label>
|
|
<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:#ff6600;color:#fff;border:none;border-radius:5px;cursor:pointer;">
|
|
Copier les URLs
|
|
</button>
|
|
`;
|
|
document.body.appendChild(panel);
|
|
|
|
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);
|
|
|
|
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: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';
|
|
}
|
|
});
|
|
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';});
|
|
});
|
|
});
|
|
|
|
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}`:''}${item.commentCount>0?` / ${item.commentCount}`:''}
|
|
</span>
|
|
`;
|
|
li.addEventListener('click',()=>{
|
|
GM_setClipboard(item.url,'text');
|
|
alert('URL copiée.');
|
|
});
|
|
urlList.appendChild(li);
|
|
});
|
|
}
|
|
|
|
updatePanel();
|
|
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.');
|
|
});
|
|
|
|
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;});
|
|
}
|
|
}
|
|
});
|
|
|
|
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();
|
|
})();
|