🏠 Home 

Hacker News Story Rank Change Indicator

Indicate the new stories and stories moving up/down on the front page


Install this script?
// ==UserScript==
// @name         Hacker News Story Rank Change Indicator
// @namespace    http://tampermonkey.net/
// @version      2024-09-07_15-42
// @description  Indicate the new stories and stories moving up/down on the front page
// @author       SMUsamaShah
// @match        https://news.ycombinator.com/
// @match        https://news.ycombinator.com/news
// @match        https://news.ycombinator.com/news?p=*
// @match        https://news.ycombinator.com/?p=*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ycombinator.com
// @grant        none
// @license      MIT
// ==/UserScript==
(function() {
'use strict';
const KEY_LAST_CLEAR = 'hackernews-rank-notifier-clear-time';
const KEY_RANK_STORE = 'hackernews-rank-notifier';
const KEY_COMMENT_STORE = 'hackernews-rank-notifier-comments';
const MAX_STORIES = 3000;
const MAX_NEW_COMMENTS_FOR_MAX_COLOR = 50;
const HIDE_SEEN_AND_UNCHANGED_STORIES = false;
const HIDE_STORIES_WITH_FEWER_NEW_COMMENTS = false;
const HIDE_STORIES_WITH_FEWER_NEW_COMMENTS_THRESHOLD = 2;
const oldRanks = JSON.parse(localStorage.getItem(KEY_RANK_STORE)) || {};
const oldComments = JSON.parse(localStorage.getItem(KEY_COMMENT_STORE)) || {};
function getAllStoryElements() { return Array.from(document.getElementsByClassName('athing')); }
function getTitleElement(story) { return story.querySelector('.title a'); }
function getId(story) { return story.id; }
function getRank(story) { return parseInt(story.querySelector('span.rank').innerText.slice(0, -1)); }
function clamp(v, min, max) { return Math.max(min, Math.min(v, max)); }
function sortAndDiscardOldRecords(ranks, comments, keepRecordsUpto) {
const topStoryIds = Object.keys(ranks).sort((a, b) => b - a).slice(0, keepRecordsUpto);
const newRanks = {};
const newComments = {};
topStoryIds.forEach(id => {
newRanks[id] = ranks[id];
newComments[id] = comments[id];
});
localStorage.setItem(KEY_RANK_STORE, JSON.stringify(newRanks));
localStorage.setItem(KEY_COMMENT_STORE, JSON.stringify(newComments));
}
function getCommentsElement(story) { return story.nextSibling.querySelector('.subline>a:last-child'); }
function getCommentCount(story) {
let commentsElement = getCommentsElement(story);
if (!commentsElement) return 0;
let commentCountText = commentsElement.innerText.match(/([0-9]*) comment/);
return (commentCountText) ? parseInt(commentCountText[1]) : 0;
}
function hideStory(story) {
story.style.display = 'none';
story.nextElementSibling.style.display = 'none';
story.nextElementSibling.nextElementSibling.style.display = 'none';
}
function updateTitle(story, rankChange) {
let title = getTitleElement(story);
if (rankChange == undefined) {
title.innerHTML = "<b style='color:green'>(NEW) </b>" + title.innerHTML;
} else {
title.textContent = '(' + (rankChange > 0 ? '+' : '-') + Math.abs(rankChange) + ') ' + title.textContent;
}
}
function updateCommentCount(story, commentChange) {
let commentsElement = getCommentsElement(story);
if (commentsElement) {
let bright = clamp(120 - commentChange * 120 / MAX_NEW_COMMENTS_FOR_MAX_COLOR, 0, 120);
const commentColor = "rgb(255, " + bright + "," + bright + ")";
commentsElement.innerHTML = '<span style="color:' + commentColor + '">(' + (commentChange > 0 ? '+' : '-') + Math.abs(commentChange) + ') </span>' + commentsElement.innerHTML;
}
}
const stories = getAllStoryElements();
stories.forEach(function(story) {
const id = getId(story);
const rank = getRank(story);
const commentCount = getCommentCount(story);
if (id in oldRanks) {
const rankChange = oldRanks[id] - rank;
const commentChange = commentCount - (oldComments[id] || 0);
if (rankChange !== 0) {
updateTitle(story, rankChange);
}
if (commentChange !== 0) {
updateCommentCount(story, commentChange);
}
if (HIDE_SEEN_AND_UNCHANGED_STORIES && commentChange === 0) {
hideStory(story);
}
if (HIDE_STORIES_WITH_FEWER_NEW_COMMENTS && commentChange < HIDE_STORIES_WITH_FEWER_NEW_COMMENTS_THRESHOLD) {
hideStory(story);
}
}
else {
updateTitle(story);
}
oldRanks[id] = rank;
oldComments[id] = commentCount;
});
const topStoryIds = sortAndDiscardOldRecords(oldRanks, oldComments, MAX_STORIES);
})();