Show HN: Display a bell emoji on HN comments you haven't read yet
gist.github.com
gist.github.com
Looking at settings and links on the page I don't see any functionality like you describe.
You mentioned that the "toggle all comments" button is broken, could you elaborate a bit more on that, and if possible open a GitHub issue for it? Thanks a lot :D.
Apart from that, with RHN you can navigate through the comments using the "J" and "K" keyboard keys and Press "Enter" on the highlighted comment to toggle it. Hope that helps too, cheers :)
Well, I tried the [toggle all comments] button on a couple of stories before posting my comment, and it was only toggling the first comment. But I did more testing for this reply, and now it’s working as advertised. I can’t explain that, but if I every figure out the conditions that prevent it from working, I’ll open an issue.
[ ...document.querySelectorAll('td.ind[indent="1"] + td.votelinks + td.default .comhead > .togg') ].forEach(div => div.click())HN should copy this feature. The top thread dominates the discussion all too often.
javascript:if (typeof hn_next_comment_to_jump_to == 'undefined') { pkqcbcnll = 0 }; document.querySelectorAll("img[src='s.gif'][width='0']")[hn_next_comment_to_jump_to++].scrollIntoView(true); javascript:if (typeof hn_next_comment_to_jump_to == 'undefined') { hn_next_comment_to_jump_to = 0 }; document.querySelectorAll("img[src='s.gif'][width='0']")[hn_next_comment_to_jump_to++].scrollIntoView(true);
Thanks for this! var collapseButton = document.createElement("button")
collapseButton.textContent = "Collapse level 2 comments"
collapseButton.onclick = (event) => document
.querySelectorAll('td[indent="1"]+td+td.default .togg')
.forEach((button) => {
if (button.textContent == "[–]") {
button.click()
}
})
var expandButton = document.createElement("button")
expandButton.textContent = "Expand level 2 comments"
expandButton.onclick = (event) => document
.querySelectorAll('td[indent="1"]+td+td.default .togg')
.forEach((button) => {
if (button.textContent != "[–]") {
button.click()
}
})
var table = document.querySelector("table.comment-tree")
table.parentNode.insertBefore(collapseButton, table)
table.parentNode.insertBefore(expandButton, table) a:link{
color: orange !important;
}
/* visited link */
a:visited {
color: green !important;
}
https://mybrowseraddon.com/custom-style-script.htmlFor example, do a Google search and click one of the links. Now do the same search again. The link you followed last time is now purple instead of blue. On Hacker News the links go a lighter grey.
----
Edit:
This works on Firefox:
- Install Stylus[0].
- Click the toolbar icon and choose Manage.
- Click "Write new style"
- Enter and save this:
a:link {
color: #0000EE;
}
a:visited {
color: #551A8B;
}
[0] https://addons.mozilla.org/en-US/firefox/addon/styl-us/----
Edit 2:
In Firefox WITHOUT installing any add-ons:
- Go to about:config
- Set toolkit.legacyUserProfileCustomizations.stylesheets to true
- Press Alt, click Help -> More Troubleshooting Information
- Next to "Profile Folder", click Open Folder
- Create a new folder there called "chrome"
- Make a file in that folder called userContent.css
- Enter the following CSS:
a:link {
color: #0000EE !important;
}
a:visited {
color: #551A8B !important;
}
- Restart Firefox @-moz-document domain(news.ycombinator.com) {
a:link {
color: orange !important;
}
/* visited link */
a:visited {
color: green !important;
}
}
Per-domain custom CSS supported out of the box!If you sign up to the alpha-testing HN[0], you get access to the thread feature, which is... (quoted from my forwarded mail)
> New feature: when you visit a thread, the software now highlights
> comments that are new since you last viewed the page. These are
> displayed with an orange (or whatever your topcolor is) bar to the
> left of the new comment. Refreshing the page will reset these.
Very useful, I can't imagine having HN without these indicators any more.
It would be nice if awareness of this was more visible, maybe as a link on the footer, with a list of in progress or planned features.
Yet I do this more on HN than any other site since I don't know if someone's responded to me.
Noprocrast was... helpful, I guess, but I found myself racing to make poor quality responses sometimes, or HN was constantly locked out whenever I wanted to take a short break, instead of being able to take the time to respond with a thoughtful answer.
If you are going to post a comment that does nothing more than respond to the previous person, you are doing a disservice by forcing thousands of people to read text that is likely irrelevant or unhelpful to them.
The fact that this site is designed to prevent this behavior is one of its stronger points IMHO
Response notifications will primarily be used by people to dominate the chat, and your use case doesn't negate that.
- Using the JavaScript Intersection Observer may be an interesting addition, as this would explicitly differentiate comments on the thread which you may not have scrolled down to.
I love one-thing-well items of code instead of large (and thus non-secure) abstractions built upon other abstractions. Big fan of the K.I.S.S principle https://en.wikipedia.org/wiki/KISS_principle
https://greasyfork.org/en/scripts/423969-hn-style-unread-con...
It uses IntersectionObserver for "exposition watch" and "crypto.subtle.digest" for SHA-1 hashes - I was pleasantly surprised those native APIs are enough to get this magic working.
It's very POC-ey (absolutely no checks for storage capacity etc) but works quite well for me.
E: Actual markers are just light green right borders on "unread" items that slightly fades out after "exposition timeout". I'm using dark userstyle for HN (https://uso.kkx.one/style/71155 ); in default theme they are probably too light to be visible.
The read comments have striped background and orange bar on the right, so identifying unread is easy.
That way the script only has to store the timestamps on which you visit a comment section, not the comment ids.
Additionally, that way you can select "highlight all comments since visit [...]" with a dropdown from which you can choose out of all recent visits you did.
Sadly, Hacker News does not provide any such metadata in the markup. (Just like the comment formatting is shit, and the markup structure is as well. HN should just copy the nesting markup and formatting 1:1 from reddit, and it'd be massively improved already).
I like the fact that HN is so "stable".
Neat!
Surprisingly it doesn't annoy me, because I'm often clicking on the comments I read anyway. (Old habit.)
EDIT: I changed it to onmousemove. Feels pretty nice!
I only wish that we didn't have to use third party extensions for loading scripts like this. I can never quite bring myself to trust them with full access to all of my pages. This really should be a part of the browser functionality already.
But I agree, it should be a browser feature.
ps im a mechanical engineer
However, I notice you have exactly 1337 karma right now. So the combination of hunter2 + 1337 is too irresistible not to mention. :) https://i.imgur.com/PM1T8DM.jpg
(It's strange that http://bash.org/?244321 "only" has ~40k upvotes. I remember upvoting it when I was in high school, and it still seems to be working today, so you'd think a famous bash.org quote would accumulate more than a mildly popular Reddit post worth of upvotes...)
Anyway, have a great week!
I remember reading http://bash.org/?5273 as a teenager and thinking "What! How could anyone possibly lose a whole computer?" But then I lost a fully-working-responds-to-ping raspberry pi a few years ago too. :)
Cheers!
That said, I'd really like a way to find, distraction free(so in some submenu), reply comments.
The current way seems narcissistic, as I have to go through my whole history to see why may have replied. And even then I have to do math to figure out if I'd seen it or not.
span.style.cssText = `
font-size: 5px;
color: transparent;
text-shadow: 0 0 0 gray;
margin-bottom: 2px;
vertical-align: middle;
display: inline-block;
`I also think it would make sense to store the page load time, and compare to a comment's post time to determine if it's new instead of storing all comment ids.
The CSS is trickier due to the HTML structure.
let style = document.createElement('style');
style.innerHTML = ".unread::after {content: ':bell:'}";
document.getElementsByTagName('head')[0].appendChild(style);
document.querySelectorAll('.athing.comtr').forEach(comm => {
if (comment is unread) {
comm.querySelector('.comhead').classList.add("unread");
}
})
That shouldn't collide with other custom CSS, and it avoids adding extra elements in the loop.Edit: I used the actual bell emoji in the code, but HN removed that, so I just replaced it with :bell:.
const div = document.createElement("div");
div.innerHTML = `<div style="position:fixed;top:20px;right:20px;width:40px;background:#ff05;">
<input type="range" orient="vertical" id="interval" min="0" max="95" value="30" style="width:100%">
<label for="interval" style="display:block"></label>
</div>`;
const input = div.firstElementChild.firstElementChild;
const label = input.nextElementSibling;
document.body.appendChild(div);
const formatTime = (mins) => {
if (mins > 60) {
return `${Math.floor(mins / 60)}h ${mins % 60}m`;
}
return `${mins}m`;
};
const extrapolate = (val) => {
val = val * 2;
for (let t = 30; t <= 150; t += 30) {
val += Math.max(0, val - t);
}
return val;
};
input.oninput = () => colorStuff(extrapolate(input.value));
const colorStuff = (interval_minutes) => {
label.innerText = `${formatTime(interval_minutes)}`;
const comments = Array.from(document.getElementsByClassName("athing comtr"));
dates = Array.from(document.getElementsByClassName('age'));
dates.sort((c1, c2) => Date.parse((c2).title) - Date.parse((c1).title));
const now = new Date();
const timestamp = now.getTime() + now.getTimezoneOffset() * 60E3;
comments.forEach((c) => {
const commentDateString = c.getElementsByClassName("age")[0].title;
const diff=(timestamp-Date.parse(c.getElementsByClassName('age')[0].title))/60E3;
const diff2=(Date.parse(dates[0].title)-Date.parse(c.getElementsByClassName('age')[0].title))/60E3;
if (diff < interval_minutes) {
const alpha = (1 - diff2 / interval_minutes)**6;
c.style.backgroundColor = `rgba(100, 255, 100, ${alpha})`;
} else {
c.style.backgroundColor = "rgba(255, 255, 255,0)";
}
});
};
colorStuff(extrapolate(input.value));Here's a similar userscript I use: https://greasyfork.org/en/scripts/18066-hn-comment-trees/
It adds a small bell icon with a dropdown to the navbar that shows new replies to your posts and comments. 10/10, would recommend.
[0]: https://chrome.google.com/webstore/detail/hacker-news-notifi...
This surely would break someone's workflow ;)
Also open the door to bugs like "why is this loading the scripts of another user?" which can be security threats.
It's "just" a simple feature, but it still have costs. I'd rather have the browser allowing me to load scripts without relying on an extension.
Given how many people here long for the quirky old days of the web, I say whomever starts a thread should also get to create a custom CSS theme for it.
There is a Chrome extension "Custom Javascript for Website" (link in the first line of the snippet) that lets you add JS scripts to any website.
Install the extension, then while on hackernews, open the extension and copy-paste the snippet into it.
function checkquota(){
// localStorage limit is ~5MB, should probably do something about exceeding quota ;)
// lets trigger around 3MB
// deleting half of the oldest empty(seen, never interacted with) entries first
// popping a warning when we finally reach over 2MB non empty
//
// total entries: localStorage.length
// storage bytes left: 1024 * 1024 * 5 - escape(encodeURIComponent(JSON.stringify(localStorage))).length
// non empty entries: Object.entries(localStorage).filter(value => value[1] != "1")
// non empty entries size: escape(encodeURIComponent(JSON.stringify(Object.entries(localStorage).filter(value => value[1] != "1")))).length
// empty entries sorted, oldest first Object.entries(localStorage).filter(value => value[1] == "1").sort(function (a, b) {return parseInt(a[0]) - parseInt(b[0]);});
//size check is expensive, can be as long as 500ms for full localstorage, limit size checks to ~once per 24h
if (GM_getValue("lastquotacheck") > Date.now()) return;
GM_setValue("lastquotacheck", Date.now()+86400000);
let size = encodeURIComponent(JSON.stringify(localStorage)).length;
console.log("Current size: "+size+" bytes");
if (size > 1024 * 1024 * 3)
{
alert("Current hackahackernews consumed localStorage size: "+size+" bytes, cleaning older entries to make more room.");
let sortedStorage = Object.entries(localStorage).filter(value => value[1] == "1").sort(function (a, b) {return parseInt(a[0]) - parseInt(b[0]);});
let cutout = parseInt(sortedStorage.length/2);
for (let [index, value] of sortedStorage.entries())
{
if (index < cutout)
{
localStorage.removeItem(value[0]);
}
}
size = encodeURIComponent(JSON.stringify(localStorage)).length;
console.log("Size after culling empty entries: "+size+" bytes");
let nonempty = Object.entries(localStorage).filter(value => value[1] != "1");
if (encodeURIComponent(JSON.stringify(nonempty)).length > 1024 * 1024 * 2)
{
console.log("Non empty entries size is over 2MB! currently at: "+encodeURIComponent(JSON.stringify(nonempty)).length+" bytes.");
if (confirm("Saved visited links are consuming "+encodeURIComponent(JSON.stringify(nonempty)).length+" bytes, delete half of the oldest entries?"))
{
let sortedNonempty = nonempty.sort(function (a, b) {return parseInt(a[0]) - parseInt(b[0]);});
let cutout = parseInt(sortedNonempty.length/2);
for (let [index, value] of sortedNonempty.entries())
{
if (index < cutout)
{
localStorage.removeItem(value[0]);
}
}
}
}
size = encodeURIComponent(JSON.stringify(localStorage)).length;
console.log("Final size after culling: "+size+" bytes");
}
}