Skip to content

Instantly share code, notes, and snippets.

@postmalloc
Last active August 30, 2023 08:10
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save postmalloc/e2602752d46c5b9dee24462356f96cca to your computer and use it in GitHub Desktop.
Save postmalloc/e2602752d46c5b9dee24462356f96cca to your computer and use it in GitHub Desktop.
Hacker News comments sidebar bookmarklet
// A handy bookmarklet to display comments from the top-rated Hacker News thread related to the current page
// Written with the help of GPT-4
javascript:(function() {
const createCommentElement = (comment, depth) => {
const commentWrapper = document.createElement('div');
commentWrapper.style.paddingLeft = (depth * 20) + 'px';
commentWrapper.style.marginBottom = '10px';
commentWrapper.style.marginLeft = '10px';
commentWrapper.style.color = '#333';
commentWrapper.style.background = '#f6f6ef';
commentWrapper.style.fontFamily = 'Verdana, Geneva, sans-serif';
commentWrapper.style.fontSize = '14px';
const toggleButton = document.createElement('button');
toggleButton.textContent = '[-]';
toggleButton.style.marginRight = '10px';
toggleButton.style.background = 'none';
toggleButton.style.border = 'none';
toggleButton.style.color = '#888';
toggleButton.style.cursor = 'pointer';
commentWrapper.appendChild(toggleButton);
const commentText = document.createElement('span');
const decoded = new DOMParser().parseFromString(comment.text, 'text/html').body.textContent || '';
const author = comment.author || 'Anonymous';
const formattedDate = new Date(comment.created_at).toLocaleString();
const authorElement = document.createElement('i');
authorElement.textContent = author;
const dateElement = document.createElement('span');
dateElement.textContent = formattedDate;
dateElement.style.color = '#888';
const contentElement = document.createElement('span');
insertTextWithLinks(decoded, contentElement);
commentText.appendChild(authorElement);
commentText.appendChild(document.createTextNode(' '));
commentText.appendChild(dateElement);
commentText.appendChild(document.createElement('br'));
commentText.appendChild(contentElement);
commentWrapper.appendChild(commentText);
const childrenWrapper = document.createElement('div');
childrenWrapper.style.marginLeft = '20px';
childrenWrapper.style.marginTop = '10px';
commentWrapper.appendChild(childrenWrapper);
let isCollapsed = false;
toggleButton.addEventListener('click', () => {
if(isCollapsed) {
commentText.style.display = '';
childrenWrapper.style.display = '';
toggleButton.textContent = '[-]';
isCollapsed = false;
} else {
commentText.style.display = 'none';
childrenWrapper.style.display = 'none';
toggleButton.textContent = '[+]';
isCollapsed = true;
}
});
return { commentWrapper, childrenWrapper };
};
const addCommentsToSidebar = (comments, depth, sidebarContent) => {
for(let c of comments) {
if(c.text){
const { commentWrapper, childrenWrapper } = createCommentElement(c, depth);
sidebarContent.appendChild(commentWrapper);
if (c.children) {
addCommentsToSidebar(c.children, depth + 1, childrenWrapper);
}
}
}
};
const insertTextWithLinks = (text, contentElement) => {
const urlRegex = /(https?:\/\/[^\s]+)/g;
let lastIndex = 0;
let match;
while (match = urlRegex.exec(text)) {
if (match.index > lastIndex) {
contentElement.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
}
const anchor = document.createElement('a');
anchor.href = match[0];
anchor.target = "_blank";
anchor.rel = "noopener noreferrer";
anchor.style.color = "#333";
anchor.style.textDecoration = "underline";
anchor.textContent = match[0];
contentElement.appendChild(anchor);
lastIndex = urlRegex.lastIndex;
}
if (lastIndex < text.length) {
contentElement.appendChild(document.createTextNode(text.slice(lastIndex)));
}
};
const url = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(document.location.href)}&tags=story`;
fetch(url)
.then((response) => response.json())
.then((data) => {
if(data.hits.length === 0){
alert('This page has not been posted on Hacker News.');
return;
}
const highestScoringStory = data.hits.reduce((prev, cur) => (prev.points > cur.points) ? prev : cur);
const storyId = highestScoringStory.objectID;
const commentsUrl = `https://hn.algolia.com/api/v1/items/${storyId}`;
fetch(commentsUrl)
.then((response) => response.json())
.then((data) => {
const sidebar = document.createElement('div');
sidebar.className = 'hn-comments-sidebar'; /* Add a unique class name for CSS scoping */
sidebar.innerHTML = `
<style>
.hn-comments-sidebar {
position:fixed;
right:0;
top:0;
width:600px;
height:100%;
background-color:#f6f6ef;
overflow-y:auto;
padding:1em;
z-index:999999;
font-family:Verdana, Geneva, sans-serif;
font-size:14px;
color:#333;
}
.hn-comments-sidebar a {
color:#333;
text-decoration:underline;
}
</style>
`;
const sidebarContent = document.createElement('div');
sidebarContent.style.overflowY = 'scroll';
sidebarContent.style.height = '100%';
sidebar.appendChild(sidebarContent);
addCommentsToSidebar(data.children, 0, sidebarContent);
const dragHandle = document.createElement('div');
dragHandle.style.cssText = 'position:absolute;top:0;left:0;bottom:0;width:10px;background:rgba(0,0,0,0.1);cursor:ew-resize;';
sidebar.appendChild(dragHandle);
let isDragging = false;
let initialX = 0;
let initialWidth = 0;
dragHandle.addEventListener('mousedown', (e) => {
isDragging = true;
initialX = e.clientX;
initialWidth = sidebar.offsetWidth;
});
window.addEventListener('mousemove', (e) => {
if(isDragging) {
const offsetX = e.clientX - initialX;
const newWidth = Math.max(400, initialWidth - offsetX);
sidebar.style.width = newWidth + 'px';
}
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
sidebar.addEventListener('scroll', (e) => {
dragHandle.style.top = sidebar.scrollTop + 'px';
});
document.body.appendChild(sidebar);
})
.catch((error) => console.error('Error:', error));
})
.catch((error) => console.error('Error:', error));
})();
@postmalloc
Copy link
Author

postmalloc commented Jun 24, 2023

Usage:

  1. Right-click on the bookmarks bar and select "Add Page..." or similar
  2. In the "Name" field, add a name (e.g., "HN Comments")
  3. In the "URL" or "Location" field, paste the above code (exclude the commented lines on top)
  4. Use it any page that's discussed on HN :)

Note: It will not work on websites (e.g. github.com) that have CSP restrictions

Screenshot:
image

@postmalloc
Copy link
Author

postmalloc commented Jun 24, 2023

WARNING: Risk of XSS, as highlighted by HN user @arkadiyt's comment here

Use at your own risk!

@postmalloc
Copy link
Author

Update: As suggested in the HN discussion, I removed the reliance on escaping, and instead create DOM nodes directly. This should minimise the chances of XSS.

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