Last active
September 7, 2025 19:57
-
-
Save overflowy/bf5d9aedffcd46242a253a3ddf1271b4 to your computer and use it in GitHub Desktop.
Hacker News Plus
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name HN Plus | |
// @match https://*.ycombinator.com/* | |
// @grant none | |
// @version 2.1 | |
// @author overflowy | |
// @description Adds favicons to HN links and navigation menu for less known sections | |
// @inject-into content | |
// ==/UserScript== | |
// Add favicons functionality | |
var favicons = document.getElementsByClassName("favicon"); | |
if (!(favicons.length > 0)) { | |
const articleLinks = document.querySelectorAll(".titleline > a"); | |
for (let link of articleLinks) { | |
const domain = new URL(link.href).hostname; | |
const imageUrl = `https://icons.duckduckgo.com/ip3/${domain}.ico`; | |
const imgEl = document.createElement("img"); | |
imgEl.src = imageUrl; | |
imgEl.className = "favicon"; | |
imgEl.width = 14; | |
imgEl.height = 14; | |
imgEl.style.paddingRight = "0.25em"; | |
imgEl.style.paddingLeft = "0.25em"; | |
link.style.alignItems = "center"; | |
link.style.display = "inline-flex"; | |
link.style.justifyContent = "center"; | |
link.prepend(imgEl); | |
} | |
} | |
// Add navigation menu | |
function createNavigationMenu() { | |
// Look for the submit link in the main navigation | |
const submitLink = document.querySelector('.pagetop a[href="submit"]'); | |
if (!submitLink) return; | |
// Create the menu button styled like other HN links | |
const menuButton = document.createElement('a'); | |
menuButton.href = '#'; | |
menuButton.textContent = 'extra'; | |
menuButton.style.cssText = 'color: #000000; text-decoration: none;'; | |
// Create the dropdown menu | |
const dropdown = document.createElement('div'); | |
dropdown.className = 'hn-dropdown'; | |
dropdown.style.cssText = ` | |
position: absolute; | |
top: 100%; | |
left: 0; | |
background: #f6f6ef; | |
border: 1px solid #ff6600; | |
z-index: 1000; | |
display: none; | |
min-width: 120px; | |
margin-top: 2px; | |
font-size: 10pt; | |
`; | |
const menuItems = [ | |
{ url: 'https://news.ycombinator.com/shownew', label: 'shownew' }, | |
{ url: 'https://news.ycombinator.com/pool', label: 'pool' }, | |
{ url: 'https://news.ycombinator.com/best', label: 'best' }, | |
{ url: 'https://news.ycombinator.com/asknew', label: 'asknew' }, | |
{ url: 'https://news.ycombinator.com/bestcomments', label: 'bestcomments' }, | |
{ url: 'https://news.ycombinator.com/active', label: 'active' }, | |
{ url: 'https://news.ycombinator.com/noobcomments', label: 'newcomments' }, | |
{ url: 'https://news.ycombinator.com/noobstories', label: 'newstories' }, | |
{ url: 'https://news.ycombinator.com/newest', label: 'newest' } | |
]; | |
menuItems.forEach((item, index) => { | |
const menuItem = document.createElement('a'); | |
menuItem.href = item.url; | |
menuItem.textContent = item.label; | |
menuItem.style.cssText = ` | |
display: block; | |
padding: 4px 8px; | |
color: #000000; | |
text-decoration: none; | |
font-size: 10pt; | |
${index < menuItems.length - 1 ? 'border-bottom: 1px solid #ff6600;' : ''} | |
`; | |
menuItem.onmouseover = function() { | |
this.style.backgroundColor = '#ffffff'; | |
}; | |
menuItem.onmouseout = function() { | |
this.style.backgroundColor = 'transparent'; | |
}; | |
dropdown.appendChild(menuItem); | |
}); | |
// Create a wrapper for positioning | |
const wrapper = document.createElement('span'); | |
wrapper.style.position = 'relative'; | |
wrapper.style.display = 'inline'; | |
wrapper.appendChild(menuButton); | |
wrapper.appendChild(dropdown); | |
// Find the separator after submit and insert our menu | |
let nextNode = submitLink.nextSibling; | |
while (nextNode && nextNode.nodeType === 3 && nextNode.textContent.trim() === '') { | |
nextNode = nextNode.nextSibling; | |
} | |
// Insert separator and the menu button after submit | |
const separator = document.createTextNode(' | '); | |
submitLink.parentNode.insertBefore(separator, nextNode); | |
submitLink.parentNode.insertBefore(wrapper, nextNode); | |
// Toggle menu visibility | |
menuButton.addEventListener('click', function(e) { | |
e.preventDefault(); | |
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; | |
}); | |
// Close menu when clicking outside | |
document.addEventListener('click', function(e) { | |
if (!wrapper.contains(e.target)) { | |
dropdown.style.display = 'none'; | |
} | |
}); | |
} | |
createNavigationMenu(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
here's an alternative that uses the google API to fetch the favicons. Don't know why but on my devices, all the favicons coming from the duckduckgo API render out to a grey circled arrow.
Thank you for this cool user-script though !