snippets/tampermonkey/hn_url_highlighter_with_preview.js

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();
})();