Skip to content

Instantly share code, notes, and snippets.

@overflowy
Last active September 7, 2025 19:57
Show Gist options
  • Save overflowy/bf5d9aedffcd46242a253a3ddf1271b4 to your computer and use it in GitHub Desktop.
Save overflowy/bf5d9aedffcd46242a253a3ddf1271b4 to your computer and use it in GitHub Desktop.
Hacker News Plus
// ==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();
@sachahjkl
Copy link

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 !

// ==UserScript==
// @name        Favicons for HN
// @namespace   Violentmonkey Scripts
// @match https://*.ycombinator.com/*
// @grant       none
// @version     1.0
// @author      [email protected]
// @description 07/06/2023, 08:48:00 AM
// @inject-into content
// ==/UserScript==

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://s2.googleusercontent.com/s2/favicons?domain=${domain}&sz=32`;
		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);
	}
}

@polyrabbit
Copy link

Alternative - https://hackernews.betacat.io/

with:

  • favicon
  • summary
  • illustration

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment