Instantly share code, notes, and snippets.
Last active
August 30, 2023 08:10
-
Star
(14)
14
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save postmalloc/e2602752d46c5b9dee24462356f96cca to your computer and use it in GitHub Desktop.
Hacker News comments sidebar bookmarklet
This file contains 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
// 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)); | |
})(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.